Files
coreth/plugin/evm/wrapped_block.go
zeekayandHanzo Dev f2d18226a0 Convert BlackholeAddr to a geth address where one is wanted
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]>
2026-09-06 00:28:51 -07:00

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
}