forked from cerc-io/plugeth
Merge tag 'v1.10.16' into re-merge/v1.10.16
This commit is contained in:
+1
-1
@@ -234,7 +234,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, merger)
|
||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
|
||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||
|
||||
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
||||
|
||||
+78
-383
@@ -20,37 +20,15 @@ package catalyst
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/beacon"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
chainParams "github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
VALID = GenericStringResponse{"VALID"}
|
||||
SUCCESS = GenericStringResponse{"SUCCESS"}
|
||||
INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
|
||||
SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil}
|
||||
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
|
||||
UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"}
|
||||
InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"}
|
||||
InvalidPayloadID = rpc.CustomError{Code: 1, ValidationError: "invalid payload id"}
|
||||
)
|
||||
|
||||
// Register adds catalyst APIs to the full node.
|
||||
@@ -60,21 +38,7 @@ func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||
{
|
||||
Namespace: "engine",
|
||||
Version: "1.0",
|
||||
Service: NewConsensusAPI(backend, nil),
|
||||
Public: true,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterLight adds catalyst APIs to the light client.
|
||||
func RegisterLight(stack *node.Node, backend *les.LightEthereum) error {
|
||||
log.Warn("Catalyst mode enabled", "protocol", "les")
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "engine",
|
||||
Version: "1.0",
|
||||
Service: NewConsensusAPI(nil, backend),
|
||||
Service: NewConsensusAPI(backend),
|
||||
Public: true,
|
||||
},
|
||||
})
|
||||
@@ -82,184 +46,86 @@ func RegisterLight(stack *node.Node, backend *les.LightEthereum) error {
|
||||
}
|
||||
|
||||
type ConsensusAPI struct {
|
||||
light bool
|
||||
eth *eth.Ethereum
|
||||
les *les.LightEthereum
|
||||
engine consensus.Engine // engine is the post-merge consensus engine, only for block creation
|
||||
preparedBlocks map[uint64]*ExecutableDataV1
|
||||
preparedBlocks *payloadQueue // preparedBlocks caches payloads (*ExecutableDataV1) by payload ID (PayloadID)
|
||||
}
|
||||
|
||||
func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
|
||||
var engine consensus.Engine
|
||||
if eth == nil {
|
||||
if les.BlockChain().Config().TerminalTotalDifficulty == nil {
|
||||
panic("Catalyst started without valid total difficulty")
|
||||
}
|
||||
if b, ok := les.Engine().(*beacon.Beacon); ok {
|
||||
engine = beacon.New(b.InnerEngine())
|
||||
} else {
|
||||
engine = beacon.New(les.Engine())
|
||||
}
|
||||
} else {
|
||||
if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
|
||||
panic("Catalyst started without valid total difficulty")
|
||||
}
|
||||
if b, ok := eth.Engine().(*beacon.Beacon); ok {
|
||||
engine = beacon.New(b.InnerEngine())
|
||||
} else {
|
||||
engine = beacon.New(eth.Engine())
|
||||
}
|
||||
// NewConsensusAPI creates a new consensus api for the given backend.
|
||||
// The underlying blockchain needs to have a valid terminal total difficulty set.
|
||||
func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
|
||||
if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
|
||||
panic("Catalyst started without valid total difficulty")
|
||||
}
|
||||
return &ConsensusAPI{
|
||||
light: eth == nil,
|
||||
eth: eth,
|
||||
les: les,
|
||||
engine: engine,
|
||||
preparedBlocks: make(map[uint64]*ExecutableDataV1),
|
||||
preparedBlocks: newPayloadQueue(),
|
||||
}
|
||||
}
|
||||
|
||||
// blockExecutionEnv gathers all the data required to execute
|
||||
// a block, either when assembling it or when inserting it.
|
||||
type blockExecutionEnv struct {
|
||||
chain *core.BlockChain
|
||||
state *state.StateDB
|
||||
tcount int
|
||||
gasPool *core.GasPool
|
||||
|
||||
header *types.Header
|
||||
txs []*types.Transaction
|
||||
receipts []*types.Receipt
|
||||
}
|
||||
|
||||
func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
|
||||
vmconfig := *env.chain.GetVMConfig()
|
||||
snap := env.state.Snapshot()
|
||||
receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
|
||||
if err != nil {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
return err
|
||||
}
|
||||
env.txs = append(env.txs, tx)
|
||||
env.receipts = append(env.receipts, receipt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
|
||||
// The parent state might be missing. It can be the special scenario
|
||||
// that consensus layer tries to build a new block based on the very
|
||||
// old side chain block and the relevant state is already pruned. So
|
||||
// try to retrieve the live state from the chain, if it's not existent,
|
||||
// do the necessary recovery work.
|
||||
var (
|
||||
err error
|
||||
state *state.StateDB
|
||||
)
|
||||
if api.eth.BlockChain().HasState(parent.Root()) {
|
||||
state, err = api.eth.BlockChain().StateAt(parent.Root())
|
||||
} else {
|
||||
// The maximum acceptable reorg depth can be limited by the
|
||||
// finalised block somehow. TODO(rjl493456442) fix the hard-
|
||||
// coded number here later.
|
||||
state, err = api.eth.StateAtBlock(parent, 1000, nil, false, false)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env := &blockExecutionEnv{
|
||||
chain: api.eth.BlockChain(),
|
||||
state: state,
|
||||
header: header,
|
||||
gasPool: new(core.GasPool).AddGas(header.GasLimit),
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) GetPayloadV1(payloadID hexutil.Bytes) (*ExecutableDataV1, error) {
|
||||
hash := []byte(payloadID)
|
||||
if len(hash) < 8 {
|
||||
return nil, &InvalidPayloadID
|
||||
}
|
||||
id := binary.BigEndian.Uint64(hash[:8])
|
||||
data, ok := api.preparedBlocks[id]
|
||||
if !ok {
|
||||
return nil, &UnknownPayload
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, PayloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
|
||||
// ForkchoiceUpdatedV1 has several responsibilities:
|
||||
// If the method is called with an empty head block:
|
||||
// we return success, which can be used to check if the catalyst mode is enabled
|
||||
// If the total difficulty was not reached:
|
||||
// we return INVALID
|
||||
// If the finalizedBlockHash is set:
|
||||
// we check if we have the finalizedBlockHash in our db, if not we start a sync
|
||||
// We try to set our blockchain to the headBlock
|
||||
// If there are payloadAttributes:
|
||||
// we try to assemble a block with the payloadAttributes and return its payloadID
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
|
||||
log.Trace("Engine API request received", "method", "ForkChoiceUpdated", "head", heads.HeadBlockHash, "finalized", heads.FinalizedBlockHash, "safe", heads.SafeBlockHash)
|
||||
if heads.HeadBlockHash == (common.Hash{}) {
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
|
||||
return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil
|
||||
}
|
||||
if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
|
||||
if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil {
|
||||
// TODO (MariusVanDerWijden) trigger sync
|
||||
return SYNCING, nil
|
||||
return beacon.SYNCING, nil
|
||||
}
|
||||
return INVALID, err
|
||||
return beacon.INVALID, err
|
||||
}
|
||||
// If the finalized block is set, check if it is in our blockchain
|
||||
if heads.FinalizedBlockHash != (common.Hash{}) {
|
||||
if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil {
|
||||
// TODO (MariusVanDerWijden) trigger sync
|
||||
return SYNCING, nil
|
||||
return beacon.SYNCING, nil
|
||||
}
|
||||
}
|
||||
// SetHead
|
||||
if err := api.setHead(heads.HeadBlockHash); err != nil {
|
||||
return INVALID, err
|
||||
return beacon.INVALID, err
|
||||
}
|
||||
// Assemble block (if needed)
|
||||
if PayloadAttributes != nil {
|
||||
data, err := api.assembleBlock(heads.HeadBlockHash, PayloadAttributes)
|
||||
// Assemble block (if needed). It only works for full node.
|
||||
if payloadAttributes != nil {
|
||||
data, err := api.assembleBlock(heads.HeadBlockHash, payloadAttributes)
|
||||
if err != nil {
|
||||
return INVALID, err
|
||||
return beacon.INVALID, err
|
||||
}
|
||||
hash := computePayloadId(heads.HeadBlockHash, PayloadAttributes)
|
||||
id := binary.BigEndian.Uint64(hash)
|
||||
api.preparedBlocks[id] = data
|
||||
log.Info("Created payload", "payloadid", id)
|
||||
// TODO (MariusVanDerWijden) do something with the payloadID?
|
||||
hex := hexutil.Bytes(hash)
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &hex}, nil
|
||||
id := computePayloadId(heads.HeadBlockHash, payloadAttributes)
|
||||
api.preparedBlocks.put(id, data)
|
||||
log.Info("Created payload", "payloadID", id)
|
||||
return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: &id}, nil
|
||||
}
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
|
||||
return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil
|
||||
}
|
||||
|
||||
func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) []byte {
|
||||
// Hash
|
||||
hasher := sha256.New()
|
||||
hasher.Write(headBlockHash[:])
|
||||
binary.Write(hasher, binary.BigEndian, params.Timestamp)
|
||||
hasher.Write(params.Random[:])
|
||||
hasher.Write(params.SuggestedFeeRecipient[:])
|
||||
return hasher.Sum([]byte{})[:8]
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) invalid() ExecutePayloadResponse {
|
||||
if api.light {
|
||||
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()}
|
||||
// GetPayloadV1 returns a cached payload by id.
|
||||
func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) {
|
||||
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
|
||||
data := api.preparedBlocks.get(payloadID)
|
||||
if data == nil {
|
||||
return nil, &beacon.UnknownPayload
|
||||
}
|
||||
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ExecutePayload creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) {
|
||||
block, err := ExecutableDataToBlock(params)
|
||||
// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) {
|
||||
log.Trace("Engine API request received", "method", "ExecutePayload", params.BlockHash, "number", params.Number)
|
||||
block, err := beacon.ExecutableDataToBlock(params)
|
||||
if err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
if api.light {
|
||||
parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
|
||||
if parent == nil {
|
||||
return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
|
||||
}
|
||||
if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
|
||||
}
|
||||
if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
/*
|
||||
TODO (MariusVanDerWijden) reenable once sync is merged
|
||||
@@ -268,7 +134,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo
|
||||
}
|
||||
*/
|
||||
// TODO (MariusVanDerWijden) we should return nil here not empty hash
|
||||
return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil
|
||||
return beacon.ExecutePayloadResponse{Status: beacon.SYNCING.Status, LatestValidHash: common.Hash{}}, nil
|
||||
}
|
||||
parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
|
||||
td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
|
||||
@@ -276,188 +142,44 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo
|
||||
if td.Cmp(ttd) < 0 {
|
||||
return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
|
||||
}
|
||||
log.Trace("Inserting block without head", "hash", block.Hash(), "number", block.Number)
|
||||
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
|
||||
if merger := api.merger(); !merger.TDDReached() {
|
||||
if merger := api.eth.Merger(); !merger.TDDReached() {
|
||||
merger.ReachTTD()
|
||||
}
|
||||
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
|
||||
return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil
|
||||
}
|
||||
|
||||
// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
|
||||
// data" required for eth2 clients to process the new block.
|
||||
func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) {
|
||||
if api.light {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
// computePayloadId computes a pseudo-random payloadid, based on the parameters.
|
||||
func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID {
|
||||
// Hash
|
||||
hasher := sha256.New()
|
||||
hasher.Write(headBlockHash[:])
|
||||
binary.Write(hasher, binary.BigEndian, params.Timestamp)
|
||||
hasher.Write(params.Random[:])
|
||||
hasher.Write(params.SuggestedFeeRecipient[:])
|
||||
var out beacon.PayloadID
|
||||
copy(out[:], hasher.Sum(nil)[:8])
|
||||
return out
|
||||
}
|
||||
|
||||
// invalid returns a response "INVALID" with the latest valid hash set to the current head.
|
||||
func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse {
|
||||
return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
|
||||
}
|
||||
|
||||
// assembleBlock creates a new block and returns the "execution
|
||||
// data" required for beacon clients to process the new block.
|
||||
func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
|
||||
log.Info("Producing block", "parentHash", parentHash)
|
||||
|
||||
bc := api.eth.BlockChain()
|
||||
parent := bc.GetBlockByHash(parentHash)
|
||||
if parent == nil {
|
||||
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", parentHash)
|
||||
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", parentHash)
|
||||
}
|
||||
|
||||
if params.Timestamp < parent.Time() {
|
||||
return nil, fmt.Errorf("child timestamp lower than parent's: %d < %d", params.Timestamp, parent.Time())
|
||||
}
|
||||
if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
|
||||
diff := time.Duration(params.Timestamp-now) * time.Second
|
||||
log.Warn("Producing block too far in the future", "diff", common.PrettyDuration(diff))
|
||||
}
|
||||
pending := api.eth.TxPool().Pending(true)
|
||||
coinbase := params.SuggestedFeeRecipient
|
||||
num := parent.Number()
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Number: num.Add(num, common.Big1),
|
||||
Coinbase: coinbase,
|
||||
GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
|
||||
Extra: []byte{}, // TODO (MariusVanDerWijden) properly set extra data
|
||||
Time: params.Timestamp,
|
||||
}
|
||||
if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) {
|
||||
header.BaseFee = misc.CalcBaseFee(config, parent.Header())
|
||||
}
|
||||
if err := api.engine.Prepare(bc, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env, err := api.makeEnv(parent, header)
|
||||
block, err := api.eth.Miner().GetSealingBlock(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
signer = types.MakeSigner(bc.Config(), header.Number)
|
||||
txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil)
|
||||
transactions []*types.Transaction
|
||||
)
|
||||
for {
|
||||
if env.gasPool.Gas() < chainParams.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas)
|
||||
break
|
||||
}
|
||||
tx := txHeap.Peek()
|
||||
if tx == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// The sender is only for logging purposes, and it doesn't really matter if it's correct.
|
||||
from, _ := types.Sender(signer, tx)
|
||||
|
||||
// Execute the transaction
|
||||
env.state.Prepare(tx.Hash(), env.tcount)
|
||||
err = env.commitTransaction(tx, coinbase)
|
||||
switch err {
|
||||
case core.ErrGasLimitReached:
|
||||
// Pop the current out-of-gas transaction without shifting in the next from the account
|
||||
log.Trace("Gas limit exceeded for current block", "sender", from)
|
||||
txHeap.Pop()
|
||||
|
||||
case core.ErrNonceTooLow:
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txHeap.Shift()
|
||||
|
||||
case core.ErrNonceTooHigh:
|
||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||
log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txHeap.Pop()
|
||||
|
||||
case nil:
|
||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||
env.tcount++
|
||||
txHeap.Shift()
|
||||
transactions = append(transactions, tx)
|
||||
|
||||
default:
|
||||
// Strange error, discard the transaction and get the next in line (note, the
|
||||
// nonce-too-high clause will prevent us from executing in vain).
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
txHeap.Shift()
|
||||
}
|
||||
}
|
||||
// Create the block.
|
||||
block, err := api.engine.FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BlockToExecutableData(block, params.Random), nil
|
||||
}
|
||||
|
||||
func encodeTransactions(txs []*types.Transaction) [][]byte {
|
||||
var enc = make([][]byte, len(txs))
|
||||
for i, tx := range txs {
|
||||
enc[i], _ = tx.MarshalBinary()
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||
var txs = make([]*types.Transaction, len(enc))
|
||||
for i, encTx := range enc {
|
||||
var tx types.Transaction
|
||||
if err := tx.UnmarshalBinary(encTx); err != nil {
|
||||
return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
|
||||
}
|
||||
txs[i] = &tx
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
|
||||
txs, err := decodeTransactions(params.Transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(params.ExtraData) > 32 {
|
||||
return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData))
|
||||
}
|
||||
number := big.NewInt(0)
|
||||
number.SetUint64(params.Number)
|
||||
header := &types.Header{
|
||||
ParentHash: params.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: params.FeeRecipient,
|
||||
Root: params.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: params.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(params.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: number,
|
||||
GasLimit: params.GasLimit,
|
||||
GasUsed: params.GasUsed,
|
||||
Time: params.Timestamp,
|
||||
BaseFee: params.BaseFeePerGas,
|
||||
Extra: params.ExtraData,
|
||||
// TODO (MariusVanDerWijden) add params.Random to header once required
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
|
||||
if block.Hash() != params.BlockHash {
|
||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableDataV1 {
|
||||
return &ExecutableDataV1{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: random,
|
||||
ExtraData: block.Extra(),
|
||||
}
|
||||
return beacon.BlockToExecutableData(block), nil
|
||||
}
|
||||
|
||||
// Used in tests to add a the list of transactions from a block to the tx pool.
|
||||
@@ -470,17 +192,17 @@ func (api *ConsensusAPI) insertTransactions(txs types.Transactions) error {
|
||||
|
||||
func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
|
||||
// shortcut if we entered PoS already
|
||||
if api.merger().PoSFinalized() {
|
||||
if api.eth.Merger().PoSFinalized() {
|
||||
return nil
|
||||
}
|
||||
// make sure the parent has enough terminal total difficulty
|
||||
newHeadBlock := api.eth.BlockChain().GetBlockByHash(head)
|
||||
if newHeadBlock == nil {
|
||||
return &GenericServerError
|
||||
return &beacon.GenericServerError
|
||||
}
|
||||
td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64())
|
||||
if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 {
|
||||
return &InvalidTB
|
||||
return &beacon.InvalidTB
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -488,49 +210,22 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
|
||||
// setHead is called to perform a force choice.
|
||||
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
|
||||
log.Info("Setting head", "head", newHead)
|
||||
if api.light {
|
||||
headHeader := api.les.BlockChain().CurrentHeader()
|
||||
if headHeader.Hash() == newHead {
|
||||
return nil
|
||||
}
|
||||
newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead)
|
||||
if newHeadHeader == nil {
|
||||
return &GenericServerError
|
||||
}
|
||||
if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
// Trigger the transition if it's the first `NewHead` event.
|
||||
merger := api.merger()
|
||||
if !merger.PoSFinalized() {
|
||||
merger.FinalizePoS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
headBlock := api.eth.BlockChain().CurrentBlock()
|
||||
if headBlock.Hash() == newHead {
|
||||
return nil
|
||||
}
|
||||
newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead)
|
||||
if newHeadBlock == nil {
|
||||
return &GenericServerError
|
||||
return &beacon.GenericServerError
|
||||
}
|
||||
if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
// Trigger the transition if it's the first `NewHead` event.
|
||||
if merger := api.merger(); !merger.PoSFinalized() {
|
||||
if merger := api.eth.Merger(); !merger.PoSFinalized() {
|
||||
merger.FinalizePoS()
|
||||
}
|
||||
// TODO (MariusVanDerWijden) are we really synced now?
|
||||
api.eth.SetSynced()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function, return the merger instance.
|
||||
func (api *ConsensusAPI) merger() *consensus.Merger {
|
||||
if api.light {
|
||||
return api.les.Merger()
|
||||
}
|
||||
return api.eth.Merger()
|
||||
}
|
||||
|
||||
+76
-26
@@ -17,14 +17,15 @@
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/beacon"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -78,14 +79,14 @@ func TestEth2AssembleBlock(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks)
|
||||
defer n.Close()
|
||||
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
api := NewConsensusAPI(ethservice)
|
||||
signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
|
||||
tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("error signing transaction, err=%v", err)
|
||||
}
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
blockParams := PayloadAttributesV1{
|
||||
blockParams := beacon.PayloadAttributesV1{
|
||||
Timestamp: blocks[9].Time() + 5,
|
||||
}
|
||||
execData, err := api.assembleBlock(blocks[9].Hash(), &blockParams)
|
||||
@@ -102,11 +103,11 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks[:9])
|
||||
defer n.Close()
|
||||
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
api := NewConsensusAPI(ethservice)
|
||||
|
||||
// Put the 10th block's tx in the pool and produce a new block
|
||||
api.insertTransactions(blocks[9].Transactions())
|
||||
blockParams := PayloadAttributesV1{
|
||||
blockParams := beacon.PayloadAttributesV1{
|
||||
Timestamp: blocks[8].Time() + 5,
|
||||
}
|
||||
execData, err := api.assembleBlock(blocks[8].Hash(), &blockParams)
|
||||
@@ -123,8 +124,8 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks)
|
||||
defer n.Close()
|
||||
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
fcState := ForkchoiceStateV1{
|
||||
api := NewConsensusAPI(ethservice)
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: blocks[5].Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
@@ -141,14 +142,14 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks[:9])
|
||||
defer n.Close()
|
||||
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
api := NewConsensusAPI(ethservice)
|
||||
|
||||
// Put the 10th block's tx in the pool and produce a new block
|
||||
api.insertTransactions(blocks[9].Transactions())
|
||||
blockParams := PayloadAttributesV1{
|
||||
blockParams := beacon.PayloadAttributesV1{
|
||||
Timestamp: blocks[8].Time() + 5,
|
||||
}
|
||||
fcState := ForkchoiceStateV1{
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: blocks[8].Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
@@ -158,13 +159,21 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
|
||||
execData, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
|
||||
execData, err := api.GetPayloadV1(payloadID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting payload, err=%v", err)
|
||||
}
|
||||
if len(execData.Transactions) != blocks[9].Transactions().Len() {
|
||||
t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
|
||||
}
|
||||
// Test invalid payloadID
|
||||
var invPayload beacon.PayloadID
|
||||
copy(invPayload[:], payloadID[:])
|
||||
invPayload[0] = ^invPayload[0]
|
||||
_, err = api.GetPayloadV1(invPayload)
|
||||
if err == nil {
|
||||
t.Fatal("expected error retrieving invalid payload")
|
||||
}
|
||||
}
|
||||
|
||||
func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) {
|
||||
@@ -185,6 +194,48 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidPayloadTimestamp(t *testing.T) {
|
||||
genesis, preMergeBlocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
ethservice.Merger().ReachTTD()
|
||||
defer n.Close()
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice)
|
||||
parent = ethservice.BlockChain().CurrentBlock()
|
||||
)
|
||||
tests := []struct {
|
||||
time uint64
|
||||
shouldErr bool
|
||||
}{
|
||||
{0, true},
|
||||
{parent.Time(), true},
|
||||
{parent.Time() - 1, true},
|
||||
{parent.Time() + 1, false},
|
||||
{uint64(time.Now().Unix()) + uint64(time.Minute), false},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) {
|
||||
params := beacon.PayloadAttributesV1{
|
||||
Timestamp: test.time,
|
||||
Random: crypto.Keccak256Hash([]byte{byte(123)}),
|
||||
SuggestedFeeRecipient: parent.Coinbase(),
|
||||
}
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
_, err := api.ForkchoiceUpdatedV1(fcState, ¶ms)
|
||||
if test.shouldErr && err == nil {
|
||||
t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
|
||||
} else if !test.shouldErr && err != nil {
|
||||
t.Fatalf("error preparing payload with valid timestamp, err=%v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEth2NewBlock(t *testing.T) {
|
||||
genesis, preMergeBlocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
@@ -192,7 +243,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
defer n.Close()
|
||||
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice, nil)
|
||||
api = NewConsensusAPI(ethservice)
|
||||
parent = preMergeBlocks[len(preMergeBlocks)-1]
|
||||
|
||||
// This EVM code generates a log when the contract is created.
|
||||
@@ -210,13 +261,13 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
|
||||
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
|
||||
execData, err := api.assembleBlock(parent.Hash(), &beacon.PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create the executable data %v", err)
|
||||
}
|
||||
block, err := ExecutableDataToBlock(*execData)
|
||||
block, err := beacon.ExecutableDataToBlock(*execData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
@@ -228,7 +279,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
t.Fatalf("Chain head shouldn't be updated")
|
||||
}
|
||||
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
|
||||
fcState := ForkchoiceStateV1{
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: block.Hash(),
|
||||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
@@ -250,13 +301,13 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
)
|
||||
parent = preMergeBlocks[len(preMergeBlocks)-1]
|
||||
for i := 0; i < 10; i++ {
|
||||
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
|
||||
execData, err := api.assembleBlock(parent.Hash(), &beacon.PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 6,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create the executable data %v", err)
|
||||
}
|
||||
block, err := ExecutableDataToBlock(*execData)
|
||||
block, err := beacon.ExecutableDataToBlock(*execData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
@@ -268,7 +319,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
t.Fatalf("Chain head shouldn't be updated")
|
||||
}
|
||||
|
||||
fcState := ForkchoiceStateV1{
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: block.Hash(),
|
||||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
@@ -362,7 +413,7 @@ func TestFullAPI(t *testing.T) {
|
||||
ethservice.Merger().ReachTTD()
|
||||
defer n.Close()
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice, nil)
|
||||
api = NewConsensusAPI(ethservice)
|
||||
parent = ethservice.BlockChain().CurrentBlock()
|
||||
// This EVM code generates a log when the contract is created.
|
||||
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||
@@ -373,12 +424,12 @@ func TestFullAPI(t *testing.T) {
|
||||
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
|
||||
params := PayloadAttributesV1{
|
||||
params := beacon.PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 1,
|
||||
Random: crypto.Keccak256Hash([]byte{byte(i)}),
|
||||
SuggestedFeeRecipient: parent.Coinbase(),
|
||||
}
|
||||
fcState := ForkchoiceStateV1{
|
||||
fcState := beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
@@ -387,11 +438,11 @@ func TestFullAPI(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
if resp.Status != SUCCESS.Status {
|
||||
if resp.Status != beacon.SUCCESS.Status {
|
||||
t.Fatalf("error preparing payload, invalid status: %v", resp.Status)
|
||||
}
|
||||
payloadID := computePayloadId(parent.Hash(), ¶ms)
|
||||
payload, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
|
||||
payload, err := api.GetPayloadV1(payloadID)
|
||||
if err != nil {
|
||||
t.Fatalf("can't get payload: %v", err)
|
||||
}
|
||||
@@ -399,10 +450,10 @@ func TestFullAPI(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("can't execute payload: %v", err)
|
||||
}
|
||||
if execResp.Status != VALID.Status {
|
||||
if execResp.Status != beacon.VALID.Status {
|
||||
t.Fatalf("invalid status: %v", execResp.Status)
|
||||
}
|
||||
fcState = ForkchoiceStateV1{
|
||||
fcState = beacon.ForkchoiceStateV1{
|
||||
HeadBlockHash: payload.BlockHash,
|
||||
SafeBlockHash: payload.ParentHash,
|
||||
FinalizedBlockHash: payload.ParentHash,
|
||||
@@ -414,6 +465,5 @@ func TestFullAPI(t *testing.T) {
|
||||
t.Fatalf("Chain head should be updated")
|
||||
}
|
||||
parent = ethservice.BlockChain().CurrentBlock()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
||||
// Structure described at https://github.com/ethereum/execution-apis/pull/74
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for PayloadAttributesV1.
|
||||
type payloadAttributesMarshaling struct {
|
||||
Timestamp hexutil.Uint64
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
|
||||
|
||||
// Structure described at https://github.com/ethereum/execution-apis/src/engine/specification.md
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
type executableDataMarshaling struct {
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
BaseFeePerGas *hexutil.Big
|
||||
ExtraData hexutil.Bytes
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadResponse -field-override payloadResponseMarshaling -out gen_payload.go
|
||||
|
||||
type PayloadResponse struct {
|
||||
PayloadID uint64 `json:"payloadId"`
|
||||
}
|
||||
|
||||
// JSON type overrides for payloadResponse.
|
||||
type payloadResponseMarshaling struct {
|
||||
PayloadID hexutil.Uint64
|
||||
}
|
||||
|
||||
type NewBlockResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
|
||||
type GenericResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type GenericStringResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ExecutePayloadResponse struct {
|
||||
Status string `json:"status"`
|
||||
LatestValidHash common.Hash `json:"latestValidHash"`
|
||||
}
|
||||
|
||||
type ConsensusValidatedParams struct {
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ForkChoiceResponse struct {
|
||||
Status string `json:"status"`
|
||||
PayloadID *hexutil.Bytes `json:"payloadId"`
|
||||
}
|
||||
|
||||
type ForkchoiceStateV1 struct {
|
||||
HeadBlockHash common.Hash `json:"headBlockHash"`
|
||||
SafeBlockHash common.Hash `json:"safeBlockHash"`
|
||||
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*payloadAttributesMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var enc PayloadAttributesV1
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
enc.Random = p.Random
|
||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random *common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var dec PayloadAttributesV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for PayloadAttributesV1")
|
||||
}
|
||||
p.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'random' for PayloadAttributesV1")
|
||||
}
|
||||
p.Random = *dec.Random
|
||||
if dec.SuggestedFeeRecipient == nil {
|
||||
return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV1")
|
||||
}
|
||||
p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient
|
||||
return nil
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var enc ExecutableDataV1
|
||||
enc.ParentHash = e.ParentHash
|
||||
enc.FeeRecipient = e.FeeRecipient
|
||||
enc.StateRoot = e.StateRoot
|
||||
enc.ReceiptsRoot = e.ReceiptsRoot
|
||||
enc.LogsBloom = e.LogsBloom
|
||||
enc.Random = e.Random
|
||||
enc.Number = hexutil.Uint64(e.Number)
|
||||
enc.GasLimit = hexutil.Uint64(e.GasLimit)
|
||||
enc.GasUsed = hexutil.Uint64(e.GasUsed)
|
||||
enc.Timestamp = hexutil.Uint64(e.Timestamp)
|
||||
enc.ExtraData = e.ExtraData
|
||||
enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas)
|
||||
enc.BlockHash = e.BlockHash
|
||||
if e.Transactions != nil {
|
||||
enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
|
||||
for k, v := range e.Transactions {
|
||||
enc.Transactions[k] = v
|
||||
}
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"random" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var dec ExecutableDataV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.ParentHash == nil {
|
||||
return errors.New("missing required field 'parentHash' for ExecutableDataV1")
|
||||
}
|
||||
e.ParentHash = *dec.ParentHash
|
||||
if dec.FeeRecipient == nil {
|
||||
return errors.New("missing required field 'feeRecipient' for ExecutableDataV1")
|
||||
}
|
||||
e.FeeRecipient = *dec.FeeRecipient
|
||||
if dec.StateRoot == nil {
|
||||
return errors.New("missing required field 'stateRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.StateRoot = *dec.StateRoot
|
||||
if dec.ReceiptsRoot == nil {
|
||||
return errors.New("missing required field 'receiptsRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.ReceiptsRoot = *dec.ReceiptsRoot
|
||||
if dec.LogsBloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for ExecutableDataV1")
|
||||
}
|
||||
e.LogsBloom = *dec.LogsBloom
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'random' for ExecutableDataV1")
|
||||
}
|
||||
e.Random = *dec.Random
|
||||
if dec.Number == nil {
|
||||
return errors.New("missing required field 'blockNumber' for ExecutableDataV1")
|
||||
}
|
||||
e.Number = uint64(*dec.Number)
|
||||
if dec.GasLimit == nil {
|
||||
return errors.New("missing required field 'gasLimit' for ExecutableDataV1")
|
||||
}
|
||||
e.GasLimit = uint64(*dec.GasLimit)
|
||||
if dec.GasUsed == nil {
|
||||
return errors.New("missing required field 'gasUsed' for ExecutableDataV1")
|
||||
}
|
||||
e.GasUsed = uint64(*dec.GasUsed)
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for ExecutableDataV1")
|
||||
}
|
||||
e.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.ExtraData == nil {
|
||||
return errors.New("missing required field 'extraData' for ExecutableDataV1")
|
||||
}
|
||||
e.ExtraData = *dec.ExtraData
|
||||
if dec.BaseFeePerGas == nil {
|
||||
return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1")
|
||||
}
|
||||
e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
|
||||
if dec.BlockHash == nil {
|
||||
return errors.New("missing required field 'blockHash' for ExecutableDataV1")
|
||||
}
|
||||
e.BlockHash = *dec.BlockHash
|
||||
if dec.Transactions == nil {
|
||||
return errors.New("missing required field 'transactions' for ExecutableDataV1")
|
||||
}
|
||||
e.Transactions = make([][]byte, len(dec.Transactions))
|
||||
for k, v := range dec.Transactions {
|
||||
e.Transactions[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*payloadResponseMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (p PayloadResponse) MarshalJSON() ([]byte, error) {
|
||||
type PayloadResponse struct {
|
||||
PayloadID hexutil.Uint64 `json:"payloadId"`
|
||||
}
|
||||
var enc PayloadResponse
|
||||
enc.PayloadID = hexutil.Uint64(p.PayloadID)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (p *PayloadResponse) UnmarshalJSON(input []byte) error {
|
||||
type PayloadResponse struct {
|
||||
PayloadID *hexutil.Uint64 `json:"payloadId"`
|
||||
}
|
||||
var dec PayloadResponse
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.PayloadID != nil {
|
||||
p.PayloadID = uint64(*dec.PayloadID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/beacon"
|
||||
)
|
||||
|
||||
// maxTrackedPayloads is the maximum number of prepared payloads the execution
|
||||
// engine tracks before evicting old ones. Ideally we should only ever track the
|
||||
// latest one; but have a slight wiggle room for non-ideal conditions.
|
||||
const maxTrackedPayloads = 10
|
||||
|
||||
// payloadQueueItem represents an id->payload tuple to store until it's retrieved
|
||||
// or evicted.
|
||||
type payloadQueueItem struct {
|
||||
id beacon.PayloadID
|
||||
payload *beacon.ExecutableDataV1
|
||||
}
|
||||
|
||||
// payloadQueue tracks the latest handful of constructed payloads to be retrieved
|
||||
// by the beacon chain if block production is requested.
|
||||
type payloadQueue struct {
|
||||
payloads []*payloadQueueItem
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
|
||||
// all containing empty items.
|
||||
func newPayloadQueue() *payloadQueue {
|
||||
return &payloadQueue{
|
||||
payloads: make([]*payloadQueueItem, maxTrackedPayloads),
|
||||
}
|
||||
}
|
||||
|
||||
// put inserts a new payload into the queue at the given id.
|
||||
func (q *payloadQueue) put(id beacon.PayloadID, data *beacon.ExecutableDataV1) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
copy(q.payloads[1:], q.payloads)
|
||||
q.payloads[0] = &payloadQueueItem{
|
||||
id: id,
|
||||
payload: data,
|
||||
}
|
||||
}
|
||||
|
||||
// get retrieves a previously stored payload item or nil if it does not exist.
|
||||
func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
for _, item := range q.payloads {
|
||||
if item == nil {
|
||||
return nil // no more items
|
||||
}
|
||||
if item.id == id {
|
||||
return item.payload
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -364,6 +364,7 @@ func testSequentialAnnouncements(t *testing.T, light bool) {
|
||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||
|
||||
tester := newTester(light)
|
||||
defer tester.fetcher.Stop()
|
||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
||||
|
||||
@@ -743,7 +744,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) {
|
||||
badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0)
|
||||
|
||||
imported := make(chan interface{})
|
||||
announced := make(chan interface{})
|
||||
announced := make(chan interface{}, 2)
|
||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
||||
if light {
|
||||
if header == nil {
|
||||
@@ -806,6 +807,7 @@ func TestEmptyBlockShortCircuit(t *testing.T) {
|
||||
hashes, blocks := makeChain(32, 0, genesis)
|
||||
|
||||
tester := newTester(false)
|
||||
defer tester.fetcher.Stop()
|
||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
||||
|
||||
|
||||
+1
-1
@@ -433,7 +433,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||
return
|
||||
}
|
||||
peer.Log().Debug("Whitelist block verified", "number", number, "hash", hash)
|
||||
|
||||
res.Done <- nil
|
||||
case <-timeout.C:
|
||||
peer.Log().Warn("Whitelist challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
h.removePeer(peer.ID())
|
||||
|
||||
@@ -299,7 +299,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac
|
||||
size uint64
|
||||
last common.Hash
|
||||
)
|
||||
for it.Next() && size < req.Bytes {
|
||||
for it.Next() {
|
||||
hash, account := it.Hash(), common.CopyBytes(it.Account())
|
||||
|
||||
// Track the returned interval for the Merkle proofs
|
||||
@@ -315,6 +315,9 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac
|
||||
if bytes.Compare(hash[:], req.Limit[:]) >= 0 {
|
||||
break
|
||||
}
|
||||
if size > req.Bytes {
|
||||
break
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
@@ -464,7 +467,7 @@ func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [
|
||||
// Peers should not request the empty code, but if they do, at
|
||||
// least sent them back a correct response without db lookups
|
||||
codes = append(codes, []byte{})
|
||||
} else if blob, err := chain.ContractCode(hash); err == nil {
|
||||
} else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil {
|
||||
codes = append(codes, blob)
|
||||
bytes += uint64(len(blob))
|
||||
}
|
||||
|
||||
@@ -1781,7 +1781,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
|
||||
for i, account := range res.accounts {
|
||||
// Check if the account is a contract with an unknown code
|
||||
if !bytes.Equal(account.CodeHash, emptyCode[:]) {
|
||||
if code := rawdb.ReadCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)); code == nil {
|
||||
if !rawdb.HasCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)) {
|
||||
res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{}
|
||||
res.task.needCode[i] = true
|
||||
res.task.pend++
|
||||
@@ -1789,7 +1789,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
|
||||
}
|
||||
// Check if the account is a contract with an unknown storage trie
|
||||
if account.Root != emptyRoot {
|
||||
if node, err := s.db.Get(account.Root[:]); err != nil || node == nil {
|
||||
if ok, err := s.db.Has(account.Root[:]); err != nil || !ok {
|
||||
// If there was a previous large state retrieval in progress,
|
||||
// don't restart it from scratch. This happens if a sync cycle
|
||||
// is interrupted and resumed later. However, *do* update the
|
||||
|
||||
+2
-1
@@ -593,11 +593,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||
if threads > len(txs) {
|
||||
threads = len(txs)
|
||||
}
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
blockHash := block.Hash()
|
||||
for th := 0; th < threads; th++ {
|
||||
pend.Add(1)
|
||||
go func() {
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
defer pend.Done()
|
||||
// Fetch and execute the next transaction trace tasks
|
||||
for task := range jobs {
|
||||
@@ -618,6 +618,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||
}
|
||||
// Feed the transactions into the tracers and return
|
||||
var failed error
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
for i, tx := range txs {
|
||||
// Send the trace task over for execution
|
||||
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
|
||||
|
||||
@@ -130,10 +130,6 @@ func TestCallTracerLegacy(t *testing.T) {
|
||||
testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
|
||||
}
|
||||
|
||||
func TestCallTracerJs(t *testing.T) {
|
||||
testCallTracer("callTracerJs", "call_tracer", t)
|
||||
}
|
||||
|
||||
func TestCallTracerNative(t *testing.T) {
|
||||
testCallTracer("callTracer", "call_tracer", t)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,112 +0,0 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
// callFrameTracer uses the new call frame tracing methods to report useful information
|
||||
// about internal messages of a transaction.
|
||||
{
|
||||
callstack: [{}],
|
||||
fault: function(log, db) {},
|
||||
result: function(ctx, db) {
|
||||
// Prepare outer message info
|
||||
var result = {
|
||||
type: ctx.type,
|
||||
from: toHex(ctx.from),
|
||||
to: toHex(ctx.to),
|
||||
value: '0x' + ctx.value.toString(16),
|
||||
gas: '0x' + bigInt(ctx.gas).toString(16),
|
||||
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
|
||||
input: toHex(ctx.input),
|
||||
output: toHex(ctx.output),
|
||||
}
|
||||
if (this.callstack[0].calls !== undefined) {
|
||||
result.calls = this.callstack[0].calls
|
||||
}
|
||||
if (this.callstack[0].error !== undefined) {
|
||||
result.error = this.callstack[0].error
|
||||
} else if (ctx.error !== undefined) {
|
||||
result.error = ctx.error
|
||||
}
|
||||
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
|
||||
delete result.output
|
||||
}
|
||||
|
||||
return this.finalize(result)
|
||||
},
|
||||
enter: function(frame) {
|
||||
var call = {
|
||||
type: frame.getType(),
|
||||
from: toHex(frame.getFrom()),
|
||||
to: toHex(frame.getTo()),
|
||||
input: toHex(frame.getInput()),
|
||||
gas: '0x' + bigInt(frame.getGas()).toString('16'),
|
||||
}
|
||||
if (frame.getValue() !== undefined){
|
||||
call.value='0x' + bigInt(frame.getValue()).toString(16)
|
||||
}
|
||||
this.callstack.push(call)
|
||||
},
|
||||
exit: function(frameResult) {
|
||||
var len = this.callstack.length
|
||||
if (len > 1) {
|
||||
var call = this.callstack.pop()
|
||||
call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16')
|
||||
var error = frameResult.getError()
|
||||
if (error === undefined) {
|
||||
call.output = toHex(frameResult.getOutput())
|
||||
} else {
|
||||
call.error = error
|
||||
if (call.type === 'CREATE' || call.type === 'CREATE2') {
|
||||
delete call.to
|
||||
}
|
||||
}
|
||||
len -= 1
|
||||
if (this.callstack[len-1].calls === undefined) {
|
||||
this.callstack[len-1].calls = []
|
||||
}
|
||||
this.callstack[len-1].calls.push(call)
|
||||
}
|
||||
},
|
||||
// finalize recreates a call object using the final desired field oder for json
|
||||
// serialization. This is a nicety feature to pass meaningfully ordered results
|
||||
// to users who don't interpret it, just display it.
|
||||
finalize: function(call) {
|
||||
var sorted = {
|
||||
type: call.type,
|
||||
from: call.from,
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
gas: call.gas,
|
||||
gasUsed: call.gasUsed,
|
||||
input: call.input,
|
||||
output: call.output,
|
||||
error: call.error,
|
||||
time: call.time,
|
||||
calls: call.calls,
|
||||
}
|
||||
for (var key in sorted) {
|
||||
if (sorted[key] === undefined) {
|
||||
delete sorted[key]
|
||||
}
|
||||
}
|
||||
if (sorted.calls !== undefined) {
|
||||
for (var i=0; i<sorted.calls.length; i++) {
|
||||
sorted.calls[i] = this.finalize(sorted.calls[i])
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -47,6 +47,13 @@
|
||||
// result is invoked when all the opcodes have been iterated over and returns
|
||||
// the final result of the tracing.
|
||||
result: function(ctx, db) {
|
||||
if (this.prestate === null) {
|
||||
this.prestate = {};
|
||||
// If tx is transfer-only, the recipient account
|
||||
// hasn't been populated.
|
||||
this.lookupAccount(ctx.to, db);
|
||||
}
|
||||
|
||||
// At this point, we need to deduct the 'value' from the
|
||||
// outer transaction, and move it back to the origin
|
||||
this.lookupAccount(ctx.from, db);
|
||||
@@ -79,7 +86,7 @@
|
||||
}
|
||||
// Whenever new state is accessed, add it to the prestate
|
||||
switch (log.op.toString()) {
|
||||
case "EXTCODECOPY": case "EXTCODESIZE": case "BALANCE":
|
||||
case "EXTCODECOPY": case "EXTCODESIZE": case "EXTCODEHASH": case "BALANCE":
|
||||
this.lookupAccount(toAddress(log.stack.peek(0).toString(16)), db);
|
||||
break;
|
||||
case "CREATE":
|
||||
@@ -697,7 +697,7 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
|
||||
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
|
||||
jst.dbWrapper.db = env.StateDB
|
||||
// Update list of precompiles based on current block
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
|
||||
jst.activePrecompiles = vm.ActivePrecompiles(rules)
|
||||
|
||||
// Compute intrinsic gas
|
||||
|
||||
@@ -83,7 +83,7 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
|
||||
t.env = env
|
||||
|
||||
// Update list of precompiles based on current block
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
|
||||
t.activePrecompiles = vm.ActivePrecompiles(rules)
|
||||
|
||||
// Save the outer calldata also
|
||||
|
||||
@@ -59,8 +59,7 @@ type callTracer struct {
|
||||
func newCallTracer() tracers.Tracer {
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
t := &callTracer{callstack: make([]callFrame, 1)}
|
||||
return t
|
||||
return &callTracer{callstack: make([]callFrame, 1)}
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("noopTracerNative", newNoopTracer)
|
||||
register("noopTracer", newNoopTracer)
|
||||
}
|
||||
|
||||
// noopTracer is a go implementation of the Tracer interface which
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("prestateTracer", newPrestateTracer)
|
||||
}
|
||||
|
||||
type prestate = map[common.Address]*account
|
||||
type account struct {
|
||||
Balance string `json:"balance"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Code string `json:"code"`
|
||||
Storage map[common.Hash]common.Hash `json:"storage"`
|
||||
}
|
||||
|
||||
type prestateTracer struct {
|
||||
env *vm.EVM
|
||||
prestate prestate
|
||||
create bool
|
||||
to common.Address
|
||||
interrupt uint32 // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
}
|
||||
|
||||
func newPrestateTracer() tracers.Tracer {
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
return &prestateTracer{prestate: prestate{}}
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
t.create = create
|
||||
t.to = to
|
||||
|
||||
// Compute intrinsic gas
|
||||
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
|
||||
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
|
||||
intrinsicGas, err := core.IntrinsicGas(input, nil, create, isHomestead, isIstanbul)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.lookupAccount(from)
|
||||
t.lookupAccount(to)
|
||||
|
||||
// The recipient balance includes the value transferred.
|
||||
toBal := hexutil.MustDecodeBig(t.prestate[to].Balance)
|
||||
toBal = new(big.Int).Sub(toBal, value)
|
||||
t.prestate[to].Balance = hexutil.EncodeBig(toBal)
|
||||
|
||||
// The sender balance is after reducing: value, gasLimit, intrinsicGas.
|
||||
// We need to re-add them to get the pre-tx balance.
|
||||
fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance)
|
||||
gasPrice := env.TxContext.GasPrice
|
||||
consumedGas := new(big.Int).Mul(
|
||||
gasPrice,
|
||||
new(big.Int).Add(
|
||||
new(big.Int).SetUint64(intrinsicGas),
|
||||
new(big.Int).SetUint64(gas),
|
||||
),
|
||||
)
|
||||
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
|
||||
t.prestate[from].Balance = hexutil.EncodeBig(fromBal)
|
||||
t.prestate[from].Nonce--
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
|
||||
if t.create {
|
||||
// Exclude created contract.
|
||||
delete(t.prestate, t.to)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
stackData := stack.Data()
|
||||
stackLen := len(stackData)
|
||||
switch {
|
||||
case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
|
||||
slot := common.Hash(stackData[stackLen-1].Bytes32())
|
||||
t.lookupStorage(scope.Contract.Address(), slot)
|
||||
case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
|
||||
addr := common.Address(stackData[stackLen-1].Bytes20())
|
||||
t.lookupAccount(addr)
|
||||
case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
|
||||
addr := common.Address(stackData[stackLen-2].Bytes20())
|
||||
t.lookupAccount(addr)
|
||||
case op == vm.CREATE:
|
||||
addr := scope.Contract.Address()
|
||||
nonce := t.env.StateDB.GetNonce(addr)
|
||||
t.lookupAccount(crypto.CreateAddress(addr, nonce))
|
||||
case stackLen >= 4 && op == vm.CREATE2:
|
||||
offset := stackData[stackLen-2]
|
||||
size := stackData[stackLen-3]
|
||||
init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
inithash := crypto.Keccak256(init)
|
||||
salt := stackData[stackLen-4]
|
||||
t.lookupAccount(crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash))
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault.
|
||||
func (t *prestateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *prestateTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
}
|
||||
|
||||
// GetResult returns the json-encoded nested list of call traces, and any
|
||||
// error arising from the encoding or forceful termination (via `Stop`).
|
||||
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
|
||||
res, err := json.Marshal(t.prestate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(res), t.reason
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *prestateTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
atomic.StoreUint32(&t.interrupt, 1)
|
||||
}
|
||||
|
||||
// lookupAccount fetches details of an account and adds it to the prestate
|
||||
// if it doesn't exist there.
|
||||
func (t *prestateTracer) lookupAccount(addr common.Address) {
|
||||
if _, ok := t.prestate[addr]; ok {
|
||||
return
|
||||
}
|
||||
t.prestate[addr] = &account{
|
||||
Balance: bigToHex(t.env.StateDB.GetBalance(addr)),
|
||||
Nonce: t.env.StateDB.GetNonce(addr),
|
||||
Code: bytesToHex(t.env.StateDB.GetCode(addr)),
|
||||
Storage: make(map[common.Hash]common.Hash),
|
||||
}
|
||||
}
|
||||
|
||||
// lookupStorage fetches the requested storage slot and adds
|
||||
// it to the prestate of the given contract. It assumes `lookupAccount`
|
||||
// has been performed on the contract before.
|
||||
func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) {
|
||||
if _, ok := t.prestate[addr].Storage[key]; ok {
|
||||
return
|
||||
}
|
||||
t.prestate[addr].Storage[key] = t.env.StateDB.GetState(addr, key)
|
||||
}
|
||||
Reference in New Issue
Block a user