mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 19:50:36 +00:00
92446bfa66
doing some testing some bugs in the API implementation were found and fixed --------- Co-authored-by: alex.sharov <AskAlexSharov@gmail.com>
472 lines
13 KiB
Go
472 lines
13 KiB
Go
package jsonrpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/RoaringBitmap/roaring"
|
|
"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/core/rawdb"
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
"github.com/ledgerwatch/erigon/eth/filters"
|
|
"github.com/ledgerwatch/erigon/ethdb/cbor"
|
|
"github.com/ledgerwatch/erigon/rpc"
|
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
|
)
|
|
|
|
// GetLogsByHash implements erigon_getLogsByHash. Returns an array of arrays of logs generated by the transactions in the block given by the block's hash.
|
|
func (api *ErigonImpl) GetLogsByHash(ctx context.Context, hash common.Hash) ([][]*types.Log, 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
|
|
}
|
|
|
|
block, err := api.blockByHashWithSenders(tx, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
receipts, err := api.getReceipts(ctx, tx, chainConfig, block, block.Body().SendersFromTxs())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getReceipts error: %w", err)
|
|
}
|
|
|
|
logs := make([][]*types.Log, len(receipts))
|
|
for i, receipt := range receipts {
|
|
logs[i] = receipt.Logs
|
|
}
|
|
return logs, nil
|
|
}
|
|
|
|
// GetLogs implements erigon_getLogs. Returns an array of logs matching a given filter object.
|
|
func (api *ErigonImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) (types.ErigonLogs, error) {
|
|
var begin, end uint64
|
|
erigonLogs := types.ErigonLogs{}
|
|
|
|
tx, beginErr := api.db.BeginRo(ctx)
|
|
if beginErr != nil {
|
|
return erigonLogs, beginErr
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if crit.BlockHash != nil {
|
|
number := rawdb.ReadHeaderNumber(tx, *crit.BlockHash)
|
|
if number == nil {
|
|
return nil, fmt.Errorf("block not found: %x", *crit.BlockHash)
|
|
}
|
|
begin = *number
|
|
end = *number
|
|
} else {
|
|
// Convert the RPC block numbers into internal representations
|
|
latest, err := rpchelper.GetLatestBlockNumber(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
begin = latest
|
|
if crit.FromBlock != nil {
|
|
if crit.FromBlock.Sign() >= 0 {
|
|
begin = crit.FromBlock.Uint64()
|
|
} else if !crit.FromBlock.IsInt64() || crit.FromBlock.Int64() != int64(rpc.LatestBlockNumber) {
|
|
return nil, fmt.Errorf("negative value for FromBlock: %v", crit.FromBlock)
|
|
}
|
|
}
|
|
end = latest
|
|
if crit.ToBlock != nil {
|
|
if crit.ToBlock.Sign() >= 0 {
|
|
end = crit.ToBlock.Uint64()
|
|
} else if !crit.ToBlock.IsInt64() || crit.ToBlock.Int64() != int64(rpc.LatestBlockNumber) {
|
|
return nil, fmt.Errorf("negative value for ToBlock: %v", crit.ToBlock)
|
|
}
|
|
}
|
|
}
|
|
if end < begin {
|
|
return nil, fmt.Errorf("end (%d) < begin (%d)", end, begin)
|
|
}
|
|
if end > roaring.MaxUint32 {
|
|
return nil, fmt.Errorf("end (%d) > MaxUint32", end)
|
|
}
|
|
blockNumbers := bitmapdb.NewBitmap()
|
|
defer bitmapdb.ReturnToPool(blockNumbers)
|
|
if err := applyFilters(blockNumbers, tx, begin, end, crit); err != nil {
|
|
return nil, err
|
|
}
|
|
if blockNumbers.IsEmpty() {
|
|
return erigonLogs, 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 erigonLogs, err
|
|
}
|
|
var logs types.Logs
|
|
if err := cbor.Unmarshal(&logs, bytes.NewReader(v)); err != nil {
|
|
return erigonLogs, 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
|
|
}
|
|
|
|
header, err := api._blockReader.HeaderByNumber(ctx, tx, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if header == nil {
|
|
return nil, fmt.Errorf("block header not found: %d", blockNumber)
|
|
}
|
|
timestamp := header.Time
|
|
|
|
blockHash := header.Hash()
|
|
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 {
|
|
erigonLog := &types.ErigonLog{}
|
|
erigonLog.BlockNumber = blockNumber
|
|
erigonLog.BlockHash = blockHash
|
|
if log.TxIndex == uint(len(body.Transactions)) {
|
|
erigonLog.TxHash = types.ComputeBorTxHash(blockNumber, blockHash)
|
|
} else {
|
|
erigonLog.TxHash = body.Transactions[log.TxIndex].Hash()
|
|
}
|
|
erigonLog.Timestamp = timestamp
|
|
erigonLog.Address = log.Address
|
|
erigonLog.Topics = log.Topics
|
|
erigonLog.Data = log.Data
|
|
erigonLog.Index = log.Index
|
|
erigonLog.Removed = log.Removed
|
|
erigonLog.TxIndex = log.TxIndex
|
|
erigonLogs = append(erigonLogs, erigonLog)
|
|
}
|
|
}
|
|
|
|
return erigonLogs, nil
|
|
}
|
|
|
|
// GetLatestLogs implements erigon_getLatestLogs.
|
|
// Return specific number of logs or block matching a give filter objects by descend.
|
|
// IgnoreTopicsOrder option provide a way to match the logs with addresses and topics without caring the topics's orders
|
|
// When IgnoreTopicsOrde option is true, once the logs have a topic that matched, it will be returned no matter what topic position it is in.
|
|
|
|
// blockCount parameter is for better pagination.
|
|
// `crit` filter is the same filter.
|
|
//
|
|
// Examples:
|
|
// {} or nil matches any topics list
|
|
// {{A}} matches topic A in any positions. Logs with {{B}, {A}} will be matched
|
|
func (api *ErigonImpl) GetLatestLogs(ctx context.Context, crit filters.FilterCriteria, logOptions filters.LogFilterOptions) (types.ErigonLogs, error) {
|
|
if logOptions.LogCount != 0 && logOptions.BlockCount != 0 {
|
|
return nil, fmt.Errorf("logs count & block count are ambigious")
|
|
}
|
|
if logOptions.LogCount == 0 && logOptions.BlockCount == 0 {
|
|
logOptions = filters.DefaultLogFilterOptions()
|
|
}
|
|
erigonLogs := types.ErigonLogs{}
|
|
tx, beginErr := api.db.BeginRo(ctx)
|
|
if beginErr != nil {
|
|
return erigonLogs, beginErr
|
|
}
|
|
defer tx.Rollback()
|
|
var err error
|
|
var begin, end uint64 // Filter range: begin-end(from-to). Two limits are included in the filter
|
|
|
|
if crit.BlockHash != nil {
|
|
header, err := api._blockReader.HeaderByHash(ctx, tx, *crit.BlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if header == nil {
|
|
return nil, fmt.Errorf("block header not found %x", *crit.BlockHash)
|
|
}
|
|
begin = header.Number.Uint64()
|
|
end = header.Number.Uint64()
|
|
} else {
|
|
// Convert the RPC block numbers into internal representations
|
|
latest, err := rpchelper.GetLatestBlockNumber(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
begin = 0
|
|
if crit.FromBlock != nil {
|
|
if crit.FromBlock.Sign() >= 0 {
|
|
begin = crit.FromBlock.Uint64()
|
|
} else if !crit.FromBlock.IsInt64() || crit.FromBlock.Int64() != int64(rpc.LatestBlockNumber) {
|
|
return nil, fmt.Errorf("negative value for FromBlock: %v", crit.FromBlock)
|
|
}
|
|
}
|
|
end = latest
|
|
if crit.ToBlock != nil {
|
|
if crit.ToBlock.Sign() >= 0 {
|
|
end = crit.ToBlock.Uint64()
|
|
} else if !crit.ToBlock.IsInt64() || crit.ToBlock.Int64() != int64(rpc.LatestBlockNumber) {
|
|
return nil, fmt.Errorf("negative value for ToBlock: %v", crit.ToBlock)
|
|
}
|
|
}
|
|
}
|
|
if end < begin {
|
|
return nil, fmt.Errorf("end (%d) < begin (%d)", end, begin)
|
|
}
|
|
|
|
blockNumbers := bitmapdb.NewBitmap()
|
|
defer bitmapdb.ReturnToPool(blockNumbers)
|
|
if err := applyFilters(blockNumbers, tx, begin, end, crit); err != nil {
|
|
return erigonLogs, err
|
|
}
|
|
if blockNumbers.IsEmpty() {
|
|
return erigonLogs, nil
|
|
}
|
|
|
|
addrMap := make(map[common.Address]struct{}, len(crit.Addresses))
|
|
for _, v := range crit.Addresses {
|
|
addrMap[v] = struct{}{}
|
|
}
|
|
topicsMap := make(map[common.Hash]struct{})
|
|
for i := range crit.Topics {
|
|
for j := range crit.Topics[i] {
|
|
topicsMap[crit.Topics[i][j]] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// latest logs that match the filter crit
|
|
iter := blockNumbers.ReverseIterator()
|
|
var logCount, blockCount uint64
|
|
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 erigonLogs, err
|
|
}
|
|
var logs types.Logs
|
|
if err := cbor.Unmarshal(&logs, bytes.NewReader(v)); err != nil {
|
|
return erigonLogs, fmt.Errorf("receipt unmarshal failed: %w", err)
|
|
}
|
|
for _, log := range logs {
|
|
log.Index = logIndex
|
|
logIndex++
|
|
}
|
|
var filtered types.Logs
|
|
if logOptions.IgnoreTopicsOrder {
|
|
filtered = logs.CointainTopics(addrMap, topicsMap)
|
|
} else {
|
|
filtered = logs.Filter(addrMap, crit.Topics)
|
|
}
|
|
if len(filtered) == 0 {
|
|
continue
|
|
}
|
|
txIndex = uint(binary.BigEndian.Uint32(k[8:]))
|
|
for i := range filtered {
|
|
filtered[i].TxIndex = txIndex
|
|
}
|
|
for i := len(filtered) - 1; i >= 0; i-- {
|
|
blockLogs = append(blockLogs, filtered[i])
|
|
logCount++
|
|
}
|
|
if logOptions.LogCount != 0 && logOptions.LogCount <= logCount {
|
|
break
|
|
}
|
|
|
|
}
|
|
blockCount++
|
|
if len(blockLogs) == 0 {
|
|
continue
|
|
}
|
|
|
|
header, err := api._blockReader.HeaderByNumber(ctx, tx, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if header == nil {
|
|
return nil, fmt.Errorf("block header not found: %d", blockNumber)
|
|
}
|
|
timestamp := header.Time
|
|
|
|
blockHash := header.Hash()
|
|
|
|
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 {
|
|
erigonLog := &types.ErigonLog{}
|
|
erigonLog.BlockNumber = blockNumber
|
|
erigonLog.BlockHash = blockHash
|
|
if log.TxIndex == uint(len(body.Transactions)) {
|
|
erigonLog.TxHash = types.ComputeBorTxHash(blockNumber, blockHash)
|
|
} else {
|
|
erigonLog.TxHash = body.Transactions[log.TxIndex].Hash()
|
|
}
|
|
erigonLog.Timestamp = timestamp
|
|
erigonLog.Address = log.Address
|
|
erigonLog.Topics = log.Topics
|
|
erigonLog.Data = log.Data
|
|
erigonLog.Index = log.Index
|
|
erigonLog.TxIndex = log.TxIndex
|
|
erigonLog.Removed = log.Removed
|
|
erigonLogs = append(erigonLogs, erigonLog)
|
|
}
|
|
|
|
if logOptions.LogCount != 0 && logOptions.LogCount <= logCount {
|
|
return erigonLogs, nil
|
|
}
|
|
if logOptions.BlockCount != 0 && logOptions.BlockCount <= blockCount {
|
|
return erigonLogs, nil
|
|
}
|
|
}
|
|
return erigonLogs, nil
|
|
}
|
|
|
|
func (api *ErigonImpl) GetBlockReceiptsByBlockHash(ctx context.Context, cannonicalBlockHash common.Hash) ([]map[string]interface{}, error) {
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
{
|
|
blockNum := rawdb.ReadHeaderNumber(tx, cannonicalBlockHash)
|
|
if blockNum == nil {
|
|
return nil, fmt.Errorf("the hash %s is not cannonical", cannonicalBlockHash)
|
|
}
|
|
isCanonicalHash, err := rawdb.IsCanonicalHash(tx, cannonicalBlockHash, *blockNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isCanonicalHash {
|
|
return nil, fmt.Errorf("the hash %s is not cannonical", cannonicalBlockHash)
|
|
}
|
|
}
|
|
|
|
blockNum, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithHash(cannonicalBlockHash, true), tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := api.blockWithSenders(tx, cannonicalBlockHash, 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
|
|
}
|
|
|
|
// GetLogsByNumber implements erigon_getLogsByHash. Returns all the logs that appear in a block given the block's hash.
|
|
// func (api *ErigonImpl) GetLogsByNumber(ctx context.Context, number rpc.BlockNumber) ([][]*types.Log, error) {
|
|
// tx, err := api.db.Begin(ctx, false)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// defer tx.Rollback()
|
|
|
|
// number := rawdb.ReadHeaderNumber(tx, hash)
|
|
// if number == nil {
|
|
// return nil, fmt.Errorf("block not found: %x", hash)
|
|
// }
|
|
|
|
// receipts, err := getReceipts(ctx, tx, *number, hash)
|
|
// if err != nil {
|
|
// return nil, fmt.Errorf("getReceipts error: %w", err)
|
|
// }
|
|
|
|
// logs := make([][]*types.Log, len(receipts))
|
|
// for i, receipt := range receipts {
|
|
// logs[i] = receipt.Logs
|
|
// }
|
|
// return logs, nil
|
|
// }
|