package commands import ( "bytes" "context" "math/big" "sync" "time" lru "github.com/hashicorp/golang-lru" "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/kvcache" "github.com/ledgerwatch/erigon-lib/kv/kvcfg" libstate "github.com/ledgerwatch/erigon-lib/state" "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/types" ethFilters "github.com/ledgerwatch/erigon/eth/filters" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rpc" ethapi2 "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" "github.com/ledgerwatch/erigon/turbo/rpchelper" "github.com/ledgerwatch/erigon/turbo/services" ) // EthAPI is a collection of functions that are exposed in the type EthAPI interface { // Block related (proposed file: ./eth_blocks.go) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(ctx context.Context, hash rpc.BlockNumberOrHash, fullTx bool) (map[string]interface{}, error) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) // Transaction related (see ./eth_txs.go) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, txIndex hexutil.Uint64) (*RPCTransaction, error) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, txIndex hexutil.Uint) (*RPCTransaction, error) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (hexutil.Bytes, error) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (hexutil.Bytes, error) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) // Receipt related (see ./eth_receipts.go) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) GetLogs(ctx context.Context, crit ethFilters.FilterCriteria) (types.Logs, error) GetBlockReceipts(ctx context.Context, number rpc.BlockNumber) ([]map[string]interface{}, error) // Uncle related (see ./eth_uncles.go) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) GetUncleByBlockHashAndIndex(ctx context.Context, hash common.Hash, index hexutil.Uint) (map[string]interface{}, error) GetUncleCountByBlockNumber(ctx context.Context, number rpc.BlockNumber) (*hexutil.Uint, error) GetUncleCountByBlockHash(ctx context.Context, hash common.Hash) (*hexutil.Uint, error) // Filter related (see ./eth_filters.go) NewPendingTransactionFilter(_ context.Context) (string, error) NewBlockFilter(_ context.Context) (string, error) NewFilter(_ context.Context, crit ethFilters.FilterCriteria) (string, error) UninstallFilter(_ context.Context, index string) (bool, error) GetFilterChanges(_ context.Context, index string) ([]interface{}, error) // Account related (see ./eth_accounts.go) Accounts(ctx context.Context) ([]common.Address, error) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) GetStorageAt(ctx context.Context, address common.Address, index string, blockNrOrHash rpc.BlockNumberOrHash) (string, error) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) // System related (see ./eth_system.go) BlockNumber(ctx context.Context) (hexutil.Uint64, error) Syncing(ctx context.Context) (interface{}, error) ChainId(ctx context.Context) (hexutil.Uint64, error) /* called eth_protocolVersion elsewhere */ ProtocolVersion(_ context.Context) (hexutil.Uint, error) GasPrice(_ context.Context) (*hexutil.Big, error) // Sending related (see ./eth_call.go) Call(ctx context.Context, args ethapi2.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi2.StateOverrides) (hexutil.Bytes, error) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) SendTransaction(_ context.Context, txObject interface{}) (common.Hash, error) Sign(ctx context.Context, _ common.Address, _ hexutil.Bytes) (hexutil.Bytes, error) SignTransaction(_ context.Context, txObject interface{}) (common.Hash, error) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*interface{}, error) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash, optimizeGas *bool) (*accessListResult, error) // Mining related (see ./eth_mining.go) Coinbase(ctx context.Context) (common.Address, error) Hashrate(ctx context.Context) (uint64, error) Mining(ctx context.Context) (bool, error) GetWork(ctx context.Context) ([4]string, error) SubmitWork(ctx context.Context, nonce types.BlockNonce, powHash, digest common.Hash) (bool, error) SubmitHashrate(ctx context.Context, hashRate hexutil.Uint64, id common.Hash) (bool, error) } type BaseAPI struct { stateCache kvcache.Cache // thread-safe blocksLRU *lru.Cache // thread-safe filters *rpchelper.Filters _chainConfig *params.ChainConfig _genesis *types.Block _genesisLock sync.RWMutex _historyV3 *bool _historyV3Lock sync.RWMutex _blockReader services.FullBlockReader _txnReader services.TxnReader _agg *libstate.Aggregator22 _engine consensus.EngineReader evmCallTimeout time.Duration } func NewBaseApi(f *rpchelper.Filters, stateCache kvcache.Cache, blockReader services.FullBlockReader, agg *libstate.Aggregator22, singleNodeMode bool, evmCallTimeout time.Duration, engine consensus.EngineReader) *BaseAPI { blocksLRUSize := 128 // ~32Mb if !singleNodeMode { blocksLRUSize = 512 } blocksLRU, err := lru.New(blocksLRUSize) if err != nil { panic(err) } return &BaseAPI{filters: f, stateCache: stateCache, blocksLRU: blocksLRU, _blockReader: blockReader, _txnReader: blockReader, _agg: agg, evmCallTimeout: evmCallTimeout, _engine: engine} } func (api *BaseAPI) chainConfig(tx kv.Tx) (*params.ChainConfig, error) { cfg, _, err := api.chainConfigWithGenesis(tx) return cfg, err } func (api *BaseAPI) engine() consensus.EngineReader { return api._engine } // nolint:unused func (api *BaseAPI) genesis(tx kv.Tx) (*types.Block, error) { _, genesis, err := api.chainConfigWithGenesis(tx) return genesis, err } func (api *BaseAPI) txnLookup(ctx context.Context, tx kv.Tx, txnHash common.Hash) (uint64, bool, error) { return api._txnReader.TxnLookup(ctx, tx, txnHash) } func (api *BaseAPI) blockByNumberWithSenders(tx kv.Tx, number uint64) (*types.Block, error) { hash, hashErr := rawdb.ReadCanonicalHash(tx, number) if hashErr != nil { return nil, hashErr } return api.blockWithSenders(tx, hash, number) } func (api *BaseAPI) blockByHashWithSenders(tx kv.Tx, hash common.Hash) (*types.Block, error) { if api.blocksLRU != nil { if it, ok := api.blocksLRU.Get(hash); ok && it != nil { return it.(*types.Block), nil } } number := rawdb.ReadHeaderNumber(tx, hash) if number == nil { return nil, nil } return api.blockWithSenders(tx, hash, *number) } func (api *BaseAPI) blockWithSenders(tx kv.Tx, hash common.Hash, number uint64) (*types.Block, error) { if api.blocksLRU != nil { if it, ok := api.blocksLRU.Get(hash); ok && it != nil { return it.(*types.Block), nil } } block, _, err := api._blockReader.BlockWithSenders(context.Background(), tx, hash, number) if err != nil { return nil, err } if block == nil { // don't save nil's to cache return nil, nil } // don't save empty blocks to cache, because in Erigon // if block become non-canonical - we remove it's transactions, but block can become canonical in future if block.Transactions().Len() == 0 { return block, nil } if api.blocksLRU != nil { // calc fields before put to cache for _, txn := range block.Transactions() { txn.Hash() } block.Hash() api.blocksLRU.Add(hash, block) } return block, nil } func (api *BaseAPI) historyV3(tx kv.Tx) bool { api._historyV3Lock.RLock() historyV3 := api._historyV3 api._historyV3Lock.RUnlock() if historyV3 != nil { return *historyV3 } enabled, err := kvcfg.HistoryV3.Enabled(tx) if err != nil { log.Warn("HisoryV2Enabled: read", "err", err) return false } api._historyV3Lock.Lock() api._historyV3 = &enabled api._historyV3Lock.Unlock() return enabled } func (api *BaseAPI) chainConfigWithGenesis(tx kv.Tx) (*params.ChainConfig, *types.Block, error) { api._genesisLock.RLock() cc, genesisBlock := api._chainConfig, api._genesis api._genesisLock.RUnlock() if cc != nil { return cc, genesisBlock, nil } genesisBlock, err := rawdb.ReadBlockByNumber(tx, 0) if err != nil { return nil, nil, err } cc, err = rawdb.ReadChainConfig(tx, genesisBlock.Hash()) if err != nil { return nil, nil, err } if cc != nil && genesisBlock != nil { api._genesisLock.Lock() api._genesis = genesisBlock api._chainConfig = cc api._genesisLock.Unlock() } return cc, genesisBlock, nil } func (api *BaseAPI) pendingBlock() *types.Block { return api.filters.LastPendingBlock() } func (api *BaseAPI) blockByRPCNumber(number rpc.BlockNumber, tx kv.Tx) (*types.Block, error) { n, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters) if err != nil { return nil, err } block, err := api.blockByNumberWithSenders(tx, n) return block, err } func (api *BaseAPI) headerByRPCNumber(number rpc.BlockNumber, tx kv.Tx) (*types.Header, error) { n, h, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters) if err != nil { return nil, err } return api._blockReader.Header(context.Background(), tx, h, n) } // APIImpl is implementation of the EthAPI interface based on remote Db access type APIImpl struct { *BaseAPI ethBackend rpchelper.ApiBackend txPool txpool.TxpoolClient mining txpool.MiningClient gasCache *GasPriceCache db kv.RoDB GasCap uint64 } // NewEthAPI returns APIImpl instance func NewEthAPI(base *BaseAPI, db kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient, gascap uint64) *APIImpl { if gascap == 0 { gascap = uint64(math.MaxUint64 / 2) } return &APIImpl{ BaseAPI: base, db: db, ethBackend: eth, txPool: txPool, mining: mining, gasCache: NewGasPriceCache(), GasCap: gascap, } } // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { BlockHash *common.Hash `json:"blockHash"` BlockNumber *hexutil.Big `json:"blockNumber"` From common.Address `json:"from"` Gas hexutil.Uint64 `json:"gas"` GasPrice *hexutil.Big `json:"gasPrice,omitempty"` Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` Hash common.Hash `json:"hash"` Input hexutil.Bytes `json:"input"` Nonce hexutil.Uint64 `json:"nonce"` To *common.Address `json:"to"` TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` Value *hexutil.Big `json:"value"` Type hexutil.Uint64 `json:"type"` Accesses *types.AccessList `json:"accessList,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"` V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction { // Determine the signer. For replay-protected transactions, use the most permissive // signer, because we assume that signers are backwards-compatible with old // transactions. For non-protected transactions, the homestead signer signer is used // because the return value of ChainId is zero for those transactions. chainId := uint256.NewInt(0) result := &RPCTransaction{ Type: hexutil.Uint64(tx.Type()), Gas: hexutil.Uint64(tx.GetGas()), Hash: tx.Hash(), Input: hexutil.Bytes(tx.GetData()), Nonce: hexutil.Uint64(tx.GetNonce()), To: tx.GetTo(), Value: (*hexutil.Big)(tx.GetValue().ToBig()), } switch t := tx.(type) { case *types.LegacyTx: chainId = types.DeriveChainId(&t.V) // if a legacy transaction has an EIP-155 chain id, include it explicitly, otherwise chain id is not included if !chainId.IsZero() { result.ChainID = (*hexutil.Big)(chainId.ToBig()) } result.GasPrice = (*hexutil.Big)(t.GasPrice.ToBig()) result.V = (*hexutil.Big)(t.V.ToBig()) result.R = (*hexutil.Big)(t.R.ToBig()) result.S = (*hexutil.Big)(t.S.ToBig()) case *types.AccessListTx: chainId.Set(t.ChainID) result.ChainID = (*hexutil.Big)(chainId.ToBig()) result.GasPrice = (*hexutil.Big)(t.GasPrice.ToBig()) result.V = (*hexutil.Big)(t.V.ToBig()) result.R = (*hexutil.Big)(t.R.ToBig()) result.S = (*hexutil.Big)(t.S.ToBig()) result.Accesses = &t.AccessList case *types.DynamicFeeTransaction: chainId.Set(t.ChainID) result.ChainID = (*hexutil.Big)(chainId.ToBig()) result.Tip = (*hexutil.Big)(t.Tip.ToBig()) result.FeeCap = (*hexutil.Big)(t.FeeCap.ToBig()) result.V = (*hexutil.Big)(t.V.ToBig()) result.R = (*hexutil.Big)(t.R.ToBig()) result.S = (*hexutil.Big)(t.S.ToBig()) result.Accesses = &t.AccessList baseFee, overflow := uint256.FromBig(baseFee) if baseFee != nil && !overflow && blockHash != (common.Hash{}) { // price = min(tip + baseFee, gasFeeCap) price := math.Min256(new(uint256.Int).Add(tx.GetTip(), baseFee), tx.GetFeeCap()) result.GasPrice = (*hexutil.Big)(price.ToBig()) } else { result.GasPrice = nil } } signer := types.LatestSignerForChainID(chainId.ToBig()) result.From, _ = tx.Sender(*signer) if blockHash != (common.Hash{}) { result.BlockHash = &blockHash result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } return result } // newRPCBorTransaction returns a Bor transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCBorTransaction(opaqueTx types.Transaction, txHash common.Hash, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int, chainId *big.Int) *RPCTransaction { tx := opaqueTx.(*types.LegacyTx) result := &RPCTransaction{ Type: hexutil.Uint64(tx.Type()), ChainID: (*hexutil.Big)(new(big.Int)), GasPrice: (*hexutil.Big)(tx.GasPrice.ToBig()), Gas: hexutil.Uint64(tx.GetGas()), Hash: txHash, Input: hexutil.Bytes(tx.GetData()), Nonce: hexutil.Uint64(tx.GetNonce()), From: common.Address{}, To: tx.GetTo(), Value: (*hexutil.Big)(tx.GetValue().ToBig()), V: (*hexutil.Big)(big.NewInt(0)), R: (*hexutil.Big)(big.NewInt(0)), S: (*hexutil.Big)(big.NewInt(0)), } if blockHash != (common.Hash{}) { result.ChainID = (*hexutil.Big)(new(big.Int).SetUint64(chainId.Uint64())) result.BlockHash = &blockHash result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } return result } // newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation func newRPCPendingTransaction(tx types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction { var baseFee *big.Int if current != nil { baseFee = misc.CalcBaseFee(config, current) } return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee) } // newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) (hexutil.Bytes, error) { txs := b.Transactions() if index >= uint64(len(txs)) { return nil, nil } var buf bytes.Buffer err := txs[index].MarshalBinary(&buf) return buf.Bytes(), err } type GasPriceCache struct { latestPrice *big.Int latestHash common.Hash mtx sync.Mutex } func NewGasPriceCache() *GasPriceCache { return &GasPriceCache{ latestPrice: big.NewInt(0), latestHash: common.Hash{}, } } func (c *GasPriceCache) GetLatest() (common.Hash, *big.Int) { var hash common.Hash var price *big.Int c.mtx.Lock() hash = c.latestHash price = c.latestPrice c.mtx.Unlock() return hash, price } func (c *GasPriceCache) SetLatest(hash common.Hash, price *big.Int) { c.mtx.Lock() c.latestPrice = price c.latestHash = hash c.mtx.Unlock() }