Files
zeekay f480e48e39 run on the native fleet
Adds .hanzo/workflows/cicd.yml calling the canonical pipeline and drops the
GitHub-hosted workflows it replaces. Lands here because this is where the repo
is written — git.hanzo.ai is upstream and github.com is its push mirror, so the
same change made on github.com never reaches the runner fleet.
2026-08-20 20:20:25 -07:00

238 lines
6.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"
"github.com/luxfi/log"
"github.com/luxfi/vm/chains/atomic"
"github.com/luxfi/proto/p/block"
"github.com/luxfi/proto/p/metrics"
"github.com/luxfi/proto/p/state"
"github.com/luxfi/proto/p/validators"
)
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) AbortBlock(b *block.AbortBlock) error {
return a.optionBlock(b, "abort")
}
func (a *acceptor) CommitBlock(b *block.CommitBlock) error {
return a.optionBlock(b, "commit")
}
func (a *acceptor) ProposalBlock(b *block.ProposalBlock) error {
a.proposalBlock(b, "proposal")
return nil
}
func (a *acceptor) StandardBlock(b *block.StandardBlock) error {
return a.standardBlock(b, "standard")
}
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
}