github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/mempool/reactor.go (about) 1 package mempool 2 3 import ( 4 "fmt" 5 "log/slog" 6 "math" 7 "reflect" 8 "sync" 9 "time" 10 11 "github.com/gnolang/gno/tm2/pkg/amino" 12 cfg "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" 13 "github.com/gnolang/gno/tm2/pkg/bft/types" 14 "github.com/gnolang/gno/tm2/pkg/clist" 15 "github.com/gnolang/gno/tm2/pkg/p2p" 16 ) 17 18 const ( 19 MempoolChannel = byte(0x30) 20 21 peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount 22 23 // UnknownPeerID is the peer ID to use when running CheckTx when there is 24 // no peer (e.g. RPC) 25 UnknownPeerID uint16 = 0 26 27 maxActiveIDs = math.MaxUint16 28 ) 29 30 // Reactor handles mempool tx broadcasting amongst peers. 31 // It maintains a map from peer ID to counter, to prevent gossiping txs to the 32 // peers you received it from. 33 type Reactor struct { 34 p2p.BaseReactor 35 config *cfg.MempoolConfig 36 mempool *CListMempool 37 ids *mempoolIDs 38 } 39 40 type mempoolIDs struct { 41 mtx sync.RWMutex 42 peerMap map[p2p.ID]uint16 43 nextID uint16 // assumes that a node will never have over 65536 active peers 44 activeIDs map[uint16]struct{} // used to check if a given peerID key is used, the value doesn't matter 45 } 46 47 // Reserve searches for the next unused ID and assigns it to the 48 // peer. 49 func (ids *mempoolIDs) ReserveForPeer(peer p2p.Peer) { 50 ids.mtx.Lock() 51 defer ids.mtx.Unlock() 52 53 curID := ids.nextPeerID() 54 ids.peerMap[peer.ID()] = curID 55 ids.activeIDs[curID] = struct{}{} 56 } 57 58 // nextPeerID returns the next unused peer ID to use. 59 // This assumes that ids's mutex is already locked. 60 func (ids *mempoolIDs) nextPeerID() uint16 { 61 if len(ids.activeIDs) == maxActiveIDs { 62 panic(fmt.Sprintf("node has maximum %d active IDs and wanted to get one more", maxActiveIDs)) 63 } 64 65 _, idExists := ids.activeIDs[ids.nextID] 66 for idExists { 67 ids.nextID++ 68 _, idExists = ids.activeIDs[ids.nextID] 69 } 70 curID := ids.nextID 71 ids.nextID++ 72 return curID 73 } 74 75 // Reclaim returns the ID reserved for the peer back to unused pool. 76 func (ids *mempoolIDs) Reclaim(peer p2p.Peer) { 77 ids.mtx.Lock() 78 defer ids.mtx.Unlock() 79 80 removedID, ok := ids.peerMap[peer.ID()] 81 if ok { 82 delete(ids.activeIDs, removedID) 83 delete(ids.peerMap, peer.ID()) 84 } 85 } 86 87 // GetForPeer returns an ID reserved for the peer. 88 func (ids *mempoolIDs) GetForPeer(peer p2p.Peer) uint16 { 89 ids.mtx.RLock() 90 defer ids.mtx.RUnlock() 91 92 return ids.peerMap[peer.ID()] 93 } 94 95 func newMempoolIDs() *mempoolIDs { 96 return &mempoolIDs{ 97 peerMap: make(map[p2p.ID]uint16), 98 activeIDs: map[uint16]struct{}{0: {}}, 99 nextID: 1, // reserve unknownPeerID(0) for mempoolReactor.BroadcastTx 100 } 101 } 102 103 // NewReactor returns a new Reactor with the given config and mempool. 104 func NewReactor(config *cfg.MempoolConfig, mempool *CListMempool) *Reactor { 105 memR := &Reactor{ 106 config: config, 107 mempool: mempool, 108 ids: newMempoolIDs(), 109 } 110 memR.BaseReactor = *p2p.NewBaseReactor("Reactor", memR) 111 return memR 112 } 113 114 // SetLogger sets the Logger on the reactor and the underlying mempool. 115 func (memR *Reactor) SetLogger(l *slog.Logger) { 116 memR.Logger = l 117 memR.mempool.SetLogger(l) 118 } 119 120 // OnStart implements p2p.BaseReactor. 121 func (memR *Reactor) OnStart() error { 122 if !memR.config.Broadcast { 123 memR.Logger.Info("Tx broadcasting is disabled") 124 } 125 return nil 126 } 127 128 // GetChannels implements Reactor. 129 // It returns the list of channels for this reactor. 130 func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { 131 return []*p2p.ChannelDescriptor{ 132 { 133 ID: MempoolChannel, 134 Priority: 5, 135 }, 136 } 137 } 138 139 // AddPeer implements Reactor. 140 // It starts a broadcast routine ensuring all txs are forwarded to the given peer. 141 func (memR *Reactor) AddPeer(peer p2p.Peer) { 142 memR.ids.ReserveForPeer(peer) 143 go memR.broadcastTxRoutine(peer) 144 } 145 146 // RemovePeer implements Reactor. 147 func (memR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { 148 memR.ids.Reclaim(peer) 149 // broadcast routine checks if peer is gone and returns 150 } 151 152 // Receive implements Reactor. 153 // It adds any received transactions to the mempool. 154 func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { 155 msg, err := memR.decodeMsg(msgBytes) 156 if err != nil { 157 memR.Logger.Error("Error decoding mempool message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) 158 memR.Switch.StopPeerForError(src, err) 159 return 160 } 161 memR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) 162 163 switch msg := msg.(type) { 164 case *TxMessage: 165 peerID := memR.ids.GetForPeer(src) 166 err := memR.mempool.CheckTxWithInfo(msg.Tx, nil, TxInfo{SenderID: peerID}) 167 if err != nil { 168 memR.Logger.Info("Could not check tx", "tx", txID(msg.Tx), "err", err) 169 } 170 // broadcasting happens from go routines per peer 171 default: 172 memR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) 173 } 174 } 175 176 // PeerState describes the state of a peer. 177 type PeerState interface { 178 GetHeight() int64 179 } 180 181 // Send new mempool txs to peer. 182 func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { 183 if !memR.config.Broadcast { 184 return 185 } 186 187 peerID := memR.ids.GetForPeer(peer) 188 var next *clist.CElement 189 for { 190 // In case of both next.NextWaitChan() and peer.Quit() are variable at the same time 191 if !memR.IsRunning() || !peer.IsRunning() { 192 return 193 } 194 // This happens because the CElement we were looking at got garbage 195 // collected (removed). That is, .NextWait() returned nil. Go ahead and 196 // start from the beginning. 197 if next == nil { 198 select { 199 case <-memR.mempool.TxsWaitChan(): // Wait until a tx is available 200 if next = memR.mempool.TxsFront(); next == nil { 201 continue 202 } 203 case <-peer.Quit(): 204 return 205 case <-memR.Quit(): 206 return 207 } 208 } 209 210 memTx := next.Value.(*mempoolTx) 211 212 // make sure the peer is up to date 213 peerState, ok := peer.Get(types.PeerStateKey).(PeerState) 214 if !ok { 215 // Peer does not have a state yet. We set it in the consensus reactor, but 216 // when we add peer in Switch, the order we call reactors#AddPeer is 217 // different every time due to us using a map. Sometimes other reactors 218 // will be initialized before the consensus reactor. We should wait a few 219 // milliseconds and retry. 220 time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) 221 continue 222 } 223 if peerState.GetHeight() < memTx.Height()-1 { // Allow for a lag of 1 block 224 time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) 225 continue 226 } 227 228 // ensure peer hasn't already sent us this tx 229 if _, ok := memTx.senders.Load(peerID); !ok { 230 // send memTx 231 msg := &TxMessage{Tx: memTx.tx} 232 success := peer.Send(MempoolChannel, amino.MustMarshalAny(msg)) 233 if !success { 234 time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) 235 continue 236 } 237 } 238 239 select { 240 case <-next.NextWaitChan(): 241 // see the start of the for loop for nil check 242 next = next.Next() 243 case <-peer.Quit(): 244 return 245 case <-memR.Quit(): 246 return 247 } 248 } 249 } 250 251 // ----------------------------------------------------------------------------- 252 // Messages 253 254 // MempoolMessage is a message sent or received by the Reactor. 255 type MempoolMessage interface{} 256 257 func (memR *Reactor) decodeMsg(bz []byte) (msg MempoolMessage, err error) { 258 err = amino.Unmarshal(bz, &msg) 259 return 260 } 261 262 // ------------------------------------- 263 264 // TxMessage is a MempoolMessage containing a transaction. 265 type TxMessage struct { 266 Tx types.Tx 267 } 268 269 // String returns a string representation of the TxMessage. 270 func (m *TxMessage) String() string { 271 return fmt.Sprintf("[TxMessage %v]", m.Tx) 272 }