Files
zeekayandHanzo Dev b1cc108c9f gofmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:51:51 -07:00

205 lines
4.9 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"errors"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/node/vms/txs/mempool"
"github.com/luxfi/node/vms/xvm/block"
"github.com/luxfi/node/vms/xvm/metrics"
"github.com/luxfi/node/vms/xvm/state"
"github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/node/vms/xvm/txs/executor"
"github.com/luxfi/timer/mockable"
chain "github.com/luxfi/vm/chain"
"github.com/luxfi/vm/chains/atomic"
)
var (
_ Manager = (*manager)(nil)
ErrChainNotSynced = errors.New("chain not synced")
ErrConflictingParentTxs = errors.New("block contains a transaction that conflicts with a transaction in a parent block")
)
type Manager interface {
state.Versions
// Returns the ID of the most recently accepted block.
LastAccepted() ids.ID
SetPreference(blkID ids.ID)
Preferred() ids.ID
GetBlock(blkID ids.ID) (chain.Block, error)
GetStatelessBlock(blkID ids.ID) (block.Block, error)
NewBlock(block.Block) chain.Block
// VerifyTx verifies that the transaction can be issued based on the currently
// preferred state. This should *not* be used to verify transactions in a block.
VerifyTx(tx *txs.Tx) error
// VerifyUniqueInputs returns nil iff no blocks in the inclusive
// ancestry of [blkID] consume an input in [inputs].
VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error
}
func NewManager(
mempool mempool.Mempool[*txs.Tx],
metrics metrics.Metrics,
state state.State,
backend *executor.Backend,
clk *mockable.Clock,
onAccept func(*txs.Tx),
) Manager {
lastAccepted := state.GetLastAccepted()
return &manager{
backend: backend,
state: state,
metrics: metrics,
mempool: mempool,
clk: clk,
onAccept: onAccept,
blkIDToState: map[ids.ID]*blockState{},
lastAccepted: lastAccepted,
preferred: lastAccepted,
}
}
type manager struct {
backend *executor.Backend
state state.State
metrics metrics.Metrics
mempool mempool.Mempool[*txs.Tx]
clk *mockable.Clock
// Invariant: onAccept is called when [tx] is being marked as accepted, but
// before its state changes are applied.
onAccept func(*txs.Tx)
// blkIDToState is a map from a block's ID to the state of the block.
// Blocks are put into this map when they are verified.
// Blocks are removed from this map when they are decided.
blkIDToState map[ids.ID]*blockState
// lastAccepted is the ID of the last block that had Accept() called on it.
lastAccepted ids.ID
preferred ids.ID
}
type blockState struct {
statelessBlock block.Block
onAcceptState state.Diff
importedInputs set.Set[ids.ID]
atomicRequests map[ids.ID]*atomic.Requests
}
func (m *manager) GetState(blkID ids.ID) (state.Chain, bool) {
// If the block is in the map, it is processing.
if state, ok := m.blkIDToState[blkID]; ok {
return state.onAcceptState, true
}
return m.state, blkID == m.lastAccepted
}
func (m *manager) LastAccepted() ids.ID {
return m.lastAccepted
}
func (m *manager) SetPreference(blockID ids.ID) {
m.preferred = blockID
}
func (m *manager) Preferred() ids.ID {
return m.preferred
}
func (m *manager) GetBlock(blkID ids.ID) (chain.Block, error) {
blk, err := m.GetStatelessBlock(blkID)
if err != nil {
return nil, err
}
return m.NewBlock(blk), nil
}
func (m *manager) GetStatelessBlock(blkID ids.ID) (block.Block, error) {
// See if the block is in memory.
if blkState, ok := m.blkIDToState[blkID]; ok {
return blkState.statelessBlock, nil
}
// The block isn't in memory. Check the database.
return m.state.GetBlock(blkID)
}
func (m *manager) NewBlock(blk block.Block) chain.Block {
return &Block{
Block: blk,
manager: m,
}
}
func (m *manager) VerifyTx(tx *txs.Tx) error {
if !m.backend.Bootstrapped {
return ErrChainNotSynced
}
err := tx.Unsigned.Visit(&executor.SyntacticVerifier{
Backend: m.backend,
Tx: tx,
})
if err != nil {
return err
}
stateDiff, err := state.NewDiff(m.lastAccepted, m)
if err != nil {
return err
}
err = tx.Unsigned.Visit(&executor.SemanticVerifier{
Backend: m.backend,
State: stateDiff,
Tx: tx,
})
if err != nil {
return err
}
executor := &executor.Executor{
State: stateDiff,
Tx: tx,
Inputs: set.NewSet[ids.ID](0), // Initialize empty set for imported inputs
}
return tx.Unsigned.Visit(executor)
}
func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error {
if inputs.Len() == 0 {
return nil
}
// Check for conflicts in ancestors.
for {
state, ok := m.blkIDToState[blkID]
if !ok {
// The parent state isn't pinned in memory.
// This means the parent must be accepted already.
return nil
}
if state.importedInputs.Overlaps(inputs) {
return ErrConflictingParentTxs
}
blk := state.statelessBlock
blkID = blk.Parent()
}
}
func (m *manager) free(blkID ids.ID) {
delete(m.blkIDToState, blkID)
}