erigon-pulse/cmd/rpcdaemon/commands/eth_block.go
ledgerwatch 0a31f5ac2a
Workaround for the code history of BSC system contracts (#6274)
Works around a flaw in the upgrade logic of the system contracts. Since
they are updated directly, without first being self-destructed and then
re-created, the usual incarnation logic does not get activated, and all
historical records of the code of these contracts are retrieved as the
most recent version. This problem will not exist in erigon3, but until
then, a workaround will be used to access code of such contracts through
a special structure, `SystemContractCodeLookup`

Fixes https://github.com/ledgerwatch/erigon/issues/5865

Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
2022-12-10 22:41:04 +00:00

390 lines
11 KiB
Go

package commands
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/crypto/cryptopool"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
"github.com/ledgerwatch/erigon/turbo/transactions"
)
func (api *APIImpl) CallBundle(ctx context.Context, txHashes []common.Hash, stateBlockNumberOrHash rpc.BlockNumberOrHash, timeoutMilliSecondsPtr *int64) (map[string]interface{}, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}
engine := api.engine()
if len(txHashes) == 0 {
return nil, nil
}
var txs types.Transactions
for _, txHash := range txHashes {
blockNum, ok, err := api.txnLookup(ctx, tx, txHash)
if err != nil {
return nil, err
}
if !ok {
return nil, nil
}
block, err := api.blockByNumberWithSenders(tx, blockNum)
if err != nil {
return nil, err
}
if block == nil {
return nil, nil
}
var txn types.Transaction
for _, transaction := range block.Transactions() {
if transaction.Hash() == txHash {
txn = transaction
break
}
}
if txn == nil {
return nil, nil // not error, see https://github.com/ledgerwatch/turbo-geth/issues/1645
}
txs = append(txs, txn)
}
defer func(start time.Time) { log.Trace("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
stateBlockNumber, hash, latest, err := rpchelper.GetBlockNumber(stateBlockNumberOrHash, tx, api.filters)
if err != nil {
return nil, err
}
var stateReader state.StateReader
if latest {
cacheView, err := api.stateCache.View(ctx, tx)
if err != nil {
return nil, err
}
stateReader = state.NewCachedReader2(cacheView, tx)
} else {
stateReader, err = rpchelper.CreateHistoryStateReader(tx, stateBlockNumber, 0, api._agg, api.historyV3(tx), chainConfig.ChainName)
if err != nil {
return nil, err
}
}
ibs := state.New(stateReader)
parent := rawdb.ReadHeader(tx, hash, stateBlockNumber)
if parent == nil {
return nil, fmt.Errorf("block %d(%x) not found", stateBlockNumber, hash)
}
blockNumber := stateBlockNumber + 1
timestamp := parent.Time + clparams.MainnetBeaconConfig.SecondsPerSlot
coinbase := parent.Coinbase
header := &types.Header{
ParentHash: parent.Hash(),
Number: big.NewInt(int64(blockNumber)),
GasLimit: parent.GasLimit,
Time: timestamp,
Difficulty: parent.Difficulty,
Coinbase: coinbase,
}
signer := types.MakeSigner(chainConfig, blockNumber)
rules := chainConfig.Rules(blockNumber, timestamp)
firstMsg, err := txs[0].AsMessage(*signer, nil, rules)
if err != nil {
return nil, err
}
blockCtx := transactions.NewEVMBlockContext(engine, header, stateBlockNumberOrHash.RequireCanonical, tx, api._blockReader)
txCtx := core.NewEVMTxContext(firstMsg)
// Get a new instance of the EVM
evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: false})
timeoutMilliSeconds := int64(5000)
if timeoutMilliSecondsPtr != nil {
timeoutMilliSeconds = *timeoutMilliSecondsPtr
}
timeout := time.Millisecond * time.Duration(timeoutMilliSeconds)
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
<-ctx.Done()
evm.Cancel()
}()
// Setup the gas pool (also for unmetered requests)
// and apply the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
results := []map[string]interface{}{}
bundleHash := cryptopool.NewLegacyKeccak256()
defer cryptopool.ReturnToPoolKeccak256(bundleHash)
for _, txn := range txs {
msg, err := txn.AsMessage(*signer, nil, rules)
if err != nil {
return nil, err
}
// Execute the transaction message
result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */)
if err != nil {
return nil, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
txHash := txn.Hash().String()
jsonResult := map[string]interface{}{
"txHash": txHash,
"gasUsed": result.UsedGas,
}
bundleHash.Write(txn.Hash().Bytes())
if result.Err != nil {
jsonResult["error"] = result.Err.Error()
} else {
jsonResult["value"] = common.BytesToHash(result.Return())
}
results = append(results, jsonResult)
}
ret := map[string]interface{}{}
ret["results"] = results
ret["bundleHash"] = hexutil.Encode(bundleHash.Sum(nil))
return ret, nil
}
// GetBlockByNumber implements eth_getBlockByNumber. Returns information about a block given the block's number.
func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
b, err := api.blockByNumber(ctx, number, tx)
if err != nil {
return nil, err
}
if b == nil {
return nil, nil
}
additionalFields := make(map[string]interface{})
td, err := rawdb.ReadTd(tx, b.Hash(), b.NumberU64())
if err != nil {
return nil, err
}
if td != nil {
additionalFields["totalDifficulty"] = (*hexutil.Big)(td)
}
chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}
var borTx types.Transaction
var borTxHash common.Hash
if chainConfig.Bor != nil {
borTx, _, _, _ = rawdb.ReadBorTransactionForBlock(tx, b)
if borTx != nil {
borTxHash = types.ComputeBorTxHash(b.NumberU64(), b.Hash())
}
}
response, err := ethapi.RPCMarshalBlockEx(b, true, fullTx, borTx, borTxHash, additionalFields)
if chainConfig.Bor != nil {
response["miner"], _ = ecrecover(b.Header(), chainConfig.Bor)
}
if err == nil && number == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
response[field] = nil
}
}
return response, err
}
// GetBlockByHash implements eth_getBlockByHash. Returns information about a block given the block's hash.
func (api *APIImpl) GetBlockByHash(ctx context.Context, numberOrHash rpc.BlockNumberOrHash, fullTx bool) (map[string]interface{}, error) {
if numberOrHash.BlockHash == nil {
// some web3.js based apps (like ethstats client) for some reason call
// eth_getBlockByHash with a block number as a parameter
// so no matter how weird that is, we would love to support that.
if numberOrHash.BlockNumber == nil {
return nil, nil // not error, see https://github.com/ledgerwatch/erigon/issues/1645
}
return api.GetBlockByNumber(ctx, *numberOrHash.BlockNumber, fullTx)
}
hash := *numberOrHash.BlockHash
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
additionalFields := make(map[string]interface{})
block, err := api.blockByHashWithSenders(tx, hash)
if err != nil {
return nil, err
}
if block == nil {
return nil, nil // not error, see https://github.com/ledgerwatch/erigon/issues/1645
}
number := block.NumberU64()
td, err := rawdb.ReadTd(tx, hash, number)
if err != nil {
return nil, err
}
additionalFields["totalDifficulty"] = (*hexutil.Big)(td)
chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}
var borTx types.Transaction
var borTxHash common.Hash
if chainConfig.Bor != nil {
borTx, _, _, _ = rawdb.ReadBorTransactionForBlock(tx, block)
if borTx != nil {
borTxHash = types.ComputeBorTxHash(block.NumberU64(), block.Hash())
}
}
response, err := ethapi.RPCMarshalBlockEx(block, true, fullTx, borTx, borTxHash, additionalFields)
if chainConfig.Bor != nil {
response["miner"], _ = ecrecover(block.Header(), chainConfig.Bor)
}
if err == nil && int64(number) == rpc.PendingBlockNumber.Int64() {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
response[field] = nil
}
}
return response, err
}
// GetBlockTransactionCountByNumber implements eth_getBlockTransactionCountByNumber. Returns the number of transactions in a block given the block's block number.
func (api *APIImpl) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
if blockNr == rpc.PendingBlockNumber {
b, err := api.blockByRPCNumber(blockNr, tx)
if err != nil {
return nil, err
}
if b == nil {
return nil, nil
}
n := hexutil.Uint(len(b.Transactions()))
return &n, nil
}
blockNum, blockHash, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(blockNr), tx, api.filters)
if err != nil {
return nil, err
}
_, txAmount, err := api._blockReader.Body(ctx, tx, blockHash, blockNum)
if err != nil {
return nil, err
}
if txAmount == 0 {
return nil, nil
}
numOfTx := hexutil.Uint(txAmount)
return &numOfTx, nil
}
// GetBlockTransactionCountByHash implements eth_getBlockTransactionCountByHash. Returns the number of transactions in a block given the block's block hash.
func (api *APIImpl) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockNum, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHash{BlockHash: &blockHash}, tx, nil)
if err != nil {
// (Compatibility) Every other node just return `null` for when the block does not exist.
log.Debug("eth_getBlockTransactionCountByHash GetBlockNumber failed", "err", err)
return nil, nil
}
_, txAmount, err := api._blockReader.Body(ctx, tx, blockHash, blockNum)
if err != nil {
return nil, err
}
if txAmount == 0 {
return nil, nil
}
numOfTx := hexutil.Uint(txAmount)
return &numOfTx, nil
}
func (api *APIImpl) blockByNumber(ctx context.Context, number rpc.BlockNumber, tx kv.Tx) (*types.Block, error) {
if number != rpc.PendingBlockNumber {
return api.blockByRPCNumber(number, tx)
}
if block := api.pendingBlock(); block != nil {
return block, nil
}
block, err := api.ethBackend.PendingBlock(ctx)
if err != nil {
return nil, err
}
if block != nil {
return block, nil
}
return api.blockByRPCNumber(number, tx)
}