mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
ethclient/lightclient: split up chain.go and reordered functions
This commit is contained in:
parent
02b67cd9e7
commit
d323dac883
5 changed files with 514 additions and 483 deletions
|
|
@ -21,241 +21,15 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||
btypes "github.com/ethereum/go-ethereum/beacon/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
const recentCanonicalLength = 256
|
||||
|
||||
type canonicalChainFields struct {
|
||||
chainLock sync.Mutex
|
||||
head, finality *btypes.ExecutionHeader
|
||||
recent map[uint64]common.Hash // nil while head == nil
|
||||
recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head
|
||||
tailFetchCh chan struct{}
|
||||
finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes
|
||||
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized
|
||||
}
|
||||
|
||||
func (c *Client) initCanonicalChain() {
|
||||
c.finalized = lru.NewCache[uint64, common.Hash](10000)
|
||||
c.requests = newRequestMap[uint64, common.Hash](nil)
|
||||
c.tailFetchCh = make(chan struct{})
|
||||
go c.tailFetcher()
|
||||
}
|
||||
|
||||
func (c *Client) closeCanonicalChain() {
|
||||
c.requests.close()
|
||||
}
|
||||
|
||||
// Process implements request.Module in order to get notified about new heads.
|
||||
func (c *Client) Process(requester request.Requester, events []request.Event) {
|
||||
if finality, ok := c.headTracker.ValidatedFinality(); ok {
|
||||
finalized := finality.Finalized.PayloadHeader
|
||||
c.setFinality(finalized)
|
||||
c.addPayloadHeader(finalized)
|
||||
}
|
||||
if optimistic, ok := c.headTracker.ValidatedOptimistic(); ok {
|
||||
head := optimistic.Attested.PayloadHeader
|
||||
c.addPayloadHeader(head)
|
||||
if c.setHead(head) {
|
||||
go c.processNewHead(head.BlockNumber(), head.BlockHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) tailFetcher() { //TODO stop
|
||||
for {
|
||||
c.chainLock.Lock()
|
||||
var (
|
||||
tailNum uint64
|
||||
tailHash common.Hash
|
||||
)
|
||||
if c.recent != nil {
|
||||
tailNum, tailHash = c.recentTail, c.recent[c.recentTail]
|
||||
}
|
||||
needTail := tailNum
|
||||
for _, reqNum := range c.requests.allKeys() {
|
||||
if reqNum < needTail {
|
||||
needTail = reqNum
|
||||
}
|
||||
}
|
||||
c.chainLock.Unlock()
|
||||
if needTail < tailNum { //TODO check recentCanonicalLength
|
||||
log.Debug("Fetching tail headers", "have", tailNum, "need", needTail)
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
|
||||
//TODO parallel fetch by number
|
||||
if header, err := c.getHeader(ctx, tailHash); err == nil {
|
||||
c.addRecentTail(header)
|
||||
}
|
||||
} else {
|
||||
<-c.tailFetchCh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error) {
|
||||
if hash, ok := c.getCachedHash(number); ok {
|
||||
return hash, nil
|
||||
}
|
||||
req := c.requests.request(number)
|
||||
select {
|
||||
case c.tailFetchCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
defer req.release()
|
||||
return req.getResult(ctx)
|
||||
}
|
||||
|
||||
func (c *Client) getCachedHash(number uint64) (common.Hash, bool) {
|
||||
c.chainLock.Lock()
|
||||
hash, ok := c.recent[number]
|
||||
c.chainLock.Unlock()
|
||||
if ok {
|
||||
return hash, true
|
||||
}
|
||||
return c.finalized.Get(number)
|
||||
}
|
||||
|
||||
func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
headNum, headHash := head.BlockNumber(), head.BlockHash()
|
||||
if c.head != nil && c.head.BlockHash() == headHash {
|
||||
return false
|
||||
}
|
||||
if c.recent == nil || c.head == nil || c.head.BlockNumber()+1 != headNum || c.head.BlockHash() != head.ParentHash() {
|
||||
// initialize recent canonical hash map when first head is added or when
|
||||
// it is not a descendant of the previous head
|
||||
c.recent = make(map[uint64]common.Hash)
|
||||
if headNum > 0 {
|
||||
c.recent[headNum-1] = head.ParentHash()
|
||||
c.recentTail = headNum - 1
|
||||
} else {
|
||||
c.recentTail = 0
|
||||
}
|
||||
}
|
||||
c.head = head
|
||||
c.recent[headNum] = headHash
|
||||
for headNum >= c.recentTail+recentCanonicalLength {
|
||||
if c.finality != nil && c.recentTail <= c.finality.BlockNumber() {
|
||||
c.finalized.Add(c.recentTail, c.recent[c.recentTail])
|
||||
}
|
||||
delete(c.recent, c.recentTail)
|
||||
c.recentTail++
|
||||
}
|
||||
c.requests.tryDeliver(headNum, headHash)
|
||||
log.Debug("SetHead", "recentTail", c.recentTail, "head", headNum)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
c.finality = finality
|
||||
finalNum := finality.BlockNumber()
|
||||
if finalNum < c.recentTail {
|
||||
c.finalized.Add(finalNum, finality.BlockHash())
|
||||
}
|
||||
c.requests.tryDeliver(finalNum, finality.BlockHash())
|
||||
}
|
||||
|
||||
func (c *Client) addRecentTail(tail *types.Header) bool {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
if c.recent == nil || tail.Number.Uint64() != c.recentTail || c.recent[c.recentTail] != tail.Hash() {
|
||||
return false
|
||||
}
|
||||
if c.recentTail > 0 {
|
||||
c.recentTail--
|
||||
c.recent[c.recentTail] = tail.ParentHash
|
||||
c.requests.tryDeliver(c.recentTail, tail.ParentHash)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) getHead() *btypes.ExecutionHeader {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
return c.head
|
||||
}
|
||||
|
||||
func (c *Client) getFinality() *btypes.ExecutionHeader {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
return c.finality
|
||||
}
|
||||
|
||||
func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionHeader, error) {
|
||||
if !number.IsInt64() {
|
||||
return 0, nil, errors.New("Invalid block number")
|
||||
}
|
||||
num := number.Int64()
|
||||
if num < 0 {
|
||||
switch rpc.BlockNumber(num) {
|
||||
case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber:
|
||||
if header := c.getFinality(); header != nil {
|
||||
return header.BlockNumber(), header, nil
|
||||
}
|
||||
return 0, nil, errors.New("Finalized block unknown")
|
||||
case rpc.LatestBlockNumber, rpc.PendingBlockNumber:
|
||||
if header := c.getHead(); header != nil {
|
||||
return header.BlockNumber(), header, nil
|
||||
}
|
||||
return 0, nil, errors.New("Head block unknown")
|
||||
default:
|
||||
return 0, nil, errors.New("Invalid block number")
|
||||
}
|
||||
}
|
||||
return uint64(num), nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common.Hash, error) {
|
||||
num, pheader, err := c.resolveBlockNumber(number)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if pheader != nil {
|
||||
return pheader.BlockHash(), nil
|
||||
}
|
||||
return c.getHash(ctx, num)
|
||||
}
|
||||
|
||||
func (c *Client) blockNumberOrHashToHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.Hash, error) {
|
||||
if blockNrOrHash.BlockNumber != nil {
|
||||
return c.blockNumberToHash(ctx, big.NewInt(int64(*blockNrOrHash.BlockNumber)))
|
||||
}
|
||||
hash := *blockNrOrHash.BlockHash
|
||||
if blockNrOrHash.RequireCanonical {
|
||||
header, err := c.getHeader(ctx, hash)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
chash, err := c.getHash(ctx, header.Number.Uint64())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if chash != hash {
|
||||
return common.Hash{}, errors.New("hash is not currently canonical")
|
||||
}
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
type blocksAndHeadersFields struct {
|
||||
headerCache *lru.Cache[common.Hash, *types.Header]
|
||||
headerRequests *requestMap[common.Hash, *types.Header]
|
||||
|
|
@ -277,41 +51,6 @@ func (c *Client) closeBlocksAndHeaders() {
|
|||
c.blockRequests.close()
|
||||
}
|
||||
|
||||
func (c *Client) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
var header *types.Header
|
||||
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false)
|
||||
err := c.client.CallContext(ctx, &header, "eth_getBlockByHash", hash, false)
|
||||
if err == nil && header == nil {
|
||||
err = ethereum.NotFound
|
||||
}
|
||||
if err == nil && header.Hash() != hash {
|
||||
header, err = nil, errors.New("header hash does not match")
|
||||
}
|
||||
log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false, "error", err)
|
||||
return header, err
|
||||
}
|
||||
|
||||
func (c *Client) requestBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
var (
|
||||
raw json.RawMessage
|
||||
block *types.Block
|
||||
)
|
||||
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true)
|
||||
err := c.client.CallContext(ctx, &raw, "eth_getBlockByHash", hash, true)
|
||||
log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true, "error", err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err = decodeBlock(raw) // returns ethereum.NotFound if block not found
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if block.Hash() != hash {
|
||||
return nil, errors.New("block hash does not match")
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (c *Client) getHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
if header, ok := c.headerCache.Get(hash); ok {
|
||||
return header, nil
|
||||
|
|
@ -347,6 +86,10 @@ func (c *Client) getPayloadHeader(hash common.Hash) *btypes.ExecutionHeader {
|
|||
return pheader
|
||||
}
|
||||
|
||||
func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) {
|
||||
c.payloadHeaderCache.Add(header.BlockHash(), header)
|
||||
}
|
||||
|
||||
func (c *Client) getBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
if block, ok := c.blockCache.Get(hash); ok {
|
||||
return block, nil
|
||||
|
|
@ -364,7 +107,81 @@ func (c *Client) getBlock(ctx context.Context, hash common.Hash) (*types.Block,
|
|||
return block, err
|
||||
}
|
||||
|
||||
func (c *Client) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
var header *types.Header
|
||||
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false)
|
||||
err := c.client.CallContext(ctx, &header, "eth_getBlockByHash", hash, false)
|
||||
if err == nil && header == nil {
|
||||
err = ethereum.NotFound
|
||||
}
|
||||
if err == nil && header.Hash() != hash {
|
||||
header, err = nil, errors.New("header hash does not match")
|
||||
}
|
||||
log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false, "error", err)
|
||||
return header, err
|
||||
}
|
||||
|
||||
func (c *Client) requestBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
var (
|
||||
raw json.RawMessage
|
||||
block *types.Block
|
||||
)
|
||||
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true)
|
||||
err := c.client.CallContext(ctx, &raw, "eth_getBlockByHash", hash, true)
|
||||
log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true, "error", err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err = decodeBlock(raw) // returns ethereum.NotFound if block not found
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if block.Hash() != hash {
|
||||
return nil, errors.New("block hash does not match")
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
//TODO de-duplicate json block decoding
|
||||
func decodeBlock(raw json.RawMessage) (*types.Block, error) {
|
||||
// Decode header and transactions.
|
||||
var head *types.Header
|
||||
if err := json.Unmarshal(raw, &head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When the block is not found, the API returns JSON null.
|
||||
if head == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
var body rpcBlock
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
|
||||
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
|
||||
return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
|
||||
}
|
||||
if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
|
||||
return nil, errors.New("server returned empty uncle list but block header indicates uncles")
|
||||
}
|
||||
if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {
|
||||
return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions")
|
||||
}
|
||||
if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 {
|
||||
return nil, errors.New("server returned empty transaction list but block header indicates transactions")
|
||||
}
|
||||
// Fill the sender cache of transactions in the block.
|
||||
txs := make([]*types.Transaction, len(body.Transactions))
|
||||
for i, tx := range body.Transactions {
|
||||
if tx.From != nil {
|
||||
setSenderFromServer(tx.tx, *tx.From, body.Hash)
|
||||
}
|
||||
txs[i] = tx.tx
|
||||
}
|
||||
return types.NewBlockWithHeader(head).WithBody(types.Body{Transactions: txs, Withdrawals: body.Withdrawals}), nil
|
||||
}
|
||||
|
||||
type rpcBlock struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
Transactions []rpcTransaction `json:"transactions"`
|
||||
|
|
@ -426,46 +243,3 @@ func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash {
|
|||
func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||
panic("can't sign with senderFromServer")
|
||||
}
|
||||
|
||||
func decodeBlock(raw json.RawMessage) (*types.Block, error) {
|
||||
// Decode header and transactions.
|
||||
var head *types.Header
|
||||
if err := json.Unmarshal(raw, &head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When the block is not found, the API returns JSON null.
|
||||
if head == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
var body rpcBlock
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
|
||||
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
|
||||
return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
|
||||
}
|
||||
if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
|
||||
return nil, errors.New("server returned empty uncle list but block header indicates uncles")
|
||||
}
|
||||
if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {
|
||||
return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions")
|
||||
}
|
||||
if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 {
|
||||
return nil, errors.New("server returned empty transaction list but block header indicates transactions")
|
||||
}
|
||||
// Fill the sender cache of transactions in the block.
|
||||
txs := make([]*types.Transaction, len(body.Transactions))
|
||||
for i, tx := range body.Transactions {
|
||||
if tx.From != nil {
|
||||
setSenderFromServer(tx.tx, *tx.From, body.Hash)
|
||||
}
|
||||
txs[i] = tx.tx
|
||||
}
|
||||
return types.NewBlockWithHeader(head).WithBody(types.Body{Transactions: txs, Withdrawals: body.Withdrawals}), nil
|
||||
}
|
||||
|
||||
func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) {
|
||||
c.payloadHeaderCache.Add(header.BlockHash(), header)
|
||||
}
|
||||
238
ethclient/lightclient/canonical.go
Normal file
238
ethclient/lightclient/canonical.go
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lightclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
btypes "github.com/ethereum/go-ethereum/beacon/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
const recentCanonicalLength = 256
|
||||
|
||||
type canonicalChainFields struct {
|
||||
chainLock sync.Mutex
|
||||
head, finality *btypes.ExecutionHeader
|
||||
recent map[uint64]common.Hash // nil while head == nil
|
||||
recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head
|
||||
tailFetchCh chan struct{}
|
||||
finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes
|
||||
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized
|
||||
}
|
||||
|
||||
func (c *Client) initCanonicalChain() {
|
||||
c.finalized = lru.NewCache[uint64, common.Hash](10000)
|
||||
c.requests = newRequestMap[uint64, common.Hash](nil)
|
||||
c.tailFetchCh = make(chan struct{})
|
||||
go c.tailFetcher()
|
||||
}
|
||||
|
||||
func (c *Client) closeCanonicalChain() {
|
||||
c.requests.close()
|
||||
}
|
||||
|
||||
func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
headNum, headHash := head.BlockNumber(), head.BlockHash()
|
||||
if c.head != nil && c.head.BlockHash() == headHash {
|
||||
return false
|
||||
}
|
||||
if c.recent == nil || c.head == nil || c.head.BlockNumber()+1 != headNum || c.head.BlockHash() != head.ParentHash() {
|
||||
// initialize recent canonical hash map when first head is added or when
|
||||
// it is not a descendant of the previous head
|
||||
c.recent = make(map[uint64]common.Hash)
|
||||
if headNum > 0 {
|
||||
c.recent[headNum-1] = head.ParentHash()
|
||||
c.recentTail = headNum - 1
|
||||
} else {
|
||||
c.recentTail = 0
|
||||
}
|
||||
}
|
||||
c.head = head
|
||||
c.recent[headNum] = headHash
|
||||
for headNum >= c.recentTail+recentCanonicalLength {
|
||||
if c.finality != nil && c.recentTail <= c.finality.BlockNumber() {
|
||||
c.finalized.Add(c.recentTail, c.recent[c.recentTail])
|
||||
}
|
||||
delete(c.recent, c.recentTail)
|
||||
c.recentTail++
|
||||
}
|
||||
c.requests.tryDeliver(headNum, headHash)
|
||||
log.Debug("SetHead", "recentTail", c.recentTail, "head", headNum)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
c.finality = finality
|
||||
finalNum := finality.BlockNumber()
|
||||
if finalNum < c.recentTail {
|
||||
c.finalized.Add(finalNum, finality.BlockHash())
|
||||
}
|
||||
c.requests.tryDeliver(finalNum, finality.BlockHash())
|
||||
}
|
||||
|
||||
func (c *Client) getHead() *btypes.ExecutionHeader {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
return c.head
|
||||
}
|
||||
|
||||
func (c *Client) getFinality() *btypes.ExecutionHeader {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
return c.finality
|
||||
}
|
||||
|
||||
func (c *Client) addRecentTail(tail *types.Header) bool {
|
||||
c.chainLock.Lock()
|
||||
defer c.chainLock.Unlock()
|
||||
|
||||
if c.recent == nil || tail.Number.Uint64() != c.recentTail || c.recent[c.recentTail] != tail.Hash() {
|
||||
return false
|
||||
}
|
||||
if c.recentTail > 0 {
|
||||
c.recentTail--
|
||||
c.recent[c.recentTail] = tail.ParentHash
|
||||
c.requests.tryDeliver(c.recentTail, tail.ParentHash)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) tailFetcher() { //TODO stop
|
||||
for {
|
||||
c.chainLock.Lock()
|
||||
var (
|
||||
tailNum uint64
|
||||
tailHash common.Hash
|
||||
)
|
||||
if c.recent != nil {
|
||||
tailNum, tailHash = c.recentTail, c.recent[c.recentTail]
|
||||
}
|
||||
needTail := tailNum
|
||||
for _, reqNum := range c.requests.allKeys() {
|
||||
if reqNum < needTail {
|
||||
needTail = reqNum
|
||||
}
|
||||
}
|
||||
c.chainLock.Unlock()
|
||||
if needTail < tailNum { //TODO check recentCanonicalLength
|
||||
log.Debug("Fetching tail headers", "have", tailNum, "need", needTail)
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
|
||||
//TODO parallel fetch by number
|
||||
if header, err := c.getHeader(ctx, tailHash); err == nil {
|
||||
c.addRecentTail(header)
|
||||
}
|
||||
} else {
|
||||
<-c.tailFetchCh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error) {
|
||||
if hash, ok := c.getCachedHash(number); ok {
|
||||
return hash, nil
|
||||
}
|
||||
req := c.requests.request(number)
|
||||
select {
|
||||
case c.tailFetchCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
defer req.release()
|
||||
return req.getResult(ctx)
|
||||
}
|
||||
|
||||
func (c *Client) getCachedHash(number uint64) (common.Hash, bool) {
|
||||
c.chainLock.Lock()
|
||||
hash, ok := c.recent[number]
|
||||
c.chainLock.Unlock()
|
||||
if ok {
|
||||
return hash, true
|
||||
}
|
||||
return c.finalized.Get(number)
|
||||
}
|
||||
|
||||
func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionHeader, error) {
|
||||
if !number.IsInt64() {
|
||||
return 0, nil, errors.New("Invalid block number")
|
||||
}
|
||||
num := number.Int64()
|
||||
if num < 0 {
|
||||
switch rpc.BlockNumber(num) {
|
||||
case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber:
|
||||
if header := c.getFinality(); header != nil {
|
||||
return header.BlockNumber(), header, nil
|
||||
}
|
||||
return 0, nil, errors.New("Finalized block unknown")
|
||||
case rpc.LatestBlockNumber, rpc.PendingBlockNumber:
|
||||
if header := c.getHead(); header != nil {
|
||||
return header.BlockNumber(), header, nil
|
||||
}
|
||||
return 0, nil, errors.New("Head block unknown")
|
||||
default:
|
||||
return 0, nil, errors.New("Invalid block number")
|
||||
}
|
||||
}
|
||||
return uint64(num), nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common.Hash, error) {
|
||||
num, pheader, err := c.resolveBlockNumber(number)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if pheader != nil {
|
||||
return pheader.BlockHash(), nil
|
||||
}
|
||||
return c.getHash(ctx, num)
|
||||
}
|
||||
|
||||
func (c *Client) blockNumberOrHashToHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.Hash, error) {
|
||||
if blockNrOrHash.BlockNumber != nil {
|
||||
return c.blockNumberToHash(ctx, big.NewInt(int64(*blockNrOrHash.BlockNumber)))
|
||||
}
|
||||
hash := *blockNrOrHash.BlockHash
|
||||
if blockNrOrHash.RequireCanonical {
|
||||
header, err := c.getHeader(ctx, hash)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
chash, err := c.getHash(ctx, header.Number.Uint64())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if chash != hash {
|
||||
return common.Hash{}, errors.New("hash is not currently canonical")
|
||||
}
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ func NewClient(clConfig config.LightClientConfig, elConfig *params.ChainConfig,
|
|||
client.scheduler.RegisterModule(checkpointInit, "checkpointInit")
|
||||
client.scheduler.RegisterModule(forwardSync, "forwardSync")
|
||||
client.scheduler.RegisterModule(headSync, "headSync")
|
||||
client.scheduler.RegisterModule(client, "canonicalChain")
|
||||
client.scheduler.RegisterModule((*schedulerModule)(client), "lightClient")
|
||||
return client
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +100,25 @@ func (c *Client) Close() {
|
|||
c.scheduler.Stop()
|
||||
}
|
||||
|
||||
type schedulerModule Client
|
||||
|
||||
// Process implements request.Module in order to get notified about new heads.
|
||||
func (s *schedulerModule) Process(requester request.Requester, events []request.Event) {
|
||||
c := (*Client)(s)
|
||||
if finality, ok := c.headTracker.ValidatedFinality(); ok {
|
||||
finalized := finality.Finalized.PayloadHeader
|
||||
c.setFinality(finalized)
|
||||
c.addPayloadHeader(finalized)
|
||||
}
|
||||
if optimistic, ok := c.headTracker.ValidatedOptimistic(); ok {
|
||||
head := optimistic.Attested.PayloadHeader
|
||||
c.addPayloadHeader(head)
|
||||
if c.setHead(head) {
|
||||
go c.processNewHead(head.BlockNumber(), head.BlockHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChainReader interface
|
||||
|
||||
func (c *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ import (
|
|||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
type lightStateFields struct {
|
||||
proofCache *lru.Cache[proofRequest, *gethclient.AccountResult]
|
||||
proofRequests *requestMap[proofRequest, *gethclient.AccountResult]
|
||||
codeCache *lru.Cache[codeRequest, []byte]
|
||||
codeRequests *requestMap[codeRequest, []byte]
|
||||
}
|
||||
|
||||
type proofRequest struct {
|
||||
blockNumber uint64
|
||||
address common.Address
|
||||
|
|
@ -46,13 +53,6 @@ type codeRequest struct {
|
|||
address common.Address
|
||||
}
|
||||
|
||||
type lightStateFields struct {
|
||||
proofCache *lru.Cache[proofRequest, *gethclient.AccountResult]
|
||||
proofRequests *requestMap[proofRequest, *gethclient.AccountResult]
|
||||
codeCache *lru.Cache[codeRequest, []byte]
|
||||
codeRequests *requestMap[codeRequest, []byte]
|
||||
}
|
||||
|
||||
func (c *Client) initLightState() {
|
||||
c.proofCache = lru.NewCache[proofRequest, *gethclient.AccountResult](100)
|
||||
c.codeCache = lru.NewCache[codeRequest, []byte](10)
|
||||
|
|
@ -65,135 +65,6 @@ func (c *Client) closeLightState() {
|
|||
c.codeRequests.close()
|
||||
}
|
||||
|
||||
func (c *Client) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
|
||||
if proof, ok := c.proofCache.Get(req); ok {
|
||||
return proof, nil
|
||||
}
|
||||
request := c.proofRequests.request(req)
|
||||
proof, err := request.getResult(ctx)
|
||||
if err == nil {
|
||||
c.proofCache.Add(req, proof) // cached before validation; remove and retry if invalid
|
||||
}
|
||||
request.release()
|
||||
return proof, err
|
||||
}
|
||||
|
||||
func (c *Client) requestProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
|
||||
type storageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
type accountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []storageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
var storageKeys []string
|
||||
if len(req.storageKeys) > 0 {
|
||||
storageKeys = strings.Split(req.storageKeys, ",")
|
||||
}
|
||||
log.Debug("Starting RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys))
|
||||
var res accountResult
|
||||
err := c.client.CallContext(ctx, &res, "eth_getProof", req.address, storageKeys, hexutil.EncodeUint64(req.blockNumber))
|
||||
log.Debug("Finished RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys), "error", err)
|
||||
var proof *gethclient.AccountResult
|
||||
if err == nil { //TODO de-duplicate
|
||||
// Turn hexutils back to normal datatypes
|
||||
storageResults := make([]gethclient.StorageResult, 0, len(res.StorageProof))
|
||||
for _, st := range res.StorageProof {
|
||||
storageResults = append(storageResults, gethclient.StorageResult{
|
||||
Key: st.Key,
|
||||
Value: st.Value.ToInt(),
|
||||
Proof: st.Proof,
|
||||
})
|
||||
}
|
||||
proof = &gethclient.AccountResult{
|
||||
Address: res.Address,
|
||||
AccountProof: res.AccountProof,
|
||||
Balance: res.Balance.ToInt(),
|
||||
Nonce: uint64(res.Nonce),
|
||||
CodeHash: res.CodeHash,
|
||||
StorageHash: res.StorageHash,
|
||||
StorageProof: storageResults,
|
||||
}
|
||||
}
|
||||
return proof, err
|
||||
}
|
||||
|
||||
func (c *Client) fetchCode(ctx context.Context, req codeRequest) ([]byte, error) {
|
||||
if code, ok := c.codeCache.Get(req); ok {
|
||||
return code, nil
|
||||
}
|
||||
request := c.codeRequests.request(req)
|
||||
code, err := request.getResult(ctx)
|
||||
if err == nil {
|
||||
c.codeCache.Add(req, code) // cached before validation; remove and retry if invalid
|
||||
}
|
||||
request.release()
|
||||
return code, err
|
||||
}
|
||||
|
||||
func (c *Client) requestCode(ctx context.Context, req codeRequest) ([]byte, error) {
|
||||
var code hexutil.Bytes
|
||||
log.Debug("Starting RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address)
|
||||
err := c.client.CallContext(ctx, &code, "eth_getCode", req.address, hexutil.EncodeUint64(req.blockNumber))
|
||||
log.Debug("Finished RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address, "error", err)
|
||||
return code, err
|
||||
}
|
||||
|
||||
// proofReader implements ethdb.KeyValueReader.
|
||||
type proofReader map[string][]byte
|
||||
|
||||
func (p proofReader) Has(key []byte) (bool, error) {
|
||||
_, ok := p[string(key)]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (p proofReader) Get(key []byte) ([]byte, error) {
|
||||
if value, ok := p[string(key)]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func makeProofReader(proof []string) (proofReader, error) {
|
||||
pr := make(proofReader)
|
||||
for _, s := range proof {
|
||||
node, err := hexutil.Decode(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr[string(crypto.Keccak256(node))] = node
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func stValueBytes(value *big.Int) ([]byte, error) {
|
||||
if value == nil {
|
||||
return nil, errors.New("storage value is nil")
|
||||
}
|
||||
switch value.Sign() {
|
||||
case -1:
|
||||
return nil, errors.New("negative storage value")
|
||||
case 1:
|
||||
if value.BitLen() > 256 {
|
||||
return nil, errors.New("storage value bigger than uint256")
|
||||
}
|
||||
stv := make([]byte, 32)
|
||||
value.FillBytes(stv)
|
||||
return stv, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getProof(ctx context.Context, blockNumber *big.Int, account common.Address, storageKeys []string, getCode bool) (*gethclient.AccountResult, []byte, error) {
|
||||
proof, code, retry, err := c.getProofOnce(ctx, blockNumber, account, storageKeys, getCode)
|
||||
if retry {
|
||||
|
|
@ -351,3 +222,132 @@ func (c *Client) validateProof(proof *gethclient.AccountResult, stateRoot common
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// proofReader implements ethdb.KeyValueReader.
|
||||
type proofReader map[string][]byte
|
||||
|
||||
func makeProofReader(proof []string) (proofReader, error) {
|
||||
pr := make(proofReader)
|
||||
for _, s := range proof {
|
||||
node, err := hexutil.Decode(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr[string(crypto.Keccak256(node))] = node
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (p proofReader) Has(key []byte) (bool, error) {
|
||||
_, ok := p[string(key)]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (p proofReader) Get(key []byte) ([]byte, error) {
|
||||
if value, ok := p[string(key)]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func stValueBytes(value *big.Int) ([]byte, error) {
|
||||
if value == nil {
|
||||
return nil, errors.New("storage value is nil")
|
||||
}
|
||||
switch value.Sign() {
|
||||
case -1:
|
||||
return nil, errors.New("negative storage value")
|
||||
case 1:
|
||||
if value.BitLen() > 256 {
|
||||
return nil, errors.New("storage value bigger than uint256")
|
||||
}
|
||||
stv := make([]byte, 32)
|
||||
value.FillBytes(stv)
|
||||
return stv, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
|
||||
if proof, ok := c.proofCache.Get(req); ok {
|
||||
return proof, nil
|
||||
}
|
||||
request := c.proofRequests.request(req)
|
||||
proof, err := request.getResult(ctx)
|
||||
if err == nil {
|
||||
c.proofCache.Add(req, proof) // cached before validation; remove and retry if invalid
|
||||
}
|
||||
request.release()
|
||||
return proof, err
|
||||
}
|
||||
|
||||
func (c *Client) fetchCode(ctx context.Context, req codeRequest) ([]byte, error) {
|
||||
if code, ok := c.codeCache.Get(req); ok {
|
||||
return code, nil
|
||||
}
|
||||
request := c.codeRequests.request(req)
|
||||
code, err := request.getResult(ctx)
|
||||
if err == nil {
|
||||
c.codeCache.Add(req, code) // cached before validation; remove and retry if invalid
|
||||
}
|
||||
request.release()
|
||||
return code, err
|
||||
}
|
||||
|
||||
func (c *Client) requestProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
|
||||
type storageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
type accountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []storageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
var storageKeys []string
|
||||
if len(req.storageKeys) > 0 {
|
||||
storageKeys = strings.Split(req.storageKeys, ",")
|
||||
}
|
||||
log.Debug("Starting RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys))
|
||||
var res accountResult
|
||||
err := c.client.CallContext(ctx, &res, "eth_getProof", req.address, storageKeys, hexutil.EncodeUint64(req.blockNumber))
|
||||
log.Debug("Finished RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys), "error", err)
|
||||
var proof *gethclient.AccountResult
|
||||
if err == nil { //TODO de-duplicate
|
||||
// Turn hexutils back to normal datatypes
|
||||
storageResults := make([]gethclient.StorageResult, 0, len(res.StorageProof))
|
||||
for _, st := range res.StorageProof {
|
||||
storageResults = append(storageResults, gethclient.StorageResult{
|
||||
Key: st.Key,
|
||||
Value: st.Value.ToInt(),
|
||||
Proof: st.Proof,
|
||||
})
|
||||
}
|
||||
proof = &gethclient.AccountResult{
|
||||
Address: res.Address,
|
||||
AccountProof: res.AccountProof,
|
||||
Balance: res.Balance.ToInt(),
|
||||
Nonce: uint64(res.Nonce),
|
||||
CodeHash: res.CodeHash,
|
||||
StorageHash: res.StorageHash,
|
||||
StorageProof: storageResults,
|
||||
}
|
||||
}
|
||||
return proof, err
|
||||
}
|
||||
|
||||
func (c *Client) requestCode(ctx context.Context, req codeRequest) ([]byte, error) {
|
||||
var code hexutil.Bytes
|
||||
log.Debug("Starting RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address)
|
||||
err := c.client.CallContext(ctx, &code, "eth_getCode", req.address, hexutil.EncodeUint64(req.blockNumber))
|
||||
log.Debug("Finished RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address, "error", err)
|
||||
return code, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,48 +317,6 @@ func (c *Client) sendTransaction(ctx context.Context, tx *types.Transaction) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) markTxAsSeen(sender common.Address, tx *types.Transaction) {
|
||||
senderTxs := c.sentTxs[sender]
|
||||
if senderTxs == nil {
|
||||
senderTxs = make(map[common.Hash]sentTx)
|
||||
c.sentTxs[sender] = senderTxs
|
||||
}
|
||||
senderTxs[tx.Hash()] = sentTx{nonce: tx.Nonce(), lastSeen: c.headCounter}
|
||||
}
|
||||
|
||||
func (c *Client) txAndReceiptsNewHead(number uint64, hash common.Hash) {
|
||||
c.sentTxLock.Lock()
|
||||
if number > c.lastHeadNumber {
|
||||
c.lastHeadNumber = number
|
||||
c.headCounter++
|
||||
c.discardOldTxs()
|
||||
}
|
||||
c.sentTxLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) discardOldTxs() {
|
||||
for sender, senderTxs := range c.sentTxs {
|
||||
for txHash, sentTx := range senderTxs {
|
||||
if sentTx.lastSeen+maxTxAge < c.headCounter {
|
||||
delete(senderTxs, txHash)
|
||||
}
|
||||
}
|
||||
if len(senderTxs) == 0 {
|
||||
delete(c.sentTxs, sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) allSenders() []common.Address {
|
||||
c.sentTxLock.Lock()
|
||||
allSenders := make([]common.Address, 0, len(c.sentTxs))
|
||||
for sender := range c.sentTxs {
|
||||
allSenders = append(allSenders, sender)
|
||||
}
|
||||
c.sentTxLock.Unlock()
|
||||
return allSenders
|
||||
}
|
||||
|
||||
func (c *Client) nonceAndPendingTxs(ctx context.Context, head *btypes.ExecutionHeader, sender common.Address) (uint64, types.Transactions, error) {
|
||||
proof, err := c.fetchProof(ctx, proofRequest{blockNumber: head.BlockNumber(), address: sender, storageKeys: ""})
|
||||
if err != nil {
|
||||
|
|
@ -411,3 +369,45 @@ func (c *Client) nonceAndPendingTxs(ctx context.Context, head *btypes.ExecutionH
|
|||
c.sentTxLock.Unlock()
|
||||
return proof.Nonce, pendingList, nil
|
||||
}
|
||||
|
||||
func (c *Client) allSenders() []common.Address {
|
||||
c.sentTxLock.Lock()
|
||||
allSenders := make([]common.Address, 0, len(c.sentTxs))
|
||||
for sender := range c.sentTxs {
|
||||
allSenders = append(allSenders, sender)
|
||||
}
|
||||
c.sentTxLock.Unlock()
|
||||
return allSenders
|
||||
}
|
||||
|
||||
func (c *Client) txAndReceiptsNewHead(number uint64, hash common.Hash) {
|
||||
c.sentTxLock.Lock()
|
||||
if number > c.lastHeadNumber {
|
||||
c.lastHeadNumber = number
|
||||
c.headCounter++
|
||||
c.discardOldTxs()
|
||||
}
|
||||
c.sentTxLock.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) discardOldTxs() {
|
||||
for sender, senderTxs := range c.sentTxs {
|
||||
for txHash, sentTx := range senderTxs {
|
||||
if sentTx.lastSeen+maxTxAge < c.headCounter {
|
||||
delete(senderTxs, txHash)
|
||||
}
|
||||
}
|
||||
if len(senderTxs) == 0 {
|
||||
delete(c.sentTxs, sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) markTxAsSeen(sender common.Address, tx *types.Transaction) {
|
||||
senderTxs := c.sentTxs[sender]
|
||||
if senderTxs == nil {
|
||||
senderTxs = make(map[common.Hash]sentTx)
|
||||
c.sentTxs[sender] = senderTxs
|
||||
}
|
||||
senderTxs[tx.Hash()] = sentTx{nonce: tx.Nonce(), lastSeen: c.headCounter}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue