mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
304 lines
10 KiB
Go
304 lines
10 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/geth/rlp"
|
|
log "github.com/luxfi/log"
|
|
|
|
"github.com/luxfi/evm/core"
|
|
"github.com/luxfi/evm/params"
|
|
"github.com/luxfi/evm/params/extras"
|
|
"github.com/luxfi/evm/plugin/evm/header"
|
|
"github.com/luxfi/evm/precompile/precompileconfig"
|
|
"github.com/luxfi/evm/predicate"
|
|
"github.com/luxfi/geth/core/rawdb"
|
|
"github.com/luxfi/geth/core/types"
|
|
|
|
"github.com/luxfi/consensus/core/choices"
|
|
"github.com/luxfi/ids"
|
|
block "github.com/luxfi/vm/chain"
|
|
)
|
|
|
|
var (
|
|
// Block implements the chain.Block interface
|
|
_ block.Block = (*Block)(nil)
|
|
)
|
|
|
|
// Block implements the chain.Block interface
|
|
type Block struct {
|
|
id ids.ID
|
|
ethBlock *types.Block
|
|
vm *VM
|
|
}
|
|
|
|
// newBlock returns a new Block wrapping the ethBlock type and implementing the chain.Block interface
|
|
func (vm *VM) newBlock(ethBlock *types.Block) *Block {
|
|
return &Block{
|
|
id: ids.ID(ethBlock.Hash()),
|
|
ethBlock: ethBlock,
|
|
vm: vm,
|
|
}
|
|
}
|
|
|
|
// ID implements the chain.Block interface
|
|
func (b *Block) ID() ids.ID { return b.id }
|
|
|
|
// Accept implements the chain.Block interface
|
|
func (b *Block) 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.acceptedBlockDB.Put(lastAcceptedKey, blkID[:]); err != nil {
|
|
return fmt.Errorf("failed to put %s as the last accepted block: %w", blkID, err)
|
|
}
|
|
|
|
return b.vm.versiondb.Commit()
|
|
}
|
|
|
|
// handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements
|
|
// contract.Accepter
|
|
func (b *Block) 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: context.Background(),
|
|
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 chain.Block interface
|
|
func (b *Block) Reject(context.Context) error {
|
|
blkID := b.ID()
|
|
log.Debug("rejecting block",
|
|
"hash", blkID.Hex(),
|
|
"id", blkID,
|
|
"height", b.Height(),
|
|
)
|
|
return b.vm.blockChain.Reject(b.ethBlock)
|
|
}
|
|
|
|
// Parent implements the chain.Block interface
|
|
func (b *Block) Parent() ids.ID {
|
|
return ids.ID(b.ethBlock.ParentHash())
|
|
}
|
|
|
|
// ParentID implements the chain.Block interface (same as Parent)
|
|
func (b *Block) ParentID() ids.ID {
|
|
return ids.ID(b.ethBlock.ParentHash())
|
|
}
|
|
|
|
// Height implements the chain.Block interface
|
|
func (b *Block) Height() uint64 {
|
|
return b.ethBlock.NumberU64()
|
|
}
|
|
|
|
// Timestamp implements the chain.Block interface
|
|
func (b *Block) Timestamp() time.Time {
|
|
return time.Unix(int64(b.ethBlock.Time()), 0)
|
|
}
|
|
|
|
// syntacticVerify verifies that a *Block is well-formed.
|
|
func (b *Block) syntacticVerify() error {
|
|
if b == nil || b.ethBlock == nil {
|
|
return errInvalidBlock
|
|
}
|
|
|
|
header := b.ethBlock.Header()
|
|
rules := b.vm.chainConfig.Rules(header.Number, params.IsMergeTODO, header.Time)
|
|
return b.vm.syntacticBlockValidator.SyntacticVerify(b, rules)
|
|
}
|
|
|
|
// Verify implements the chain.Block interface
|
|
func (b *Block) Verify(context.Context) error {
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: context.Background(),
|
|
ProposerVMBlockCtx: nil,
|
|
}, true)
|
|
}
|
|
|
|
// ShouldVerifyWithContext implements the block.WithVerifyContext interface
|
|
func (b *Block) 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 *Block) VerifyWithContext(ctx context.Context, proposerVMBlockCtx *block.Context) error {
|
|
// Convert from node's block.Context to consensus's block.Context
|
|
var consensusBlockCtx *block.Context
|
|
if proposerVMBlockCtx != nil {
|
|
consensusBlockCtx = &block.Context{
|
|
PChainHeight: proposerVMBlockCtx.PChainHeight,
|
|
}
|
|
}
|
|
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: b.vm.ctx,
|
|
ProposerVMBlockCtx: consensusBlockCtx,
|
|
}, 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 *Block) 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)
|
|
}
|
|
|
|
// Only enforce predicates if the chain has already bootstrapped.
|
|
// If the chain is still bootstrapping, we can assume that all blocks we are verifying have
|
|
// been accepted by the network (so the predicate was validated by the network when the
|
|
// block was originally verified).
|
|
if b.vm.bootstrapped.Get() {
|
|
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.IsProcessing(b.id) {
|
|
return nil
|
|
}
|
|
|
|
return b.vm.blockChain.InsertBlockManual(b.ethBlock, writes)
|
|
}
|
|
|
|
// verifyPredicates verifies the predicates in the block are valid according to predicateContext.
|
|
func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateContext) error {
|
|
// Use RulesAt to properly set up the RulesExtra context for precompile checks
|
|
rules := params.RulesAt(b.vm.chainConfig, b.ethBlock.Number(), params.IsMergeTODO, b.ethBlock.Time())
|
|
rulesExtra := params.GetRulesExtra(rules)
|
|
|
|
switch {
|
|
case !rulesExtra.IsDurango && rulesExtra.PredicatersExist:
|
|
return errors.New("cannot enable predicates before Durango activation")
|
|
case !rulesExtra.IsDurango:
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
// NOTE: 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()
|
|
headerPredicateResultsBytes := header.PredicateBytesFromExtra(extraData)
|
|
if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) {
|
|
return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bytes implements the chain.Block interface
|
|
func (b *Block) Bytes() []byte {
|
|
res, err := rlp.EncodeToBytes(b.ethBlock)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (b *Block) String() string { return fmt.Sprintf("EVM block, ID = %s", b.ID()) }
|
|
|
|
// Status implements the chain.Block interface
|
|
func (b *Block) Status() uint8 {
|
|
// Return a simple status based on if the block is in the blockchain
|
|
// 0 = unknown, 1 = processing, 2 = accepted, 3 = rejected
|
|
// For simplicity, we'll return 2 (accepted) for now since this is mainly used in consensus
|
|
return 2
|
|
}
|
|
|
|
// SetStatus implements the chain.Block interface
|
|
// This is required for chain.Block but not used in our implementation
|
|
func (b *Block) SetStatus(status choices.Status) {
|
|
// No-op: EVM blocks manage their status internally through the blockchain
|
|
}
|