mirror of
https://github.com/luxfi/proto.git
synced 2026-07-27 07:04:45 +00:00
314 lines
8.1 KiB
Go
314 lines
8.1 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package executor
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
log "github.com/luxfi/log"
|
|
|
|
"github.com/luxfi/proto/p/block"
|
|
"github.com/luxfi/proto/p/metrics"
|
|
"github.com/luxfi/proto/p/state"
|
|
"github.com/luxfi/proto/p/validators"
|
|
"github.com/luxfi/vm/chains/atomic"
|
|
)
|
|
|
|
var (
|
|
_ block.Visitor = (*acceptor)(nil)
|
|
|
|
errMissingBlockState = errors.New("missing state of block")
|
|
)
|
|
|
|
// acceptor handles the logic for accepting a block.
|
|
// All errors returned by this struct are fatal and should result in the chain
|
|
// being shutdown.
|
|
type acceptor struct {
|
|
*backend
|
|
metrics metrics.Metrics
|
|
validators validators.Manager
|
|
}
|
|
|
|
func (a *acceptor) BanffAbortBlock(b *block.BanffAbortBlock) error {
|
|
return a.optionBlock(b, "banff abort")
|
|
}
|
|
|
|
func (a *acceptor) BanffCommitBlock(b *block.BanffCommitBlock) error {
|
|
return a.optionBlock(b, "banff commit")
|
|
}
|
|
|
|
func (a *acceptor) BanffProposalBlock(b *block.BanffProposalBlock) error {
|
|
a.proposalBlock(b, "banff proposal")
|
|
return nil
|
|
}
|
|
|
|
func (a *acceptor) BanffStandardBlock(b *block.BanffStandardBlock) error {
|
|
return a.standardBlock(b, "banff standard")
|
|
}
|
|
|
|
func (a *acceptor) ApricotAbortBlock(b *block.ApricotAbortBlock) error {
|
|
return a.optionBlock(b, "apricot abort")
|
|
}
|
|
|
|
func (a *acceptor) ApricotCommitBlock(b *block.ApricotCommitBlock) error {
|
|
return a.optionBlock(b, "apricot commit")
|
|
}
|
|
|
|
func (a *acceptor) ApricotProposalBlock(b *block.ApricotProposalBlock) error {
|
|
a.proposalBlock(b, "apricot proposal")
|
|
return nil
|
|
}
|
|
|
|
func (a *acceptor) ApricotStandardBlock(b *block.ApricotStandardBlock) error {
|
|
return a.standardBlock(b, "apricot standard")
|
|
}
|
|
|
|
func (a *acceptor) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error {
|
|
blkID := b.ID()
|
|
defer a.free(blkID)
|
|
|
|
blkState, ok := a.getBlockState(blkID)
|
|
if !ok {
|
|
return fmt.Errorf("%w %s", errMissingBlockState, blkID)
|
|
}
|
|
|
|
if err := a.commonAccept(blkState); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the state to reflect the changes made in [onAcceptState].
|
|
if err := blkState.onAcceptState.Apply(a.state); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer a.state.Abort()
|
|
batch, err := a.state.CommitBatch()
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"failed to commit VM's database for block %s: %w",
|
|
blkID,
|
|
err,
|
|
)
|
|
}
|
|
|
|
// Note that this method writes [batch] to the database.
|
|
// Apply atomic requests via SharedMemory from context
|
|
if a.rt.SharedMemory != nil {
|
|
sharedMemory := a.rt.SharedMemory.(atomic.SharedMemory)
|
|
if err := sharedMemory.Apply(blkState.atomicRequests, batch); err != nil {
|
|
return fmt.Errorf(
|
|
"failed to atomically accept tx %s in block %s: %w",
|
|
b.Tx.ID(),
|
|
blkID,
|
|
err,
|
|
)
|
|
}
|
|
} else {
|
|
// If SharedMemory is nil, we must write the batch ourselves
|
|
if err := batch.Write(); err != nil {
|
|
return fmt.Errorf("failed to write batch for block %s: %w", blkID, err)
|
|
}
|
|
}
|
|
|
|
log.Trace(
|
|
"accepted block",
|
|
log.String("blockType", "apricot atomic"),
|
|
log.Stringer("blkID", blkID),
|
|
log.Uint64("height", b.Height()),
|
|
log.Stringer("parentID", b.Parent()),
|
|
log.Stringer("checksum", a.state.Checksum()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *acceptor) optionBlock(b block.Block, blockType string) error {
|
|
parentID := b.Parent()
|
|
parentState, ok := a.getBlockState(parentID)
|
|
if !ok {
|
|
return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
|
|
}
|
|
|
|
blkID := b.ID()
|
|
defer func() {
|
|
// Note: we assume this block's sibling doesn't
|
|
// need the parent's state when it's rejected.
|
|
a.free(parentID)
|
|
a.free(blkID)
|
|
}()
|
|
|
|
// Note that the parent must be accepted first.
|
|
if err := a.commonAccept(parentState); err != nil {
|
|
return err
|
|
}
|
|
|
|
// For proposal blocks, we need to apply the onDecisionState first
|
|
// before applying the option block's state (commit or abort)
|
|
if parentState.onDecisionState != nil {
|
|
if err := parentState.onDecisionState.Apply(a.state); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
blkState, ok := a.getBlockState(blkID)
|
|
if !ok {
|
|
return fmt.Errorf("%w %s", errMissingBlockState, blkID)
|
|
}
|
|
|
|
if err := a.commonAccept(blkState); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := blkState.onAcceptState.Apply(a.state); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer a.state.Abort()
|
|
batch, err := a.state.CommitBatch()
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"failed to commit VM's database for block %s: %w",
|
|
blkID,
|
|
err,
|
|
)
|
|
}
|
|
|
|
// Note that this method writes [batch] to the database.
|
|
// Apply atomic requests via SharedMemory from context
|
|
if a.rt.SharedMemory != nil {
|
|
sharedMemory := a.rt.SharedMemory.(atomic.SharedMemory)
|
|
if err := sharedMemory.Apply(parentState.atomicRequests, batch); err != nil {
|
|
return fmt.Errorf("failed to apply vm's state to shared memory: %w", err)
|
|
}
|
|
} else {
|
|
// If SharedMemory is nil, we must write the batch ourselves
|
|
if err := batch.Write(); err != nil {
|
|
return fmt.Errorf("failed to write batch for block %s: %w", blkID, err)
|
|
}
|
|
}
|
|
|
|
if onAcceptFunc := parentState.onAcceptFunc; onAcceptFunc != nil {
|
|
onAcceptFunc()
|
|
}
|
|
|
|
log.Trace(
|
|
"accepted block",
|
|
log.String("blockType", blockType),
|
|
log.Stringer("blkID", blkID),
|
|
log.Uint64("height", b.Height()),
|
|
log.Stringer("parentID", parentID),
|
|
log.Stringer("checksum", a.state.Checksum()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *acceptor) proposalBlock(b block.Block, blockType string) {
|
|
// Note that:
|
|
//
|
|
// * We don't free the proposal block in this method.
|
|
// It is freed when its child is accepted.
|
|
// We need to keep this block's state in memory for its child to use.
|
|
//
|
|
// * We only update the metrics to reflect this block's
|
|
// acceptance when its child is accepted.
|
|
//
|
|
// * We don't write this block to state here.
|
|
// That is done when this block's child (a CommitBlock or AbortBlock) is accepted.
|
|
// We do this so that in the event that the node shuts down, the proposal block
|
|
// is not written to disk unless its child is.
|
|
// (The VM's Shutdown method commits the database.)
|
|
// The chain.Engine requires that the last committed block is a decision block
|
|
|
|
blkID := b.ID()
|
|
a.backend.lastAccepted = blkID
|
|
|
|
log.Trace(
|
|
"accepted block",
|
|
log.String("blockType", blockType),
|
|
log.Stringer("blkID", blkID),
|
|
log.Uint64("height", b.Height()),
|
|
log.Stringer("parentID", b.Parent()),
|
|
log.Stringer("checksum", a.state.Checksum()),
|
|
)
|
|
}
|
|
|
|
func (a *acceptor) standardBlock(b block.Block, blockType string) error {
|
|
blkID := b.ID()
|
|
defer a.free(blkID)
|
|
|
|
blkState, ok := a.getBlockState(blkID)
|
|
if !ok {
|
|
return fmt.Errorf("%w %s", errMissingBlockState, blkID)
|
|
}
|
|
|
|
if err := a.commonAccept(blkState); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the state to reflect the changes made in [onAcceptState].
|
|
if err := blkState.onAcceptState.Apply(a.state); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer a.state.Abort()
|
|
batch, err := a.state.CommitBatch()
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"failed to commit VM's database for block %s: %w",
|
|
blkID,
|
|
err,
|
|
)
|
|
}
|
|
|
|
// Note that this method writes [batch] to the database.
|
|
// Apply atomic requests via SharedMemory from context
|
|
if a.rt.SharedMemory != nil {
|
|
sharedMemory := a.rt.SharedMemory.(atomic.SharedMemory)
|
|
if err := sharedMemory.Apply(blkState.atomicRequests, batch); err != nil {
|
|
return fmt.Errorf("failed to apply vm's state to shared memory: %w", err)
|
|
}
|
|
} else {
|
|
// If SharedMemory is nil, we must write the batch ourselves
|
|
if err := batch.Write(); err != nil {
|
|
return fmt.Errorf("failed to write batch for block %s: %w", blkID, err)
|
|
}
|
|
}
|
|
|
|
if onAcceptFunc := blkState.onAcceptFunc; onAcceptFunc != nil {
|
|
onAcceptFunc()
|
|
}
|
|
|
|
log.Trace(
|
|
"accepted block",
|
|
log.String("blockType", blockType),
|
|
log.Stringer("blkID", blkID),
|
|
log.Uint64("height", b.Height()),
|
|
log.Stringer("parentID", b.Parent()),
|
|
log.Stringer("checksum", a.state.Checksum()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *acceptor) commonAccept(b *blockState) error {
|
|
if b == nil {
|
|
return errMissingBlockState
|
|
}
|
|
blk := b.statelessBlock
|
|
blkID := blk.ID()
|
|
|
|
if err := a.metrics.MarkAccepted(b.metrics); err != nil {
|
|
return fmt.Errorf("failed to accept block %s: %w", blkID, err)
|
|
}
|
|
|
|
a.backend.lastAccepted = blkID
|
|
a.state.SetLastAccepted(blkID)
|
|
a.state.SetHeight(blk.Height())
|
|
a.state.AddStatelessBlock(blk)
|
|
a.validators.OnAcceptedBlockID(blkID)
|
|
return nil
|
|
}
|