mirror of
https://github.com/luxfi/coreth.git
synced 2026-10-30 17:00:51 +00:00
constants.BlackholeAddr is a plain [20]byte now, so constants no longer imports geth. Go assigns an unnamed [20]byte to common.Address implicitly, so every call site still compiled, but one of them changed behavior: fmt %v prints [1 0 0 ...] for an array and 0x0100000000000000000000000000000000000000 for a common.Address, so the invalid-coinbase error in syntacticVerify lost its hex form. Writing common.Address(constants.BlackholeAddr) restores the message and names the type at the boundary; the conversion is free. Six sites take the conversion: the etherbase in initializeChain, the simulated backend etherbase, the coinbase error text, the module address in the registerer test, and the two SetCoinbase calls. The last two sit in files carrying //go:build ignore, so the compiler does not check them. Two sites compare bytes and stay as they are, the registerer against a module address and syntacticVerify against the header coinbase. go.mod is untouched. The requirement still reads constants v1.6.2 and the bump waits on the tag. Co-authored-by: Hanzo Dev <[email protected]>
455 lines
16 KiB
Go
455 lines
16 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package evm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/coreth/core"
|
|
"github.com/luxfi/coreth/params"
|
|
"github.com/luxfi/coreth/params/extras"
|
|
"github.com/luxfi/coreth/plugin/evm/customtypes"
|
|
"github.com/luxfi/coreth/plugin/evm/extension"
|
|
"github.com/luxfi/coreth/plugin/evm/header"
|
|
"github.com/luxfi/coreth/precompile/precompileconfig"
|
|
"github.com/luxfi/coreth/predicate"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/rawdb"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/rlp"
|
|
"github.com/luxfi/geth/trie"
|
|
log "github.com/luxfi/log"
|
|
|
|
"github.com/luxfi/consensus/engine/chain/block"
|
|
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
var (
|
|
_ block.Block = (*wrappedBlock)(nil)
|
|
_ block.WithVerifyContext = (*wrappedBlock)(nil)
|
|
_ extension.ExtendedBlock = (*wrappedBlock)(nil)
|
|
)
|
|
|
|
// wrappedBlock implements the quasarman.wrappedBlock interface
|
|
type wrappedBlock struct {
|
|
id ids.ID
|
|
ethBlock *types.Block
|
|
extension extension.BlockExtension
|
|
vm *VM
|
|
}
|
|
|
|
// wrapBlock returns a new Block wrapping the ethBlock type and implementing the block.Block interface
|
|
func wrapBlock(ethBlock *types.Block, vm *VM) (*wrappedBlock, error) {
|
|
b := &wrappedBlock{
|
|
id: ids.ID(ethBlock.Hash()),
|
|
ethBlock: ethBlock,
|
|
vm: vm,
|
|
}
|
|
if vm.extensionConfig.BlockExtender != nil {
|
|
extension, err := vm.extensionConfig.BlockExtender.NewBlockExtension(b)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create block extension: %w", err)
|
|
}
|
|
b.extension = extension
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// ID implements the block.Block interface
|
|
func (b *wrappedBlock) ID() ids.ID { return b.id }
|
|
|
|
// Accept implements the block.Block interface
|
|
func (b *wrappedBlock) Accept(context.Context) error {
|
|
vm := b.vm
|
|
// Although returning an error from Accept is considered fatal, it is good
|
|
// practice to cleanup the batch we were modifying in the case of an error.
|
|
defer vm.versiondb.Abort()
|
|
|
|
blkID := b.ID()
|
|
log.Debug("accepting block",
|
|
"hash", blkID.Hex(),
|
|
"id", blkID,
|
|
"height", b.Height(),
|
|
)
|
|
// Call Accept for relevant precompile logs. Note we do this prior to
|
|
// calling Accept on the blockChain so any side effects (eg warp signatures)
|
|
// take place before the accepted log is emitted to subscribers.
|
|
rules := b.vm.rules(b.ethBlock.Number(), b.ethBlock.Time())
|
|
if err := b.handlePrecompileAccept(rules); err != nil {
|
|
return err
|
|
}
|
|
if err := vm.blockChain.Accept(b.ethBlock); err != nil {
|
|
return fmt.Errorf("chain could not accept %s: %w", blkID, err)
|
|
}
|
|
|
|
if err := vm.PutLastAcceptedID(blkID); err != nil {
|
|
return fmt.Errorf("failed to put %s as the last accepted block: %w", blkID, err)
|
|
}
|
|
|
|
// Get pending operations on the vm's versionDB so we can apply them atomically
|
|
// with the block extension's changes.
|
|
vdbBatch, err := vm.versiondb.CommitBatch()
|
|
if err != nil {
|
|
return fmt.Errorf("could not create commit batch processing block[%s]: %w", blkID, err)
|
|
}
|
|
|
|
if b.extension != nil {
|
|
// Apply any changes atomically with other pending changes to
|
|
// the vm's versionDB.
|
|
// Accept flushes the changes in the batch to the database.
|
|
return b.extension.Accept(vdbBatch)
|
|
}
|
|
|
|
// If there is no extension, we still need to apply the changes to the versionDB
|
|
return vdbBatch.Write()
|
|
}
|
|
|
|
// handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements
|
|
// contract.Accepter
|
|
func (b *wrappedBlock) handlePrecompileAccept(rules extras.Rules) error {
|
|
// Short circuit early if there are no precompile accepters to execute
|
|
if len(rules.AccepterPrecompiles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Read receipts from disk
|
|
receipts := rawdb.ReadReceipts(b.vm.chaindb, b.ethBlock.Hash(), b.ethBlock.NumberU64(), b.ethBlock.Time(), b.vm.chainConfig)
|
|
// If there are no receipts, ReadReceipts may be nil, so we check the length and confirm the ReceiptHash
|
|
// is empty to ensure that missing receipts results in an error on accept.
|
|
if len(receipts) == 0 && b.ethBlock.ReceiptHash() != types.EmptyRootHash {
|
|
return fmt.Errorf("failed to fetch receipts for accepted block with non-empty root hash (%s) (Block: %s, Height: %d)", b.ethBlock.ReceiptHash(), b.ethBlock.Hash(), b.ethBlock.NumberU64())
|
|
}
|
|
acceptCtx := &precompileconfig.AcceptContext{
|
|
ConsensusCtx: b.vm.runtime,
|
|
Warp: b.vm.warpBackend,
|
|
}
|
|
for _, receipt := range receipts {
|
|
for logIdx, log := range receipt.Logs {
|
|
accepter, ok := rules.AccepterPrecompiles[log.Address]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if err := accepter.Accept(acceptCtx, log.BlockHash, log.BlockNumber, log.TxHash, logIdx, log.Topics, log.Data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reject implements the block.Block interface
|
|
// If [b] contains an atomic transaction, attempt to re-issue it
|
|
func (b *wrappedBlock) Reject(context.Context) error {
|
|
blkID := b.ID()
|
|
log.Debug("rejecting block",
|
|
"hash", blkID.Hex(),
|
|
"id", blkID,
|
|
"height", b.Height(),
|
|
)
|
|
|
|
if b.extension != nil {
|
|
if err := b.extension.Reject(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return b.vm.blockChain.Reject(b.ethBlock)
|
|
}
|
|
|
|
// Parent implements the block.Block interface (alias for ParentID)
|
|
func (b *wrappedBlock) Parent() ids.ID {
|
|
return ids.ID(b.ethBlock.ParentHash())
|
|
}
|
|
|
|
// ParentID implements the block.Block interface
|
|
func (b *wrappedBlock) ParentID() ids.ID {
|
|
return ids.ID(b.ethBlock.ParentHash())
|
|
}
|
|
|
|
// Height implements the block.Block interface
|
|
func (b *wrappedBlock) Height() uint64 {
|
|
return b.ethBlock.NumberU64()
|
|
}
|
|
|
|
// Status implements the block.Block interface
|
|
// Note: The actual status management is handled by the chain.State wrapper
|
|
func (b *wrappedBlock) Status() uint8 {
|
|
return 2 // StatusProcessing
|
|
}
|
|
|
|
// Timestamp implements the block.Block interface
|
|
func (b *wrappedBlock) Timestamp() time.Time {
|
|
return time.Unix(int64(b.ethBlock.Time()), 0)
|
|
}
|
|
|
|
// Verify implements the block.Block interface
|
|
func (b *wrappedBlock) Verify(context.Context) error {
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: b.vm.runtime,
|
|
ProposerVMBlockCtx: nil,
|
|
}, true)
|
|
}
|
|
|
|
// ShouldVerifyWithContext implements the block.WithVerifyContext interface
|
|
func (b *wrappedBlock) ShouldVerifyWithContext(context.Context) (bool, error) {
|
|
rules := b.vm.rules(b.ethBlock.Number(), b.ethBlock.Time())
|
|
predicates := rules.Predicaters
|
|
// Short circuit early if there are no predicates to verify
|
|
if len(predicates) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
// Check if any of the transactions in the block specify a precompile that enforces a predicate, which requires
|
|
// the ProposerVMBlockCtx.
|
|
for _, tx := range b.ethBlock.Transactions() {
|
|
for _, accessTuple := range tx.AccessList() {
|
|
if _, ok := predicates[accessTuple.Address]; ok {
|
|
log.Debug("Block verification requires proposerVM context", "block", b.ID(), "height", b.Height())
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Debug("Block verification does not require proposerVM context", "block", b.ID(), "height", b.Height())
|
|
return false, nil
|
|
}
|
|
|
|
// VerifyWithContext implements the block.WithVerifyContext interface
|
|
func (b *wrappedBlock) VerifyWithContext(ctx context.Context, proposerVMBlockCtx *block.Context) error {
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: b.vm.runtime,
|
|
ProposerVMBlockCtx: proposerVMBlockCtx,
|
|
}, true)
|
|
}
|
|
|
|
// Verify the block is valid.
|
|
// Enforces that the predicates are valid within [predicateContext].
|
|
// Writes the block details to disk and the state to the trie manager iff writes=true.
|
|
func (b *wrappedBlock) verify(predicateContext *precompileconfig.PredicateContext, writes bool) error {
|
|
if predicateContext.ProposerVMBlockCtx != nil {
|
|
log.Debug("Verifying block with context", "block", b.ID(), "height", b.Height())
|
|
} else {
|
|
log.Debug("Verifying block without context", "block", b.ID(), "height", b.Height())
|
|
}
|
|
if err := b.syntacticVerify(); err != nil {
|
|
return fmt.Errorf("syntactic block verification failed: %w", err)
|
|
}
|
|
|
|
if err := b.semanticVerify(); err != nil {
|
|
return fmt.Errorf("failed to verify block: %w", err)
|
|
}
|
|
|
|
// If the VM is not marked as bootstrapped the other chains may also be
|
|
// bootstrapping and not have populated the required indices. Since
|
|
// bootstrapping only verifies blocks that have been canonically accepted by
|
|
// the network, these checks would be guaranteed to pass on a synced node.
|
|
if b.vm.bootstrapped.Get() {
|
|
// Verify that all the ICM messages are correctly marked as either valid
|
|
// or invalid.
|
|
if err := b.verifyPredicates(predicateContext); err != nil {
|
|
return fmt.Errorf("failed to verify predicates: %w", err)
|
|
}
|
|
}
|
|
|
|
// The engine may call VerifyWithContext multiple times on the same block with different contexts.
|
|
// Since the engine will only call Accept/Reject once, we should only call InsertBlockManual once.
|
|
// Additionally, if a block is already in processing, then it has already passed verification and
|
|
// at this point we have checked the predicates are still valid in the different context so we
|
|
// can return nil.
|
|
if b.vm.State.IsProcessing(b.id) {
|
|
return nil
|
|
}
|
|
|
|
err := b.vm.blockChain.InsertBlockManual(b.ethBlock, writes)
|
|
// If this was not called with intention to writing to the database or
|
|
// got an error while inserting to blockchain, we may need to cleanup the extension.
|
|
// so that the extension can be garbage collected.
|
|
if doCleanup := err != nil || !writes; b.extension != nil && doCleanup {
|
|
b.extension.CleanupVerified()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// semanticVerify verifies that a *Block is internally consistent.
|
|
func (b *wrappedBlock) semanticVerify() error {
|
|
// Make sure the block isn't too far in the future
|
|
blockTimestamp := b.ethBlock.Time()
|
|
if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime {
|
|
return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime)
|
|
}
|
|
|
|
if b.extension != nil {
|
|
if err := b.extension.SemanticVerify(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// syntacticVerify verifies that a *Block is well-formed.
|
|
func (b *wrappedBlock) syntacticVerify() error {
|
|
if b == nil || b.ethBlock == nil {
|
|
return errInvalidBlock
|
|
}
|
|
|
|
// Skip verification of the genesis block since it should already be marked as accepted.
|
|
if b.ethBlock.Hash() == b.vm.genesisHash {
|
|
return nil
|
|
}
|
|
|
|
ethHeader := b.ethBlock.Header()
|
|
rules := b.vm.chainConfig.Rules(ethHeader.Number, params.IsMergeTODO, ethHeader.Time)
|
|
rulesExtra := params.GetRulesExtra(rules)
|
|
// Perform block and header sanity checks
|
|
if !ethHeader.Number.IsUint64() {
|
|
return fmt.Errorf("invalid block number: %v", ethHeader.Number)
|
|
}
|
|
if !ethHeader.Difficulty.IsUint64() || ethHeader.Difficulty.Cmp(common.Big1) != 0 {
|
|
return fmt.Errorf("invalid difficulty: %d", ethHeader.Difficulty)
|
|
}
|
|
if ethHeader.Nonce.Uint64() != 0 {
|
|
return fmt.Errorf(
|
|
"expected nonce to be 0 but got %d: %w",
|
|
ethHeader.Nonce.Uint64(), errInvalidNonce,
|
|
)
|
|
}
|
|
|
|
if ethHeader.MixDigest != (common.Hash{}) {
|
|
return fmt.Errorf("invalid mix digest: %v", ethHeader.MixDigest)
|
|
}
|
|
|
|
// Verify the extra data is well-formed.
|
|
if err := header.VerifyExtra(rulesExtra.LuxRules, ethHeader.Extra); err != nil {
|
|
return err
|
|
}
|
|
|
|
if version := customtypes.GetBlockVersion(b.ethBlock); version != 0 {
|
|
return fmt.Errorf("invalid version: %d", version)
|
|
}
|
|
|
|
// Check that the tx hash in the header matches the body
|
|
txsHash := types.DeriveSha(b.ethBlock.Transactions(), trie.NewStackTrie(nil))
|
|
if txsHash != ethHeader.TxHash {
|
|
return fmt.Errorf("invalid txs hash %v does not match calculated txs hash %v", ethHeader.TxHash, txsHash)
|
|
}
|
|
// Check that the uncle hash in the header matches the body
|
|
uncleHash := types.CalcUncleHash(b.ethBlock.Uncles())
|
|
if uncleHash != ethHeader.UncleHash {
|
|
return fmt.Errorf("invalid uncle hash %v does not match calculated uncle hash %v", ethHeader.UncleHash, uncleHash)
|
|
}
|
|
// Coinbase must match the BlackholeAddr on C-Chain
|
|
if ethHeader.Coinbase != constants.BlackholeAddr {
|
|
return fmt.Errorf("invalid coinbase %v does not match required blackhole address %v", ethHeader.Coinbase, common.Address(constants.BlackholeAddr))
|
|
}
|
|
// Block must not have any uncles
|
|
if len(b.ethBlock.Uncles()) > 0 {
|
|
return errUnclesUnsupported
|
|
}
|
|
|
|
// Under activate-all-implicitly dynamic fees are live from genesis. The
|
|
// header must carry a base fee.
|
|
if ethHeader.BaseFee == nil {
|
|
return errNilBaseFee
|
|
}
|
|
if bfLen := ethHeader.BaseFee.BitLen(); bfLen > 256 {
|
|
return fmt.Errorf("too large base fee: bitlen %d", bfLen)
|
|
}
|
|
|
|
headerExtra := customtypes.GetHeaderExtra(ethHeader)
|
|
if headerExtra.BlockGasCost != nil && !headerExtra.BlockGasCost.IsUint64() {
|
|
return fmt.Errorf("too large blockGasCost: %d", headerExtra.BlockGasCost)
|
|
}
|
|
|
|
// Verify the existence / non-existence of excessBlobGas
|
|
cancun := rules.IsCancun
|
|
if !cancun && ethHeader.ExcessBlobGas != nil {
|
|
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *ethHeader.ExcessBlobGas)
|
|
}
|
|
if !cancun && ethHeader.BlobGasUsed != nil {
|
|
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *ethHeader.BlobGasUsed)
|
|
}
|
|
if cancun && ethHeader.ExcessBlobGas == nil {
|
|
return errors.New("header is missing excessBlobGas")
|
|
}
|
|
if cancun && ethHeader.BlobGasUsed == nil {
|
|
return errors.New("header is missing blobGasUsed")
|
|
}
|
|
if !cancun && ethHeader.ParentBeaconRoot != nil {
|
|
return fmt.Errorf("invalid parentBeaconRoot: have %x, expected nil", *ethHeader.ParentBeaconRoot)
|
|
}
|
|
if cancun {
|
|
switch {
|
|
case ethHeader.ParentBeaconRoot == nil:
|
|
return errors.New("header is missing parentBeaconRoot")
|
|
case *ethHeader.ParentBeaconRoot != (common.Hash{}):
|
|
return fmt.Errorf("invalid parentBeaconRoot: have %x, expected empty hash", ethHeader.ParentBeaconRoot)
|
|
}
|
|
if ethHeader.BlobGasUsed == nil {
|
|
return fmt.Errorf("blob gas used must not be nil in Cancun")
|
|
} else if *ethHeader.BlobGasUsed > 0 {
|
|
return fmt.Errorf("blobs not enabled on lux networks: used %d blob gas, expected 0", *ethHeader.BlobGasUsed)
|
|
}
|
|
}
|
|
|
|
if b.extension != nil {
|
|
if err := b.extension.SyntacticVerify(*rulesExtra); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifyPredicates verifies the predicates in the block are valid according to predicateContext.
|
|
// Under activate-all-implicitly predicates are live from genesis.
|
|
func (b *wrappedBlock) verifyPredicates(predicateContext *precompileconfig.PredicateContext) error {
|
|
rules := b.vm.chainConfig.Rules(b.ethBlock.Number(), params.IsMergeTODO, b.ethBlock.Time())
|
|
|
|
predicateResults := predicate.NewResults()
|
|
for _, tx := range b.ethBlock.Transactions() {
|
|
results, err := core.CheckPredicates(rules, predicateContext, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
predicateResults.SetTxResults(tx.Hash(), results)
|
|
}
|
|
// TODO: document required gas constraints to ensure marshalling predicate results does not error
|
|
predicateResultsBytes, err := predicateResults.Bytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal predicate results: %w", err)
|
|
}
|
|
extraData := b.ethBlock.Extra()
|
|
luxRules := params.GetRulesExtra(rules).LuxRules
|
|
headerPredicateResultsBytes := header.PredicateBytesFromExtra(luxRules, extraData)
|
|
if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) {
|
|
return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bytes implements the block.Block interface
|
|
func (b *wrappedBlock) Bytes() []byte {
|
|
res, err := rlp.EncodeToBytes(b.ethBlock)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (b *wrappedBlock) String() string { return fmt.Sprintf("EVM block, ID = %s", b.ID()) }
|
|
|
|
func (b *wrappedBlock) GetEthBlock() *types.Block {
|
|
return b.ethBlock
|
|
}
|
|
|
|
func (b *wrappedBlock) GetBlockExtension() extension.BlockExtension {
|
|
return b.extension
|
|
}
|