otterscan/cmd/otter/commands/otterscan_transaction_by_se...

164 lines
3.8 KiB
Go

package commands
import (
"bytes"
"context"
"sort"
"github.com/RoaringBitmap/roaring/roaring64"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/changeset"
"github.com/ledgerwatch/erigon/core/rawdb"
"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()
accHistoryC, err := tx.Cursor(kv.AccountsHistory)
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 := changeset.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)
var acc accounts.Account
for {
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 := rawdb.ReadCanonicalHash(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
}