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 }