github.com/ethereum/go-ethereum@v1.16.1/core/txpool/reserver.go (about)

     1  // Copyright 2025 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package txpool
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/metrics"
    27  )
    28  
    29  var (
    30  	// reservationsGaugeName is the prefix of a per-subpool address reservation
    31  	// metric.
    32  	//
    33  	// This is mostly a sanity metric to ensure there's no bug that would make
    34  	// some subpool hog all the reservations due to mis-accounting.
    35  	reservationsGaugeName = "txpool/reservations"
    36  )
    37  
    38  // ReservationTracker is a struct shared between different subpools. It is used to reserve
    39  // the account and ensure that one address cannot initiate transactions, authorizations,
    40  // and other state-changing behaviors in different pools at the same time.
    41  type ReservationTracker struct {
    42  	accounts map[common.Address]int
    43  	lock     sync.RWMutex
    44  }
    45  
    46  // NewReservationTracker initializes the account reservation tracker.
    47  func NewReservationTracker() *ReservationTracker {
    48  	return &ReservationTracker{
    49  		accounts: make(map[common.Address]int),
    50  	}
    51  }
    52  
    53  // NewHandle creates a named handle on the ReservationTracker. The handle
    54  // identifies the subpool so ownership of reservations can be determined.
    55  func (r *ReservationTracker) NewHandle(id int) *ReservationHandle {
    56  	return &ReservationHandle{r, id}
    57  }
    58  
    59  // Reserver is an interface for creating and releasing owned reservations in the
    60  // ReservationTracker struct, which is shared between subpools.
    61  type Reserver interface {
    62  	// Hold attempts to reserve the specified account address for the given pool.
    63  	// Returns an error if the account is already reserved.
    64  	Hold(addr common.Address) error
    65  
    66  	// Release attempts to release the reservation for the specified account.
    67  	// Returns an error if the address is not reserved or is reserved by another pool.
    68  	Release(addr common.Address) error
    69  
    70  	// Has returns a flag indicating if the address has been reserved by a pool
    71  	// other than one with the current Reserver handle.
    72  	Has(address common.Address) bool
    73  }
    74  
    75  // ReservationHandle is a named handle on ReservationTracker. It is held by subpools to
    76  // make reservations for accounts it is tracking. The id is used to determine
    77  // which pool owns an address and disallows non-owners to hold or release
    78  // addresses it doesn't own.
    79  type ReservationHandle struct {
    80  	tracker *ReservationTracker
    81  	id      int
    82  }
    83  
    84  // Hold implements the Reserver interface.
    85  func (h *ReservationHandle) Hold(addr common.Address) error {
    86  	h.tracker.lock.Lock()
    87  	defer h.tracker.lock.Unlock()
    88  
    89  	// Double reservations are forbidden even from the same pool to
    90  	// avoid subtle bugs in the long term.
    91  	owner, exists := h.tracker.accounts[addr]
    92  	if exists {
    93  		if owner == h.id {
    94  			log.Error("pool attempted to reserve already-owned address", "address", addr)
    95  			return nil // Ignore fault to give the pool a chance to recover while the bug gets fixed
    96  		}
    97  		return ErrAlreadyReserved
    98  	}
    99  	h.tracker.accounts[addr] = h.id
   100  	if metrics.Enabled() {
   101  		m := fmt.Sprintf("%s/%d", reservationsGaugeName, h.id)
   102  		metrics.GetOrRegisterGauge(m, nil).Inc(1)
   103  	}
   104  	return nil
   105  }
   106  
   107  // Release implements the Reserver interface.
   108  func (h *ReservationHandle) Release(addr common.Address) error {
   109  	h.tracker.lock.Lock()
   110  	defer h.tracker.lock.Unlock()
   111  
   112  	// Ensure subpools only attempt to unreserve their own owned addresses,
   113  	// otherwise flag as a programming error.
   114  	owner, exists := h.tracker.accounts[addr]
   115  	if !exists {
   116  		log.Error("pool attempted to unreserve non-reserved address", "address", addr)
   117  		return errors.New("address not reserved")
   118  	}
   119  	if owner != h.id {
   120  		log.Error("pool attempted to unreserve non-owned address", "address", addr)
   121  		return errors.New("address not owned")
   122  	}
   123  	delete(h.tracker.accounts, addr)
   124  	if metrics.Enabled() {
   125  		m := fmt.Sprintf("%s/%d", reservationsGaugeName, h.id)
   126  		metrics.GetOrRegisterGauge(m, nil).Dec(1)
   127  	}
   128  	return nil
   129  }
   130  
   131  // Has implements the Reserver interface.
   132  func (h *ReservationHandle) Has(address common.Address) bool {
   133  	h.tracker.lock.RLock()
   134  	defer h.tracker.lock.RUnlock()
   135  
   136  	id, exists := h.tracker.accounts[address]
   137  	return exists && id != h.id
   138  }