Files
zeekay ede37547f6 refactor(platformvm): consolidate block code, drop atomic_block, add v0 lift
- Drop atomic_block.go + legacy *_block_test.go (consolidated)
- Add block/v0/ subdir + lift_v0.go for old version handling
- Copyright header: 'Lux Industries, Inc.' → 'Lux Industries Inc' (no comma)
- block_adapter.go: new adapter layer
2026-06-01 15:22:27 -07:00

219 lines
5.9 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"context"
"errors"
"fmt"
"github.com/luxfi/vm/chain"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
platformblock "github.com/luxfi/node/vms/platformvm/block"
"github.com/luxfi/node/vms/platformvm/metrics"
"github.com/luxfi/node/vms/platformvm/state"
"github.com/luxfi/node/vms/platformvm/txs"
"github.com/luxfi/node/vms/platformvm/txs/executor"
"github.com/luxfi/node/vms/platformvm/txs/fee"
"github.com/luxfi/node/vms/platformvm/validators"
"github.com/luxfi/node/vms/txs/mempool"
)
var (
_ Manager = (*manager)(nil)
ErrChainNotSynced = errors.New("chain not synced")
ErrImportTxWhilePartialSyncing = errors.New("issuing an import tx is not allowed while partial syncing")
)
type Manager interface {
state.Versions
// Returns the ID of the most recently accepted chain.
LastAccepted() ids.ID
SetPreference(blkID ids.ID)
Preferred() ids.ID
GetBlock(blkID ids.ID) (chain.Block, error)
GetStatelessBlock(blkID ids.ID) (platformblock.Block, error)
NewBlock(platformblock.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 chain.
VerifyTx(tx *txs.Tx) error
// VerifyUniqueInputs verifies that the inputs are not duplicated in the
// provided blk or any of its ancestors pinned in memory.
VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error
}
func NewManager(
mempool mempool.Mempool[*txs.Tx],
metrics metrics.Metrics,
s state.State,
txExecutorBackend *executor.Backend,
validatorManager validators.Manager,
) Manager {
lastAccepted := s.GetLastAccepted()
backend := &backend{
Mempool: mempool,
lastAccepted: lastAccepted,
state: s,
rt: txExecutorBackend.Runtime,
blkIDToState: map[ids.ID]*blockState{},
}
return &manager{
backend: backend,
acceptor: &acceptor{
backend: backend,
metrics: metrics,
validators: validatorManager,
},
rejector: &rejector{
backend: backend,
addTxsToMempool: !txExecutorBackend.Config.PartialSyncPrimaryNetwork,
},
preferred: lastAccepted,
txExecutorBackend: txExecutorBackend,
validatorManager: validatorManager,
Log: log.Noop(),
}
}
type manager struct {
*backend
acceptor platformblock.Visitor
rejector platformblock.Visitor
preferred ids.ID
txExecutorBackend *executor.Backend
validatorManager validators.Manager
Log log.Logger
}
func (m *manager) GetBlock(blkID ids.ID) (chain.Block, error) {
blk, err := m.backend.GetBlock(blkID)
if err != nil {
return nil, err
}
return m.NewBlock(blk), nil
}
func (m *manager) GetStatelessBlock(blkID ids.ID) (platformblock.Block, error) {
return m.backend.GetBlock(blkID)
}
func (m *manager) NewBlock(blk platformblock.Block) chain.Block {
return &Block{
manager: m,
Block: blk,
}
}
func (m *manager) SetPreference(blkID ids.ID) {
m.preferred = blkID
}
func (m *manager) Preferred() ids.ID {
return m.preferred
}
func (m *manager) VerifyTx(tx *txs.Tx) error {
if !m.txExecutorBackend.Bootstrapped.Get() {
return ErrChainNotSynced
}
// If partial sync is enabled, this node isn't guaranteed to have the full
// UTXO set from shared memory. To avoid issuing invalid transactions,
// issuance of an ImportTx during this state is completely disallowed.
if m.txExecutorBackend.Config.PartialSyncPrimaryNetwork {
if _, isImportTx := tx.Unsigned.(*txs.ImportTx); isImportTx {
return ErrImportTxWhilePartialSyncing
}
}
// Get current height from validator manager
recommendedPChainHeight, err := m.validatorManager.GetCurrentHeight(context.TODO())
if err != nil {
return fmt.Errorf("failed to fetch P-chain height: %w", err)
}
err = executor.VerifyWarpMessages(
context.TODO(),
m.rt.NetworkID,
m.validatorManager,
recommendedPChainHeight,
tx.Unsigned,
)
if err != nil {
return fmt.Errorf("failed verifying warp messages: %w", err)
}
// Use preferred block for state diff. If preferred state isn't available
// (race between block acceptance and preference update), fall back to
// the last accepted block which always has committed state.
preferredID := m.preferred
stateDiff, err := state.NewDiff(preferredID, m)
if err != nil {
lastAccepted := m.state.GetLastAccepted()
if lastAccepted != preferredID {
stateDiff, err = state.NewDiff(lastAccepted, m)
}
if err != nil {
return fmt.Errorf("failed creating state diff: %w", err)
}
}
nextBlkTime, _, err := state.NextBlockTime(
m.txExecutorBackend.Config.ValidatorFeeConfig,
stateDiff,
m.txExecutorBackend.Clk,
)
if err != nil {
return fmt.Errorf("failed selecting next block time: %w", err)
}
_, err = executor.AdvanceTimeTo(m.txExecutorBackend, stateDiff, nextBlkTime)
if err != nil {
return fmt.Errorf("failed to advance the chain time: %w", err)
}
{
complexity, err := fee.TxComplexity(tx.Unsigned)
if err != nil {
return fmt.Errorf("failed to calculate tx complexity: %w", err)
}
gas, err := complexity.ToGas(m.txExecutorBackend.Config.DynamicFeeConfig.Weights)
if err != nil {
return fmt.Errorf("failed to calculate tx gas: %w", err)
}
// Check against current fee state capacity. This is intentionally
// checked against the chain tip rather than mempool capacity.
feeState := stateDiff.GetFeeState()
if gas > feeState.Capacity {
return fmt.Errorf("tx exceeds current gas capacity: %d > %d", gas, feeState.Capacity)
}
}
feeCalculator := state.PickFeeCalculator(m.txExecutorBackend.Config, stateDiff)
_, _, _, err = executor.StandardTx(
m.txExecutorBackend,
feeCalculator,
tx,
stateDiff,
)
if err != nil {
return fmt.Errorf("failed execution: %w", err)
}
return nil
}
func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error {
return m.backend.verifyUniqueInputs(blkID, inputs)
}