mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 03:30:37 +00:00
298 lines
7.5 KiB
Go
298 lines
7.5 KiB
Go
package jsonrpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/RoaringBitmap/roaring/roaring64"
|
|
"github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/cmp"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"github.com/ledgerwatch/erigon-lib/kv/order"
|
|
"github.com/ledgerwatch/erigon-lib/kv/rawdbv3"
|
|
"github.com/ledgerwatch/erigon-lib/kv/temporal/historyv2"
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
|
)
|
|
|
|
func (api *OtterscanAPIImpl) GetTransactionBySenderAndNonce(ctx context.Context, addr common.Address, nonce uint64) (*common.Hash, error) {
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var acc accounts.Account
|
|
if api.historyV3(tx) {
|
|
ttx := tx.(kv.TemporalTx)
|
|
it, err := ttx.IndexRange(kv.AccountsHistoryIdx, addr[:], -1, -1, order.Asc, kv.Unlim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var prevTxnID, nextTxnID uint64
|
|
for i := 0; it.HasNext(); i++ {
|
|
txnID, err := it.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if i%4096 != 0 { // probe history periodically, not on every change
|
|
nextTxnID = txnID
|
|
continue
|
|
}
|
|
|
|
v, ok, err := ttx.HistoryGet(kv.AccountsHistory, addr[:], txnID)
|
|
if err != nil {
|
|
log.Error("Unexpected error, couldn't find changeset", "txNum", i, "addr", addr)
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
err = fmt.Errorf("couldn't find history txnID=%v addr=%v", txnID, addr)
|
|
log.Error("[rpc] Unexpected error", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(v) == 0 { // creation, but maybe not our Incarnation
|
|
prevTxnID = txnID
|
|
continue
|
|
}
|
|
|
|
if err := accounts.DeserialiseV3(&acc, v); err != nil {
|
|
return nil, err
|
|
}
|
|
// Desired nonce was found in this chunk
|
|
if acc.Nonce > nonce {
|
|
break
|
|
}
|
|
prevTxnID = txnID
|
|
}
|
|
|
|
// The sort.Search function finds the first block where the incarnation has
|
|
// changed to the desired one, so we get the previous block from the bitmap;
|
|
// however if the creationTxnID block is already the first one from the bitmap, it means
|
|
// the block we want is the max block from the previous shard.
|
|
var creationTxnID uint64
|
|
var searchErr error
|
|
|
|
if nextTxnID == 0 {
|
|
nextTxnID = prevTxnID + 1
|
|
}
|
|
// Binary search in [prevTxnID, nextTxnID] range; get first block where desired incarnation appears
|
|
// can be replaced by full-scan over ttx.HistoryRange([prevTxnID, nextTxnID])?
|
|
idx := sort.Search(int(nextTxnID-prevTxnID), func(i int) bool {
|
|
txnID := uint64(i) + prevTxnID
|
|
v, ok, err := ttx.HistoryGet(kv.AccountsHistory, addr[:], txnID)
|
|
if err != nil {
|
|
log.Error("[rpc] Unexpected error, couldn't find changeset", "txNum", i, "addr", addr)
|
|
panic(err)
|
|
}
|
|
if !ok {
|
|
return false
|
|
}
|
|
if len(v) == 0 {
|
|
creationTxnID = cmp.Max(creationTxnID, txnID)
|
|
return false
|
|
}
|
|
|
|
if err := accounts.DeserialiseV3(&acc, v); err != nil {
|
|
searchErr = err
|
|
return false
|
|
}
|
|
|
|
// Since the state contains the nonce BEFORE the block changes, we look for
|
|
// the block when the nonce changed to be > the desired once, which means the
|
|
// previous history block contains the actual change; it may contain multiple
|
|
// nonce changes.
|
|
if acc.Nonce <= nonce {
|
|
creationTxnID = cmp.Max(creationTxnID, txnID)
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
if searchErr != nil {
|
|
return nil, searchErr
|
|
}
|
|
if creationTxnID == 0 {
|
|
return nil, fmt.Errorf("binary search between %d-%d doesn't find anything", nextTxnID, prevTxnID)
|
|
}
|
|
ok, bn, err := rawdbv3.TxNums.FindBlockNum(tx, creationTxnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, fmt.Errorf("block not found by txnID=%d", creationTxnID)
|
|
}
|
|
minTxNum, err := rawdbv3.TxNums.Min(tx, bn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
txIndex := int(creationTxnID) - int(minTxNum) - 1 /* system-tx */
|
|
if txIndex == -1 {
|
|
txIndex = (idx + int(prevTxnID)) - int(minTxNum) - 1
|
|
}
|
|
txn, err := api._txnReader.TxnByIdxInBlock(ctx, ttx, bn, txIndex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if txn == nil {
|
|
log.Warn("[rpc] tx is nil", "blockNum", bn, "txIndex", txIndex)
|
|
return nil, nil
|
|
}
|
|
found := txn.GetNonce() == nonce
|
|
if !found {
|
|
return nil, nil
|
|
}
|
|
txHash := txn.Hash()
|
|
return &txHash, nil
|
|
}
|
|
|
|
accHistoryC, err := tx.Cursor(kv.E2AccountsHistory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer accHistoryC.Close()
|
|
|
|
accChangesC, err := tx.CursorDupSort(kv.AccountChangeSet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer accChangesC.Close()
|
|
|
|
// Locate the chunk where the nonce happens
|
|
acs := historyv2.Mapper[kv.AccountChangeSet]
|
|
k, v, err := accHistoryC.Seek(acs.IndexChunkKey(addr.Bytes(), 0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bitmap := roaring64.New()
|
|
maxBlPrevChunk := uint64(0)
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
if k == nil || !bytes.HasPrefix(k, addr.Bytes()) {
|
|
// Check plain state
|
|
data, err := tx.GetOne(kv.PlainState, addr.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := acc.DecodeForStorage(data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Nonce changed in plain state, so it means the last block of last chunk
|
|
// contains the actual nonce change
|
|
if acc.Nonce > nonce {
|
|
break
|
|
}
|
|
// Not found; asked for nonce still not used
|
|
return nil, nil
|
|
}
|
|
|
|
// Inspect block changeset
|
|
if _, err := bitmap.ReadFrom(bytes.NewReader(v)); err != nil {
|
|
return nil, err
|
|
}
|
|
maxBl := bitmap.Maximum()
|
|
data, err := acs.Find(accChangesC, maxBl, addr.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := acc.DecodeForStorage(data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Desired nonce was found in this chunk
|
|
if acc.Nonce > nonce {
|
|
break
|
|
}
|
|
|
|
maxBlPrevChunk = maxBl
|
|
k, v, err = accHistoryC.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Locate the exact block inside chunk when the nonce changed
|
|
blocks := bitmap.ToArray()
|
|
var errSearch error = nil
|
|
idx := sort.Search(len(blocks), func(i int) bool {
|
|
if errSearch != nil {
|
|
return false
|
|
}
|
|
|
|
// Locate the block changeset
|
|
data, err := acs.Find(accChangesC, blocks[i], addr.Bytes())
|
|
if err != nil {
|
|
errSearch = err
|
|
return false
|
|
}
|
|
|
|
if err := acc.DecodeForStorage(data); err != nil {
|
|
errSearch = err
|
|
return false
|
|
}
|
|
|
|
// Since the state contains the nonce BEFORE the block changes, we look for
|
|
// the block when the nonce changed to be > the desired once, which means the
|
|
// previous history block contains the actual change; it may contain multiple
|
|
// nonce changes.
|
|
return acc.Nonce > nonce
|
|
})
|
|
if errSearch != nil {
|
|
return nil, errSearch
|
|
}
|
|
|
|
// Since the changeset contains the state BEFORE the change, we inspect
|
|
// the block before the one we found; if it is the first block inside the chunk,
|
|
// we use the last block from prev chunk
|
|
nonceBlock := maxBlPrevChunk
|
|
if idx > 0 {
|
|
nonceBlock = blocks[idx-1]
|
|
}
|
|
found, txHash, err := api.findNonce(ctx, tx, addr, nonce, nonceBlock)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !found {
|
|
return nil, nil
|
|
}
|
|
|
|
return &txHash, nil
|
|
}
|
|
|
|
func (api *OtterscanAPIImpl) findNonce(ctx context.Context, tx kv.Tx, addr common.Address, nonce uint64, blockNum uint64) (bool, common.Hash, error) {
|
|
hash, err := api._blockReader.CanonicalHash(ctx, tx, blockNum)
|
|
if err != nil {
|
|
return false, common.Hash{}, err
|
|
}
|
|
block, senders, err := api._blockReader.BlockWithSenders(ctx, tx, hash, blockNum)
|
|
if err != nil {
|
|
return false, common.Hash{}, err
|
|
}
|
|
|
|
txs := block.Transactions()
|
|
for i, s := range senders {
|
|
if s != addr {
|
|
continue
|
|
}
|
|
|
|
t := txs[i]
|
|
if t.GetNonce() == nonce {
|
|
return true, t.Hash(), nil
|
|
}
|
|
}
|
|
|
|
return false, common.Hash{}, nil
|
|
}
|