package ethapi import ( "context" "errors" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "math/big" "time" ) // PublicBlockChainAPI provides an API to access the Ethereum blockchain. // It offers only methods that operate on public data that is freely available to anyone. type PublicBlockChainAPI struct { b Backend } // NewPublicBlockChainAPI creates a new Ethereum blockchain API. func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI { return &PublicBlockChainAPI{b} } // Call executes the given transaction on the state for the given block number. // // Additionally, the caller can specify a batch of contract for fields overriding. // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } // If the result contains a revert reason, try to unpack and return it. if len(result.Revert()) > 0 { return nil, newRevertError(result) } return result.Return(), result.Err } // ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. func (api *PublicBlockChainAPI) ChainId() (*hexutil.Big, error) { // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config if config := api.b.ChainConfig(); config.IsEIP155(api.b.CurrentBlock().Number()) { return (*hexutil.Big)(config.ChainID), nil } return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") } // BlockNumber returns the block number of the chain head. func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 { header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available return hexutil.Uint64(header.Number.Uint64()) } // GetBalance returns the amount of wei for the given address in the state of the // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // block numbers are also allowed. func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) { state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } return (*hexutil.Big)(state.GetBalance(address)), state.Error() } // EstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } func (s *PublicBlockChainAPI) ping(ctx context.Context) string { currentTime := time.Now() timeString := currentTime.Format("2006-01-02 15:04:05") return timeString } // GetProof returns the Merkle-proof for a given account and optionally some storage keys. func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } storageTrie := state.StorageTrie(address) storageHash := types.EmptyRootHash codeHash := state.GetCodeHash(address) storageProof := make([]StorageResult, len(storageKeys)) // if we have a storageTrie, (which means the account exists), we can update the storagehash if storageTrie != nil { storageHash = storageTrie.Hash() } else { // no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray. codeHash = crypto.Keccak256Hash(nil) } // create the proof for the storageKeys for i, key := range storageKeys { if storageTrie != nil { proof, storageError := state.GetStorageProof(address, common.HexToHash(key)) if storageError != nil { return nil, storageError } storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), toHexSlice(proof)} } else { storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}} } } // create the accountProof accountProof, proofErr := state.GetProof(address) if proofErr != nil { return nil, proofErr } return &AccountResult{ Address: address, AccountProof: toHexSlice(accountProof), Balance: (*hexutil.Big)(state.GetBalance(address)), CodeHash: codeHash, Nonce: hexutil.Uint64(state.GetNonce(address)), StorageHash: storageHash, StorageProof: storageProof, }, state.Error() } // GetHeaderByNumber returns the requested canonical block header. // * When blockNr is -1 the chain head is returned. // * When blockNr is -2 the pending chain head is returned. func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { response := s.rpcMarshalHeader(ctx, header) if number == rpc.PendingBlockNumber { // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { response[field] = nil } } return response, err } return nil, err } // GetHeaderByHash returns the requested header by hash. func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} { header, _ := s.b.HeaderByHash(ctx, hash) if header != nil { return s.rpcMarshalHeader(ctx, header) } return nil } // GetBlockByNumber returns the requested canonical block. // - When blockNr is -1 the chain head is returned. // - When blockNr is -2 the pending chain head is returned. // - When fullTx is true all transactions in the block are returned, otherwise // only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) 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 } return nil, err } // GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full // detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByHash(ctx, hash) if block != nil { return s.rpcMarshalBlock(ctx, block, true, fullTx) } return nil, err } func (s *PublicBlockChainAPI) Health() bool { if rpc.RpcServingTimer != nil { return rpc.RpcServingTimer.Percentile(0.75) < float64(UnHealthyTimeout) } return true } // GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true // all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, blockNr) if block != nil { uncles := block.Uncles() if index >= hexutil.Uint(len(uncles)) { log.Debug("Requested uncle not found", "number", blockNr, "hash", block.Hash(), "index", index) return nil, nil } block = types.NewBlockWithHeader(uncles[index]) return s.rpcMarshalBlock(ctx, block, false, false) } return nil, err } // GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true // all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) { block, err := s.b.BlockByHash(ctx, blockHash) if block != nil { uncles := block.Uncles() if index >= hexutil.Uint(len(uncles)) { log.Debug("Requested uncle not found", "number", block.Number(), "hash", blockHash, "index", index) return nil, nil } block = types.NewBlockWithHeader(uncles[index]) return s.rpcMarshalBlock(ctx, block, false, false) } return nil, err } // GetUncleCountByBlockNumber returns number of uncles in the block for the given block number func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint { if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { n := hexutil.Uint(len(block.Uncles())) return &n } return nil } // GetUncleCountByBlockHash returns number of uncles in the block for the given block hash func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint { if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil { n := hexutil.Uint(len(block.Uncles())) return &n } return nil } // GetCode returns the code stored at the given address in the state for the given block number. func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } code := state.GetCode(address) return code, state.Error() } // GetStorageAt returns the storage from the state at the given address, key and // block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block // numbers are also allowed. func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } res := state.GetState(address, common.HexToHash(key)) return res[:], state.Error() } // GetDiffAccounts returns changed accounts in a specific block number. func (s *PublicBlockChainAPI) GetDiffAccounts(ctx context.Context, blockNr rpc.BlockNumber) ([]common.Address, error) { if s.b.Chain() == nil { return nil, fmt.Errorf("blockchain not support get diff accounts") } header, err := s.b.HeaderByNumber(ctx, blockNr) if err != nil { return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr, err) } accounts, err := s.b.Chain().GetDiffAccounts(header.Hash()) if err == nil || !errors.Is(err, core.ErrDiffLayerNotFound) { return accounts, err } // Replay the block when diff layer not found, it is very slow. block, err := s.b.BlockByNumber(ctx, blockNr) if err != nil { return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr, err) } _, statedb, err := s.replay(ctx, block, nil) if err != nil { return nil, err } return statedb.GetDirtyAccounts(), nil } func (s *PublicBlockChainAPI) needToReplay(ctx context.Context, block *types.Block, accounts []common.Address) (bool, error) { receipts, err := s.b.GetReceipts(ctx, block.Hash()) if err != nil || len(receipts) != len(block.Transactions()) { return false, fmt.Errorf("receipt incorrect for block number (%d): %v", block.NumberU64(), err) } accountSet := make(map[common.Address]struct{}, len(accounts)) for _, account := range accounts { accountSet[account] = struct{}{} } spendValueMap := make(map[common.Address]int64, len(accounts)) receiveValueMap := make(map[common.Address]int64, len(accounts)) signer := types.MakeSigner(s.b.ChainConfig(), block.Number()) for index, tx := range block.Transactions() { receipt := receipts[index] from, err := types.Sender(signer, tx) if err != nil { return false, fmt.Errorf("get sender for tx failed: %v", err) } if _, exists := accountSet[from]; exists { spendValueMap[from] += int64(receipt.GasUsed) * tx.GasPrice().Int64() if receipt.Status == types.ReceiptStatusSuccessful { spendValueMap[from] += tx.Value().Int64() } } if tx.To() == nil { continue } if _, exists := accountSet[*tx.To()]; exists && receipt.Status == types.ReceiptStatusSuccessful { receiveValueMap[*tx.To()] += tx.Value().Int64() } } parent, err := s.b.BlockByHash(ctx, block.ParentHash()) if err != nil { return false, fmt.Errorf("block not found for block number (%d): %v", block.NumberU64()-1, err) } parentState, err := s.b.Chain().StateAt(parent.Root()) if err != nil { return false, fmt.Errorf("statedb not found for block number (%d): %v", block.NumberU64()-1, err) } currentState, err := s.b.Chain().StateAt(block.Root()) if err != nil { return false, fmt.Errorf("statedb not found for block number (%d): %v", block.NumberU64(), err) } for _, account := range accounts { parentBalance := parentState.GetBalance(account).Int64() currentBalance := currentState.GetBalance(account).Int64() if receiveValueMap[account]-spendValueMap[account] != currentBalance-parentBalance { return true, nil } } return false, nil } func (s *PublicBlockChainAPI) replay(ctx context.Context, block *types.Block, accounts []common.Address) (*types.DiffAccountsInBlock, *state.StateDB, error) { result := &types.DiffAccountsInBlock{ Number: block.NumberU64(), BlockHash: block.Hash(), Transactions: make([]types.DiffAccountsInTx, 0), } parent, err := s.b.BlockByHash(ctx, block.ParentHash()) if err != nil { return nil, nil, fmt.Errorf("block not found for block number (%d): %v", block.NumberU64()-1, err) } statedb, err := s.b.Chain().StateAt(parent.Root()) if err != nil { return nil, nil, fmt.Errorf("state not found for block number (%d): %v", block.NumberU64()-1, err) } accountSet := make(map[common.Address]struct{}, len(accounts)) for _, account := range accounts { accountSet[account] = struct{}{} } // Recompute transactions. signer := types.MakeSigner(s.b.ChainConfig(), block.Number()) for _, tx := range block.Transactions() { // Skip data empty tx and to is one of the interested accounts tx. skip := false if len(tx.Data()) == 0 { skip = true } else if to := tx.To(); to != nil { if _, exists := accountSet[*to]; exists { skip = true } } diffTx := types.DiffAccountsInTx{ TxHash: tx.Hash(), Accounts: make(map[common.Address]*big.Int, len(accounts)), } if !skip { // Record account balance for _, account := range accounts { diffTx.Accounts[account] = statedb.GetBalance(account) } } // Apply transaction msg, _ := tx.AsMessage(signer) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), s.b.Chain(), nil) vmenv := vm.NewEVM(context, txContext, statedb, s.b.ChainConfig(), vm.Config{}) if posa, ok := s.b.Engine().(consensus.PoSA); ok { if isSystem, _ := posa.IsSystemTransaction(tx, block.Header()); isSystem { balance := statedb.GetBalance(consensus.SystemAddress) if balance.Cmp(common.Big0) > 0 { statedb.SetBalance(consensus.SystemAddress, big.NewInt(0)) statedb.AddBalance(block.Header().Coinbase, balance) } } } if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) if !skip { // Compute account balance diff. for _, account := range accounts { diffTx.Accounts[account] = new(big.Int).Sub(statedb.GetBalance(account), diffTx.Accounts[account]) if diffTx.Accounts[account].Cmp(big.NewInt(0)) == 0 { delete(diffTx.Accounts, account) } } if len(diffTx.Accounts) != 0 { result.Transactions = append(result.Transactions, diffTx) } } } return result, statedb, nil } // GetDiffAccountsWithScope returns detailed changes of some interested accounts in a specific block number. func (s *PublicBlockChainAPI) GetDiffAccountsWithScope(ctx context.Context, blockNr rpc.BlockNumber, accounts []common.Address) (*types.DiffAccountsInBlock, error) { if s.b.Chain() == nil { return nil, fmt.Errorf("blockchain not support get diff accounts") } block, err := s.b.BlockByNumber(ctx, blockNr) if err != nil { return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr, err) } needReplay, err := s.needToReplay(ctx, block, accounts) if err != nil { return nil, err } if !needReplay { return &types.DiffAccountsInBlock{ Number: uint64(blockNr), BlockHash: block.Hash(), Transactions: make([]types.DiffAccountsInTx, 0), }, nil } result, _, err := s.replay(ctx, block, accounts) return result, err } // rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires // a `PublicBlockchainAPI`. func (s *PublicBlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} { fields := RPCMarshalHeader(header) fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash())) return fields } // rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires // a `PublicBlockchainAPI`. func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { fields, err := RPCMarshalBlock(b, inclTx, fullTx) if err != nil { return nil, err } if inclTx { fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash())) } return fields, err } // CreateAccessList creates a EIP-2930 type AccessList for the given transaction. // Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state. func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args) if err != nil { return nil, err } result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)} if vmerr != nil { result.Error = vmerr.Error() } return result, nil }