mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
337 lines
11 KiB
Go
337 lines
11 KiB
Go
// (c) 2020-2020, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package evm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/evm/commontype"
|
|
"github.com/luxfi/evm/core"
|
|
"github.com/luxfi/evm/core/rawdb"
|
|
"github.com/luxfi/evm/core/types"
|
|
evmids "github.com/luxfi/evm/ids"
|
|
"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/log"
|
|
"github.com/luxfi/geth/rlp"
|
|
"github.com/luxfi/node/v2/quasar/engine/chain/block"
|
|
"github.com/luxfi/node/v2/quasar/choices"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
var (
|
|
_ block.Block = (*Block)(nil)
|
|
_ block.WithVerifyContext = (*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 block.Block interface (choices.Decidable)
|
|
func (b *Block) ID() string { return b.id.String() }
|
|
|
|
// Accept implements the chain.Block interface
|
|
func (b *Block) Accept() 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.
|
|
if vm.versiondb != nil {
|
|
defer vm.versiondb.Abort()
|
|
}
|
|
|
|
blkID := ids.ID(b.id)
|
|
log.Debug("accepting block",
|
|
"hash", blkID.String(),
|
|
"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. Use of the
|
|
// sharedMemoryWriter ensures shared memory requests generated by
|
|
// precompiles are committed atomically with the vm's lastAcceptedKey.
|
|
rules := b.vm.rules(b.ethBlock.Number(), b.ethBlock.Timestamp())
|
|
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)
|
|
}
|
|
|
|
if b.vm.versiondb != nil {
|
|
return b.vm.versiondb.Commit()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
// Convert consensus.Context to commontype.ChainContext
|
|
chainCtx := &commontype.ChainContext{
|
|
NetworkID: b.vm.ctx.NetworkID,
|
|
SubnetID: evmids.SubnetID(b.vm.ctx.SubnetID),
|
|
ChainID: evmids.ChainID(b.vm.ctx.ChainID),
|
|
NodeID: func() evmids.NodeID {
|
|
var nodeID evmids.NodeID
|
|
copy(nodeID[:], b.vm.ctx.NodeID[:])
|
|
return nodeID
|
|
}(),
|
|
AppVersion: 0, // TODO: Get app version
|
|
ChainDataDir: "", // TODO: Get chain data dir
|
|
ValidatorState: nil, // TODO: Implement ValidatorState interface
|
|
}
|
|
acceptCtx := &precompileconfig.AcceptContext{
|
|
ConsensusCtx: chainCtx,
|
|
Warp: nil, // TODO: implement WarpMessageWriter wrapper
|
|
}
|
|
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() error {
|
|
blkID := ids.ID(b.id)
|
|
log.Debug("rejecting block",
|
|
"hash", blkID.String(),
|
|
"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())
|
|
}
|
|
|
|
// Height implements the chain.Block interface
|
|
func (b *Block) Height() uint64 {
|
|
return b.ethBlock.NumberU64()
|
|
}
|
|
|
|
// Timestamp implements the chain.Block interface (deprecated - use Time())
|
|
func (b *Block) Timestamp() time.Time {
|
|
return time.Unix(int64(b.ethBlock.Time()), 0)
|
|
}
|
|
|
|
// Time implements the chain.Block interface
|
|
func (b *Block) Time() uint64 {
|
|
return b.ethBlock.Time()
|
|
}
|
|
|
|
// 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, header.Time)
|
|
return b.vm.syntacticBlockValidator.SyntacticVerify(b, rules)
|
|
}
|
|
|
|
// Verify implements the chain.Block interface
|
|
func (b *Block) Verify(context.Context) error {
|
|
// Convert consensus.Context to commontype.ChainContext
|
|
chainCtx := &commontype.ChainContext{
|
|
NetworkID: b.vm.ctx.NetworkID,
|
|
SubnetID: evmids.SubnetID(b.vm.ctx.SubnetID),
|
|
ChainID: evmids.ChainID(b.vm.ctx.ChainID),
|
|
NodeID: func() evmids.NodeID {
|
|
var nodeID evmids.NodeID
|
|
copy(nodeID[:], b.vm.ctx.NodeID[:])
|
|
return nodeID
|
|
}(),
|
|
AppVersion: 0, // TODO: Get app version
|
|
ChainDataDir: "", // TODO: Get chain data dir
|
|
ValidatorState: nil, // TODO: Implement ValidatorState interface
|
|
}
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: chainCtx,
|
|
ProposerVMBlockCtx: nil,
|
|
}, true)
|
|
}
|
|
|
|
// ShouldVerifyWithContext implements the iface.WithVerifyContext interface
|
|
func (b *Block) ShouldVerifyWithContext(context.Context) (bool, error) {
|
|
rules := b.vm.rules(b.ethBlock.Number(), b.ethBlock.Timestamp())
|
|
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 consensus.Context to commontype.ChainContext
|
|
chainCtx := &commontype.ChainContext{
|
|
NetworkID: b.vm.ctx.NetworkID,
|
|
SubnetID: evmids.SubnetID(b.vm.ctx.SubnetID),
|
|
ChainID: evmids.ChainID(b.vm.ctx.ChainID),
|
|
NodeID: func() evmids.NodeID {
|
|
var nodeID evmids.NodeID
|
|
copy(nodeID[:], b.vm.ctx.NodeID[:])
|
|
return nodeID
|
|
}(),
|
|
AppVersion: 0, // TODO: Get app version
|
|
ChainDataDir: "", // TODO: Get chain data dir
|
|
ValidatorState: nil, // TODO: Implement ValidatorState interface
|
|
}
|
|
// Convert block.Context to commontype.BlockContext
|
|
var blockCtx *commontype.BlockContext
|
|
if proposerVMBlockCtx != nil {
|
|
blockCtx = &commontype.BlockContext{
|
|
PChainHeight: proposerVMBlockCtx.PChainHeight,
|
|
}
|
|
}
|
|
return b.verify(&precompileconfig.PredicateContext{
|
|
ConsensusCtx: chainCtx,
|
|
ProposerVMBlockCtx: blockCtx,
|
|
}, 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.State.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 {
|
|
// For v2.0.0, all upgrades are active including Durango
|
|
// Skip the upgrade check since we always support predicates
|
|
|
|
predicateResults := predicate.NewResults()
|
|
for _, tx := range b.ethBlock.Transactions() {
|
|
// Convert to params.Rules for CheckPredicates
|
|
rules := b.vm.chainConfig.Rules(b.ethBlock.Number(), b.ethBlock.Timestamp())
|
|
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()
|
|
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
|
|
}
|
|
|
|
// Status implements the choices.Decidable interface
|
|
func (b *Block) Status() choices.Status {
|
|
// TODO: Implement proper status tracking
|
|
// For now, return Processing as blocks are being processed
|
|
return choices.Processing
|
|
}
|
|
|
|
func (b *Block) String() string { return fmt.Sprintf("EVM block, ID = %s", b.ID()) }
|