mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 03:30:37 +00:00
436493350e
1. changes sentinel to use an http-like interface 2. moves hexutil, crypto/blake2b, metrics packages to erigon-lib
866 lines
25 KiB
Go
866 lines
25 KiB
Go
package jsonrpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"github.com/ledgerwatch/erigon-lib/common/hexutil"
|
|
"math/big"
|
|
|
|
"github.com/RoaringBitmap/roaring"
|
|
"github.com/holiman/uint256"
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
"github.com/ledgerwatch/erigon-lib/chain"
|
|
"github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/hexutility"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"github.com/ledgerwatch/erigon-lib/kv/bitmapdb"
|
|
"github.com/ledgerwatch/erigon-lib/kv/iter"
|
|
"github.com/ledgerwatch/erigon-lib/kv/order"
|
|
"github.com/ledgerwatch/erigon-lib/kv/rawdbv3"
|
|
|
|
"github.com/ledgerwatch/erigon/consensus"
|
|
"github.com/ledgerwatch/erigon/consensus/misc"
|
|
"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/core/vm/evmtypes"
|
|
"github.com/ledgerwatch/erigon/eth/filters"
|
|
"github.com/ledgerwatch/erigon/ethdb/cbor"
|
|
"github.com/ledgerwatch/erigon/rpc"
|
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
|
"github.com/ledgerwatch/erigon/turbo/services"
|
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
|
)
|
|
|
|
const PendingBlockNumber int64 = -2
|
|
|
|
func (api *BaseAPI) getReceipts(ctx context.Context, tx kv.Tx, chainConfig *chain.Config, block *types.Block, senders []common.Address) (types.Receipts, error) {
|
|
if cached := rawdb.ReadReceipts(tx, block, senders); cached != nil {
|
|
return cached, nil
|
|
}
|
|
engine := api.engine()
|
|
|
|
_, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, engine, block, chainConfig, api._blockReader, tx, 0, api.historyV3(tx))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
usedGas := new(uint64)
|
|
usedBlobGas := new(uint64)
|
|
gp := new(core.GasPool).AddGas(block.GasLimit()).AddBlobGas(chainConfig.GetMaxBlobGasPerBlock())
|
|
|
|
noopWriter := state.NewNoopWriter()
|
|
|
|
receipts := make(types.Receipts, len(block.Transactions()))
|
|
|
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
|
h, e := api._blockReader.Header(ctx, tx, hash, number)
|
|
if e != nil {
|
|
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
|
|
}
|
|
return h
|
|
}
|
|
header := block.Header()
|
|
for i, txn := range block.Transactions() {
|
|
ibs.SetTxContext(txn.Hash(), block.Hash(), i)
|
|
receipt, _, err := core.ApplyTransaction(chainConfig, core.GetHashFn(header, getHeader), engine, nil, gp, ibs, noopWriter, header, txn, usedGas, usedBlobGas, vm.Config{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
receipt.BlockHash = block.Hash()
|
|
receipts[i] = receipt
|
|
}
|
|
|
|
return receipts, nil
|
|
}
|
|
|
|
// GetLogs implements eth_getLogs. Returns an array of logs matching a given filter object.
|
|
func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) (types.Logs, error) {
|
|
var begin, end uint64
|
|
logs := types.Logs{}
|
|
|
|
tx, beginErr := api.db.BeginRo(ctx)
|
|
if beginErr != nil {
|
|
return logs, beginErr
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if crit.BlockHash != nil {
|
|
block, err := api._blockReader.BlockByHash(ctx, tx, *crit.BlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if block == nil {
|
|
return nil, fmt.Errorf("block not found: %x", *crit.BlockHash)
|
|
}
|
|
|
|
num := block.NumberU64()
|
|
begin = num
|
|
end = num
|
|
} else {
|
|
// Convert the RPC block numbers into internal representations
|
|
latest, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(rpc.LatestExecutedBlockNumber), tx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
begin = latest
|
|
if crit.FromBlock != nil {
|
|
if !getLogsIsValidBlockNumber(crit.FromBlock) {
|
|
return nil, fmt.Errorf("invalid value for FromBlock: %v", crit.FromBlock)
|
|
}
|
|
|
|
fromBlock := crit.FromBlock.Int64()
|
|
if fromBlock > 0 {
|
|
begin = uint64(fromBlock)
|
|
} else {
|
|
blockNum := rpc.BlockNumber(fromBlock)
|
|
begin, _, _, err = rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(blockNum), tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
}
|
|
end = latest
|
|
if crit.ToBlock != nil {
|
|
if !getLogsIsValidBlockNumber(crit.ToBlock) {
|
|
return nil, fmt.Errorf("invalid value for ToBlock: %v", crit.ToBlock)
|
|
}
|
|
|
|
toBlock := crit.ToBlock.Int64()
|
|
if toBlock > 0 {
|
|
end = uint64(toBlock)
|
|
} else {
|
|
blockNum := rpc.BlockNumber(toBlock)
|
|
end, _, _, err = rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(blockNum), tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if end < begin {
|
|
return nil, fmt.Errorf("end (%d) < begin (%d)", end, begin)
|
|
}
|
|
if end > roaring.MaxUint32 {
|
|
latest, err := rpchelper.GetLatestBlockNumber(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if begin > latest {
|
|
return nil, fmt.Errorf("begin (%d) > latest (%d)", begin, latest)
|
|
}
|
|
end = latest
|
|
}
|
|
|
|
if api.historyV3(tx) {
|
|
return api.getLogsV3(ctx, tx.(kv.TemporalTx), begin, end, crit)
|
|
}
|
|
|
|
blockNumbers := bitmapdb.NewBitmap()
|
|
defer bitmapdb.ReturnToPool(blockNumbers)
|
|
if err := applyFilters(blockNumbers, tx, begin, end, crit); err != nil {
|
|
return logs, err
|
|
}
|
|
if blockNumbers.IsEmpty() {
|
|
return logs, nil
|
|
}
|
|
addrMap := make(map[common.Address]struct{}, len(crit.Addresses))
|
|
for _, v := range crit.Addresses {
|
|
addrMap[v] = struct{}{}
|
|
}
|
|
iter := blockNumbers.Iterator()
|
|
for iter.HasNext() {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blockNumber := uint64(iter.Next())
|
|
var logIndex uint
|
|
var txIndex uint
|
|
var blockLogs []*types.Log
|
|
|
|
it, err := tx.Prefix(kv.Log, hexutility.EncodeTs(blockNumber))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for it.HasNext() {
|
|
k, v, err := it.Next()
|
|
if err != nil {
|
|
return logs, err
|
|
}
|
|
|
|
var logs types.Logs
|
|
if err := cbor.Unmarshal(&logs, bytes.NewReader(v)); err != nil {
|
|
return logs, fmt.Errorf("receipt unmarshal failed: %w", err)
|
|
}
|
|
for _, log := range logs {
|
|
log.Index = logIndex
|
|
logIndex++
|
|
}
|
|
filtered := logs.Filter(addrMap, crit.Topics)
|
|
if len(filtered) == 0 {
|
|
continue
|
|
}
|
|
txIndex = uint(binary.BigEndian.Uint32(k[8:]))
|
|
for _, log := range filtered {
|
|
log.TxIndex = txIndex
|
|
}
|
|
blockLogs = append(blockLogs, filtered...)
|
|
}
|
|
if len(blockLogs) == 0 {
|
|
continue
|
|
}
|
|
|
|
blockHash, err := api._blockReader.CanonicalHash(ctx, tx, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body, err := api._blockReader.BodyWithTransactions(ctx, tx, blockHash, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if body == nil {
|
|
return nil, fmt.Errorf("block not found %d", blockNumber)
|
|
}
|
|
for _, log := range blockLogs {
|
|
log.BlockNumber = blockNumber
|
|
log.BlockHash = blockHash
|
|
// bor transactions are at the end of the bodies transactions (added manually but not actually part of the block)
|
|
if log.TxIndex == uint(len(body.Transactions)) {
|
|
log.TxHash = types.ComputeBorTxHash(blockNumber, blockHash)
|
|
} else {
|
|
log.TxHash = body.Transactions[log.TxIndex].Hash()
|
|
}
|
|
}
|
|
logs = append(logs, blockLogs...)
|
|
}
|
|
|
|
return logs, nil
|
|
}
|
|
|
|
// getLogsIsValidBlockNumber checks if block number is valid integer or "latest", "pending", "earliest" block number
|
|
func getLogsIsValidBlockNumber(blockNum *big.Int) bool {
|
|
return blockNum.IsInt64() && blockNum.Int64() >= PendingBlockNumber
|
|
}
|
|
|
|
// The Topic list restricts matches to particular event topics. Each event has a list
|
|
// of topics. Topics matches a prefix of that list. An empty element slice matches any
|
|
// topic. Non-empty elements represent an alternative that matches any of the
|
|
// contained topics.
|
|
//
|
|
// Examples:
|
|
// {} or nil matches any topic list
|
|
// {{A}} matches topic A in first position
|
|
// {{}, {B}} matches any topic in first position AND B in second position
|
|
// {{A}, {B}} matches topic A in first position AND B in second position
|
|
// {{A, B}, {C, D}} matches topic (A OR B) in first position AND (C OR D) in second position
|
|
func getTopicsBitmap(c kv.Tx, topics [][]common.Hash, from, to uint64) (*roaring.Bitmap, error) {
|
|
var result *roaring.Bitmap
|
|
for _, sub := range topics {
|
|
var bitmapForORing *roaring.Bitmap
|
|
for _, topic := range sub {
|
|
m, err := bitmapdb.Get(c, kv.LogTopicIndex, topic[:], uint32(from), uint32(to))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bitmapForORing == nil {
|
|
bitmapForORing = m
|
|
continue
|
|
}
|
|
bitmapForORing.Or(m)
|
|
}
|
|
|
|
if bitmapForORing == nil {
|
|
continue
|
|
}
|
|
if result == nil {
|
|
result = bitmapForORing
|
|
continue
|
|
}
|
|
|
|
result = roaring.And(bitmapForORing, result)
|
|
}
|
|
return result, nil
|
|
}
|
|
func getAddrsBitmap(tx kv.Tx, addrs []common.Address, from, to uint64) (*roaring.Bitmap, error) {
|
|
if len(addrs) == 0 {
|
|
return nil, nil
|
|
}
|
|
rx := make([]*roaring.Bitmap, len(addrs))
|
|
defer func() {
|
|
for _, bm := range rx {
|
|
bitmapdb.ReturnToPool(bm)
|
|
}
|
|
}()
|
|
for idx, addr := range addrs {
|
|
m, err := bitmapdb.Get(tx, kv.LogAddressIndex, addr[:], uint32(from), uint32(to))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rx[idx] = m
|
|
}
|
|
return roaring.FastOr(rx...), nil
|
|
}
|
|
|
|
func applyFilters(out *roaring.Bitmap, tx kv.Tx, begin, end uint64, crit filters.FilterCriteria) error {
|
|
out.AddRange(begin, end+1) // [from,to)
|
|
topicsBitmap, err := getTopicsBitmap(tx, crit.Topics, begin, end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if topicsBitmap != nil {
|
|
out.And(topicsBitmap)
|
|
}
|
|
addrBitmap, err := getAddrsBitmap(tx, crit.Addresses, begin, end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if addrBitmap != nil {
|
|
out.And(addrBitmap)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
|
|
func applyFiltersV3(out *roaring64.Bitmap, tx kv.TemporalTx, begin, end uint64, crit filters.FilterCriteria) error {
|
|
//[from,to)
|
|
var fromTxNum, toTxNum uint64
|
|
var err error
|
|
if begin > 0 {
|
|
fromTxNum, err = rawdbv3.TxNums.Min(tx, begin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
toTxNum, err = rawdbv3.TxNums.Max(tx, end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
toTxNum++
|
|
|
|
out.AddRange(fromTxNum, toTxNum) // [from,to)
|
|
topicsBitmap, err := getTopicsBitmapV3(tx, crit.Topics, fromTxNum, toTxNum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if topicsBitmap != nil {
|
|
out.And(topicsBitmap)
|
|
}
|
|
addrBitmap, err := getAddrsBitmapV3(tx, crit.Addresses, fromTxNum, toTxNum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if addrBitmap != nil {
|
|
out.And(addrBitmap)
|
|
}
|
|
return nil
|
|
}
|
|
*/
|
|
|
|
func applyFiltersV3(tx kv.TemporalTx, begin, end uint64, crit filters.FilterCriteria) (out iter.U64, err error) {
|
|
//[from,to)
|
|
var fromTxNum, toTxNum uint64
|
|
if begin > 0 {
|
|
fromTxNum, err = rawdbv3.TxNums.Min(tx, begin)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
}
|
|
toTxNum, err = rawdbv3.TxNums.Max(tx, end)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
toTxNum++
|
|
|
|
topicsBitmap, err := getTopicsBitmapV3(tx, crit.Topics, fromTxNum, toTxNum)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if topicsBitmap != nil {
|
|
out = topicsBitmap
|
|
}
|
|
addrBitmap, err := getAddrsBitmapV3(tx, crit.Addresses, fromTxNum, toTxNum)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if addrBitmap != nil {
|
|
if out == nil {
|
|
out = addrBitmap
|
|
} else {
|
|
out = iter.Intersect[uint64](out, addrBitmap, -1)
|
|
}
|
|
}
|
|
if out == nil {
|
|
out = iter.Range[uint64](fromTxNum, toTxNum)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (api *APIImpl) getLogsV3(ctx context.Context, tx kv.TemporalTx, begin, end uint64, crit filters.FilterCriteria) ([]*types.Log, error) {
|
|
logs := []*types.Log{}
|
|
|
|
txNumbers, err := applyFiltersV3(tx, begin, end, crit)
|
|
if err != nil {
|
|
return logs, err
|
|
}
|
|
|
|
addrMap := make(map[common.Address]struct{}, len(crit.Addresses))
|
|
for _, v := range crit.Addresses {
|
|
addrMap[v] = struct{}{}
|
|
}
|
|
|
|
chainConfig, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exec := txnExecutor(tx, chainConfig, api.engine(), api._blockReader, nil)
|
|
|
|
var blockHash common.Hash
|
|
var header *types.Header
|
|
|
|
iter := MapTxNum2BlockNum(tx, txNumbers)
|
|
for iter.HasNext() {
|
|
if err = ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
txNum, blockNum, txIndex, isFinalTxn, blockNumChanged, err := iter.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isFinalTxn {
|
|
continue
|
|
}
|
|
|
|
// if block number changed, calculate all related field
|
|
if blockNumChanged {
|
|
if header, err = api._blockReader.HeaderByNumber(ctx, tx, blockNum); err != nil {
|
|
return nil, err
|
|
}
|
|
if header == nil {
|
|
log.Warn("[rpc] header is nil", "blockNum", blockNum)
|
|
continue
|
|
}
|
|
blockHash = header.Hash()
|
|
exec.changeBlock(header)
|
|
}
|
|
|
|
//fmt.Printf("txNum=%d, blockNum=%d, txIndex=%d, maxTxNumInBlock=%d,mixTxNumInBlock=%d\n", txNum, blockNum, txIndex, maxTxNumInBlock, minTxNumInBlock)
|
|
txn, err := api._txnReader.TxnByIdxInBlock(ctx, tx, blockNum, txIndex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if txn == nil {
|
|
continue
|
|
}
|
|
rawLogs, _, err := exec.execTx(txNum, txIndex, txn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//TODO: logIndex within the block! no way to calc it now
|
|
//logIndex := uint(0)
|
|
//for _, log := range rawLogs {
|
|
// log.Index = logIndex
|
|
// logIndex++
|
|
//}
|
|
filtered := types.Logs(rawLogs).Filter(addrMap, crit.Topics)
|
|
for _, log := range filtered {
|
|
log.BlockNumber = blockNum
|
|
log.BlockHash = blockHash
|
|
log.TxHash = txn.Hash()
|
|
}
|
|
logs = append(logs, filtered...)
|
|
}
|
|
|
|
//stats := api._agg.GetAndResetStats()
|
|
//log.Info("Finished", "duration", time.Since(start), "history queries", stats.HistoryQueries, "ef search duration", stats.EfSearchTime)
|
|
return logs, nil
|
|
}
|
|
|
|
type intraBlockExec struct {
|
|
ibs *state.IntraBlockState
|
|
stateReader *state.HistoryReaderV3
|
|
engine consensus.EngineReader
|
|
tx kv.TemporalTx
|
|
br services.FullBlockReader
|
|
chainConfig *chain.Config
|
|
evm *vm.EVM
|
|
|
|
tracer GenericTracer
|
|
|
|
// calculated by .changeBlock()
|
|
blockHash common.Hash
|
|
blockNum uint64
|
|
header *types.Header
|
|
blockCtx *evmtypes.BlockContext
|
|
rules *chain.Rules
|
|
signer *types.Signer
|
|
vmConfig *vm.Config
|
|
}
|
|
|
|
func txnExecutor(tx kv.TemporalTx, chainConfig *chain.Config, engine consensus.EngineReader, br services.FullBlockReader, tracer GenericTracer) *intraBlockExec {
|
|
stateReader := state.NewHistoryReaderV3()
|
|
stateReader.SetTx(tx)
|
|
|
|
ie := &intraBlockExec{
|
|
tx: tx,
|
|
engine: engine,
|
|
chainConfig: chainConfig,
|
|
br: br,
|
|
stateReader: stateReader,
|
|
tracer: tracer,
|
|
evm: vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, chainConfig, vm.Config{}),
|
|
vmConfig: &vm.Config{},
|
|
ibs: state.New(stateReader),
|
|
}
|
|
if tracer != nil {
|
|
ie.vmConfig = &vm.Config{Debug: true, Tracer: tracer}
|
|
}
|
|
return ie
|
|
}
|
|
|
|
func (e *intraBlockExec) changeBlock(header *types.Header) {
|
|
e.blockNum = header.Number.Uint64()
|
|
blockCtx := transactions.NewEVMBlockContext(e.engine, header, true /* requireCanonical */, e.tx, e.br)
|
|
e.blockCtx = &blockCtx
|
|
e.blockHash = header.Hash()
|
|
e.header = header
|
|
e.rules = e.chainConfig.Rules(e.blockNum, header.Time)
|
|
e.signer = types.MakeSigner(e.chainConfig, e.blockNum, header.Time)
|
|
e.vmConfig.SkipAnalysis = core.SkipAnalysis(e.chainConfig, e.blockNum)
|
|
}
|
|
|
|
func (e *intraBlockExec) execTx(txNum uint64, txIndex int, txn types.Transaction) ([]*types.Log, *core.ExecutionResult, error) {
|
|
e.stateReader.SetTxNum(txNum)
|
|
txHash := txn.Hash()
|
|
e.ibs.Reset()
|
|
e.ibs.SetTxContext(txHash, e.blockHash, txIndex)
|
|
gp := new(core.GasPool).AddGas(txn.GetGas()).AddBlobGas(txn.GetBlobGas())
|
|
msg, err := txn.AsMessage(*e.signer, e.header.BaseFee, e.rules)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
e.evm.ResetBetweenBlocks(*e.blockCtx, core.NewEVMTxContext(msg), e.ibs, *e.vmConfig, e.rules)
|
|
res, err := core.ApplyMessage(e.evm, msg, gp, true /* refunds */, false /* gasBailout */)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("%w: blockNum=%d, txNum=%d, %s", err, e.blockNum, txNum, e.ibs.Error())
|
|
}
|
|
if e.vmConfig.Tracer != nil {
|
|
if e.tracer.Found() {
|
|
e.tracer.SetTransaction(txn)
|
|
}
|
|
}
|
|
return e.ibs.GetLogs(txHash), res, nil
|
|
}
|
|
|
|
// The Topic list restricts matches to particular event topics. Each event has a list
|
|
// of topics. Topics matches a prefix of that list. An empty element slice matches any
|
|
// topic. Non-empty elements represent an alternative that matches any of the
|
|
// contained topics.
|
|
//
|
|
// Examples:
|
|
// {} or nil matches any topic list
|
|
// {{A}} matches topic A in first position
|
|
// {{}, {B}} matches any topic in first position AND B in second position
|
|
// {{A}, {B}} matches topic A in first position AND B in second position
|
|
// {{A, B}, {C, D}} matches topic (A OR B) in first position AND (C OR D) in second position
|
|
func getTopicsBitmapV3(tx kv.TemporalTx, topics [][]common.Hash, from, to uint64) (res iter.U64, err error) {
|
|
for _, sub := range topics {
|
|
|
|
var topicsUnion iter.U64
|
|
for _, topic := range sub {
|
|
it, err := tx.IndexRange(kv.LogTopicIdx, topic.Bytes(), int(from), int(to), order.Asc, kv.Unlim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
topicsUnion = iter.Union[uint64](topicsUnion, it, order.Asc, -1)
|
|
}
|
|
|
|
if res == nil {
|
|
res = topicsUnion
|
|
continue
|
|
}
|
|
res = iter.Intersect[uint64](res, topicsUnion, -1)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func getAddrsBitmapV3(tx kv.TemporalTx, addrs []common.Address, from, to uint64) (res iter.U64, err error) {
|
|
for _, addr := range addrs {
|
|
it, err := tx.IndexRange(kv.LogAddrIdx, addr[:], int(from), int(to), true, kv.Unlim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = iter.Union[uint64](res, it, order.Asc, -1)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// GetTransactionReceipt implements eth_getTransactionReceipt. Returns the receipt of a transaction given the transaction's hash.
|
|
func (api *APIImpl) GetTransactionReceipt(ctx context.Context, txnHash common.Hash) (map[string]interface{}, error) {
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var blockNum uint64
|
|
var ok bool
|
|
|
|
blockNum, ok, err = api.txnLookup(tx, txnHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cc, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Private API returns 0 if transaction is not found.
|
|
if blockNum == 0 && cc.Bor != nil {
|
|
blockNum, ok, err = api._blockReader.EventLookup(ctx, tx, txnHash)
|
|
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 // not error, see https://github.com/ledgerwatch/erigon/issues/1645
|
|
}
|
|
|
|
var txnIndex uint64
|
|
var txn types.Transaction
|
|
for idx, transaction := range block.Transactions() {
|
|
if transaction.Hash() == txnHash {
|
|
txn = transaction
|
|
txnIndex = uint64(idx)
|
|
break
|
|
}
|
|
}
|
|
|
|
var borTx types.Transaction
|
|
if txn == nil && cc.Bor != nil {
|
|
borTx = rawdb.ReadBorTransactionForBlock(tx, blockNum)
|
|
if borTx == nil {
|
|
borTx = types.NewBorTransaction()
|
|
}
|
|
}
|
|
receipts, err := api.getReceipts(ctx, tx, cc, block, block.Body().SendersFromTxs())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getReceipts error: %w", err)
|
|
}
|
|
|
|
if txn == nil && cc.Bor != nil {
|
|
borReceipt, err := rawdb.ReadBorReceipt(tx, block.Hash(), blockNum, receipts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if borReceipt == nil {
|
|
return nil, nil
|
|
}
|
|
return marshalReceipt(borReceipt, borTx, cc, block.HeaderNoCopy(), txnHash, false), nil
|
|
}
|
|
|
|
if len(receipts) <= int(txnIndex) {
|
|
return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txnIndex), blockNum)
|
|
}
|
|
|
|
return marshalReceipt(receipts[txnIndex], block.Transactions()[txnIndex], cc, block.HeaderNoCopy(), txnHash, true), nil
|
|
}
|
|
|
|
// GetBlockReceipts - receipts for individual block
|
|
func (api *APIImpl) GetBlockReceipts(ctx context.Context, numberOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
blockNum, blockHash, _, err := rpchelper.GetBlockNumber(numberOrHash, tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := api.blockWithSenders(tx, blockHash, blockNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
chainConfig, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
receipts, err := api.getReceipts(ctx, tx, chainConfig, block, block.Body().SendersFromTxs())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getReceipts error: %w", err)
|
|
}
|
|
result := make([]map[string]interface{}, 0, len(receipts))
|
|
for _, receipt := range receipts {
|
|
txn := block.Transactions()[receipt.TransactionIndex]
|
|
result = append(result, marshalReceipt(receipt, txn, chainConfig, block.HeaderNoCopy(), txn.Hash(), true))
|
|
}
|
|
|
|
if chainConfig.Bor != nil {
|
|
borTx := rawdb.ReadBorTransactionForBlock(tx, blockNum)
|
|
if borTx != nil {
|
|
borReceipt, err := rawdb.ReadBorReceipt(tx, block.Hash(), blockNum, receipts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if borReceipt != nil {
|
|
result = append(result, marshalReceipt(borReceipt, borTx, chainConfig, block.HeaderNoCopy(), borReceipt.TxHash, false))
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func marshalReceipt(receipt *types.Receipt, txn types.Transaction, chainConfig *chain.Config, header *types.Header, txnHash common.Hash, signed bool) map[string]interface{} {
|
|
var chainId *big.Int
|
|
switch t := txn.(type) {
|
|
case *types.LegacyTx:
|
|
if t.Protected() {
|
|
chainId = types.DeriveChainId(&t.V).ToBig()
|
|
}
|
|
default:
|
|
chainId = txn.GetChainID().ToBig()
|
|
}
|
|
|
|
var from common.Address
|
|
if signed {
|
|
signer := types.LatestSignerForChainID(chainId)
|
|
from, _ = txn.Sender(*signer)
|
|
}
|
|
|
|
fields := map[string]interface{}{
|
|
"blockHash": receipt.BlockHash,
|
|
"blockNumber": hexutil.Uint64(receipt.BlockNumber.Uint64()),
|
|
"transactionHash": txnHash,
|
|
"transactionIndex": hexutil.Uint64(receipt.TransactionIndex),
|
|
"from": from,
|
|
"to": txn.GetTo(),
|
|
"type": hexutil.Uint(txn.Type()),
|
|
"gasUsed": hexutil.Uint64(receipt.GasUsed),
|
|
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
|
|
"contractAddress": nil,
|
|
"logs": receipt.Logs,
|
|
"logsBloom": types.CreateBloom(types.Receipts{receipt}),
|
|
}
|
|
|
|
if !chainConfig.IsLondon(header.Number.Uint64()) {
|
|
fields["effectiveGasPrice"] = hexutil.Uint64(txn.GetPrice().Uint64())
|
|
} else {
|
|
baseFee, _ := uint256.FromBig(header.BaseFee)
|
|
gasPrice := new(big.Int).Add(header.BaseFee, txn.GetEffectiveGasTip(baseFee).ToBig())
|
|
fields["effectiveGasPrice"] = hexutil.Uint64(gasPrice.Uint64())
|
|
}
|
|
// Assign receipt status.
|
|
fields["status"] = hexutil.Uint64(receipt.Status)
|
|
if receipt.Logs == nil {
|
|
fields["logs"] = [][]*types.Log{}
|
|
}
|
|
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
|
if receipt.ContractAddress != (common.Address{}) {
|
|
fields["contractAddress"] = receipt.ContractAddress
|
|
}
|
|
// Set derived blob related fields
|
|
numBlobs := len(txn.GetBlobHashes())
|
|
if numBlobs > 0 {
|
|
if header.ExcessBlobGas == nil {
|
|
log.Warn("excess blob gas not set when trying to marshal blob tx")
|
|
} else {
|
|
blobGasPrice, err := misc.GetBlobGasPrice(chainConfig, *header.ExcessBlobGas)
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
}
|
|
fields["blobGasPrice"] = blobGasPrice
|
|
fields["blobGasUsed"] = hexutil.Uint64(misc.GetBlobGasUsed(numBlobs))
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
// MapTxNum2BlockNumIter - enrich iterator by TxNumbers, adding more info:
|
|
// - blockNum
|
|
// - txIndex in block: -1 means first system tx
|
|
// - isFinalTxn: last system-txn. BlockRewards and similar things - are attribute to this virtual txn.
|
|
// - blockNumChanged: means this and previous txNum belongs to different blockNumbers
|
|
//
|
|
// Expect: `it` to return sorted txNums, then blockNum will not change until `it.Next() < maxTxNumInBlock`
|
|
//
|
|
// it allow certain optimizations.
|
|
type MapTxNum2BlockNumIter struct {
|
|
it iter.U64
|
|
tx kv.Tx
|
|
orderAscend bool
|
|
|
|
blockNum uint64
|
|
minTxNumInBlock, maxTxNumInBlock uint64
|
|
}
|
|
|
|
func MapTxNum2BlockNum(tx kv.Tx, it iter.U64) *MapTxNum2BlockNumIter {
|
|
return &MapTxNum2BlockNumIter{tx: tx, it: it, orderAscend: true}
|
|
}
|
|
func MapDescendTxNum2BlockNum(tx kv.Tx, it iter.U64) *MapTxNum2BlockNumIter {
|
|
return &MapTxNum2BlockNumIter{tx: tx, it: it, orderAscend: false}
|
|
}
|
|
func (i *MapTxNum2BlockNumIter) HasNext() bool { return i.it.HasNext() }
|
|
func (i *MapTxNum2BlockNumIter) Next() (txNum, blockNum uint64, txIndex int, isFinalTxn, blockNumChanged bool, err error) {
|
|
txNum, err = i.it.Next()
|
|
if err != nil {
|
|
return txNum, blockNum, txIndex, isFinalTxn, blockNumChanged, err
|
|
}
|
|
|
|
// txNums are sorted, it means blockNum will not change until `txNum < maxTxNumInBlock`
|
|
if i.maxTxNumInBlock == 0 || (i.orderAscend && txNum > i.maxTxNumInBlock) || (!i.orderAscend && txNum < i.minTxNumInBlock) {
|
|
blockNumChanged = true
|
|
|
|
var ok bool
|
|
ok, i.blockNum, err = rawdbv3.TxNums.FindBlockNum(i.tx, txNum)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !ok {
|
|
return txNum, i.blockNum, txIndex, isFinalTxn, blockNumChanged, fmt.Errorf("can't find blockNumber by txnID=%d", txNum)
|
|
}
|
|
}
|
|
blockNum = i.blockNum
|
|
|
|
// if block number changed, calculate all related field
|
|
if blockNumChanged {
|
|
i.minTxNumInBlock, err = rawdbv3.TxNums.Min(i.tx, blockNum)
|
|
if err != nil {
|
|
return
|
|
}
|
|
i.maxTxNumInBlock, err = rawdbv3.TxNums.Max(i.tx, blockNum)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
txIndex = int(txNum) - int(i.minTxNumInBlock) - 1
|
|
isFinalTxn = txNum == i.maxTxNumInBlock
|
|
return
|
|
}
|