mirror of
https://github.com/luxfi/coreth.git
synced 2026-10-30 17:00:51 +00:00
229 lines
7.2 KiB
Go
229 lines
7.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/coreth/plugin/evm/atomic"
|
|
|
|
"github.com/luxfi/database"
|
|
safemath "github.com/luxfi/math"
|
|
luxatomic "github.com/luxfi/vm/chains/atomic"
|
|
|
|
"github.com/luxfi/coreth/params/extras"
|
|
"github.com/luxfi/coreth/plugin/evm/customtypes"
|
|
"github.com/luxfi/coreth/plugin/evm/extension"
|
|
"github.com/luxfi/coreth/utils"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
log "github.com/luxfi/log"
|
|
)
|
|
|
|
var (
|
|
_ extension.BlockExtension = (*blockExtension)(nil)
|
|
_ extension.BlockExtender = (*blockExtender)(nil)
|
|
_ atomic.AtomicBlockContext = (*blockExtension)(nil)
|
|
)
|
|
|
|
var (
|
|
errNilEthBlock = errors.New("nil ethBlock")
|
|
ErrMissingUTXOs = errors.New("missing UTXOs")
|
|
ErrEmptyBlock = errors.New("empty block")
|
|
)
|
|
|
|
type blockExtender struct {
|
|
extDataHashes map[common.Hash]common.Hash
|
|
vm *VM
|
|
}
|
|
|
|
type blockExtension struct {
|
|
atomicTxs []*atomic.Tx
|
|
blockExtender *blockExtender
|
|
block extension.ExtendedBlock
|
|
}
|
|
|
|
// newBlockExtender returns a new block extender.
|
|
func newBlockExtender(
|
|
extDataHashes map[common.Hash]common.Hash,
|
|
vm *VM,
|
|
) *blockExtender {
|
|
return &blockExtender{
|
|
extDataHashes: extDataHashes,
|
|
// Note: we need VM here to access the atomic backend that
|
|
// could be initialized later in the VM.
|
|
vm: vm,
|
|
}
|
|
}
|
|
|
|
// NewBlockExtension returns a new block extension. Under
|
|
// activate-all-implicitly the ext data is always a batch-encoded atomic-tx
|
|
func (be *blockExtender) NewBlockExtension(b extension.ExtendedBlock) (extension.BlockExtension, error) {
|
|
ethBlock := b.GetEthBlock()
|
|
if ethBlock == nil {
|
|
return nil, errNilEthBlock
|
|
}
|
|
atomicTxs, err := atomic.ExtractAtomicTxs(customtypes.BlockExtData(ethBlock), true, atomic.Codec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &blockExtension{
|
|
atomicTxs: atomicTxs,
|
|
blockExtender: be,
|
|
block: b,
|
|
}, nil
|
|
}
|
|
|
|
// SyntacticVerify checks the syntactic validity of the block. This is called by the wrapper
|
|
// block manager's SyntacticVerify method.
|
|
func (be *blockExtension) SyntacticVerify(_ extras.Rules) error {
|
|
b := be.block
|
|
ethBlock := b.GetEthBlock()
|
|
// should not happen
|
|
if ethBlock == nil {
|
|
return errNilEthBlock
|
|
}
|
|
ethHeader := ethBlock.Header()
|
|
headerExtra := customtypes.GetHeaderExtra(ethHeader)
|
|
|
|
// Under activate-all-implicitly every block carries an ExtDataHash that
|
|
// must match the canonical hash of its ext data.
|
|
extraData := customtypes.BlockExtData(ethBlock)
|
|
if hash := customtypes.CalcExtDataHash(extraData); headerExtra.ExtDataHash != hash {
|
|
return fmt.Errorf("extra data hash mismatch: have %x, want %x", headerExtra.ExtDataHash, hash)
|
|
}
|
|
|
|
// Block must not be empty
|
|
txs := ethBlock.Transactions()
|
|
atomicTxs := be.atomicTxs
|
|
if len(txs) == 0 && len(atomicTxs) == 0 {
|
|
return ErrEmptyBlock
|
|
}
|
|
|
|
// Under activate-all-implicitly the extDataGasUsed field is validated by
|
|
// [header.VerifyGasUsed]; here we cross-check it equals the sum of
|
|
// per-atomic-tx fixed-fee gas charges.
|
|
var totalGasUsed uint64
|
|
for _, atomicTx := range atomicTxs {
|
|
gasUsed, err := atomicTx.GasUsed(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
totalGasUsed, err = safemath.Add(totalGasUsed, gasUsed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !utils.BigEqualUint64(headerExtra.ExtDataGasUsed, totalGasUsed) {
|
|
return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", headerExtra.ExtDataGasUsed, totalGasUsed)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SemanticVerify checks the semantic validity of the block. This is called by the wrapper
|
|
// block manager's SemanticVerify method.
|
|
func (be *blockExtension) SemanticVerify() error {
|
|
vm := be.blockExtender.vm
|
|
if vm.bootstrapped.Get() {
|
|
// Verify that the UTXOs named in import txs are present in shared
|
|
// memory.
|
|
//
|
|
// This does not fully verify that this block can spend these UTXOs.
|
|
// However, it guarantees that any block that fails the later checks was
|
|
// built by an incorrect block proposer. This ensures that we only mark
|
|
// blocks as BAD BLOCKs if they were incorrectly generated.
|
|
if err := be.verifyUTXOsPresent(be.atomicTxs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Accept is called when the block is accepted. This is called by the wrapper
|
|
// block manager's Accept method. The acceptedBatch contains the changes that
|
|
// were made to the database as a result of accepting the block, and it's flushed
|
|
// to the database in this method.
|
|
func (be *blockExtension) Accept(acceptedBatch database.Batch) error {
|
|
vm := be.blockExtender.vm
|
|
for _, tx := range be.atomicTxs {
|
|
// Remove the accepted transaction from the mempool
|
|
vm.AtomicMempool.RemoveTx(tx)
|
|
}
|
|
|
|
// Update VM state for atomic txs in this block. This includes updating the
|
|
// atomic tx repo, atomic trie, and shared memory.
|
|
atomicState, err := vm.AtomicBackend.GetVerifiedAtomicState(common.Hash(be.block.ID()))
|
|
if err != nil {
|
|
// should never occur since [b] must be verified before calling Accept
|
|
return err
|
|
}
|
|
// Apply any shared memory changes atomically with other pending batched changes
|
|
return atomicState.Accept(acceptedBatch)
|
|
}
|
|
|
|
// Reject is called when the block is rejected. This is called by the wrapper
|
|
// block manager's Reject method.
|
|
func (be *blockExtension) Reject() error {
|
|
vm := be.blockExtender.vm
|
|
for _, tx := range be.atomicTxs {
|
|
// Re-issue the transaction in the mempool, continue even if it fails
|
|
vm.AtomicMempool.RemoveTx(tx)
|
|
if err := vm.AtomicMempool.AddRemoteTx(tx); err != nil {
|
|
log.Debug("Failed to re-issue transaction in rejected block", "txID", tx.ID(), "err", err)
|
|
}
|
|
}
|
|
atomicState, err := vm.AtomicBackend.GetVerifiedAtomicState(common.Hash(be.block.ID()))
|
|
if err != nil {
|
|
// should never occur since [b] must be verified before calling Reject
|
|
return err
|
|
}
|
|
return atomicState.Reject()
|
|
}
|
|
|
|
// CleanupVerified is called when the block is cleaned up after a failed insertion.
|
|
func (be *blockExtension) CleanupVerified() {
|
|
vm := be.blockExtender.vm
|
|
if atomicState, err := vm.AtomicBackend.GetVerifiedAtomicState(be.block.GetEthBlock().Hash()); err == nil {
|
|
atomicState.Reject()
|
|
}
|
|
}
|
|
|
|
// AtomicTxs returns the atomic transactions in this block.
|
|
func (be *blockExtension) AtomicTxs() []*atomic.Tx {
|
|
return be.atomicTxs
|
|
}
|
|
|
|
// verifyUTXOsPresent verifies all atomic UTXOs consumed by the block are
|
|
// present in shared memory.
|
|
func (be *blockExtension) verifyUTXOsPresent(atomicTxs []*atomic.Tx) error {
|
|
b := be.block
|
|
blockHash := common.Hash(b.ID())
|
|
vm := be.blockExtender.vm
|
|
if vm.AtomicBackend.IsBonus(b.Height(), blockHash) {
|
|
log.Info("skipping atomic tx verification on bonus block", "block", blockHash)
|
|
return nil
|
|
}
|
|
|
|
// verify UTXOs named in import txs are present in shared memory.
|
|
sharedMemory, ok := vm.Runtime.SharedMemory.(luxatomic.SharedMemory)
|
|
if !ok {
|
|
// SharedMemory unavailable (ZAP transport); skip atomic UTXO verification
|
|
log.Warn("skipping atomic UTXO verification: SharedMemory unavailable (ZAP transport)")
|
|
return nil
|
|
}
|
|
for _, atomicTx := range atomicTxs {
|
|
utx := atomicTx.UnsignedAtomicTx
|
|
chainID, requests, err := utx.AtomicOps()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := sharedMemory.Get(chainID, requests.RemoveRequests); err != nil {
|
|
return fmt.Errorf("%w: %s", ErrMissingUTXOs, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|