github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/core/txpool/blobpool/limbo.go (about) 1 // Copyright 2023 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 blobpool 18 19 import ( 20 "errors" 21 22 "github.com/ethereum/go-ethereum/common" 23 "github.com/ethereum/go-ethereum/core/types" 24 "github.com/ethereum/go-ethereum/log" 25 "github.com/ethereum/go-ethereum/rlp" 26 "github.com/holiman/billy" 27 ) 28 29 // limboBlob is a wrapper around an opaque blobset that also contains the tx hash 30 // to which it belongs as well as the block number in which it was included for 31 // finality eviction. 32 type limboBlob struct { 33 TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs 34 Block uint64 // Block in which the blob transaction was included 35 Tx *types.Transaction 36 } 37 38 // limbo is a light, indexed database to temporarily store recently included 39 // blobs until they are finalized. The purpose is to support small reorgs, which 40 // would require pulling back up old blobs (which aren't part of the chain). 41 // 42 // TODO(karalabe): Currently updating the inclusion block of a blob needs a full db rewrite. Can we do without? 43 type limbo struct { 44 store billy.Database // Persistent data store for limboed blobs 45 46 index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids 47 groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks 48 } 49 50 // newLimbo opens and indexes a set of limboed blob transactions. 51 func newLimbo(datadir string) (*limbo, error) { 52 l := &limbo{ 53 index: make(map[common.Hash]uint64), 54 groups: make(map[uint64]map[uint64]common.Hash), 55 } 56 // Index all limboed blobs on disk and delete anything unprocessable 57 var fails []uint64 58 index := func(id uint64, size uint32, data []byte) { 59 if l.parseBlob(id, data) != nil { 60 fails = append(fails, id) 61 } 62 } 63 store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(), index) 64 if err != nil { 65 return nil, err 66 } 67 l.store = store 68 69 if len(fails) > 0 { 70 log.Warn("Dropping invalidated limboed blobs", "ids", fails) 71 for _, id := range fails { 72 if err := l.store.Delete(id); err != nil { 73 l.Close() 74 return nil, err 75 } 76 } 77 } 78 return l, nil 79 } 80 81 // Close closes down the underlying persistent store. 82 func (l *limbo) Close() error { 83 return l.store.Close() 84 } 85 86 // parseBlob is a callback method on limbo creation that gets called for each 87 // limboed blob on disk to create the in-memory metadata index. 88 func (l *limbo) parseBlob(id uint64, data []byte) error { 89 item := new(limboBlob) 90 if err := rlp.DecodeBytes(data, item); err != nil { 91 // This path is impossible unless the disk data representation changes 92 // across restarts. For that ever improbable case, recover gracefully 93 // by ignoring this data entry. 94 log.Error("Failed to decode blob limbo entry", "id", id, "err", err) 95 return err 96 } 97 if _, ok := l.index[item.TxHash]; ok { 98 // This path is impossible, unless due to a programming error a blob gets 99 // inserted into the limbo which was already part of if. Recover gracefully 100 // by ignoring this data entry. 101 log.Error("Dropping duplicate blob limbo entry", "owner", item.TxHash, "id", id) 102 return errors.New("duplicate blob") 103 } 104 l.index[item.TxHash] = id 105 106 if _, ok := l.groups[item.Block]; !ok { 107 l.groups[item.Block] = make(map[uint64]common.Hash) 108 } 109 l.groups[item.Block][id] = item.TxHash 110 111 return nil 112 } 113 114 // finalize evicts all blobs belonging to a recently finalized block or older. 115 func (l *limbo) finalize(final *types.Header) { 116 // Just in case there's no final block yet (network not yet merged, weird 117 // restart, sethead, etc), fail gracefully. 118 if final == nil { 119 log.Error("Nil finalized block cannot evict old blobs") 120 return 121 } 122 for block, ids := range l.groups { 123 if block > final.Number.Uint64() { 124 continue 125 } 126 for id, owner := range ids { 127 if err := l.store.Delete(id); err != nil { 128 log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err) 129 } 130 delete(l.index, owner) 131 } 132 delete(l.groups, block) 133 } 134 } 135 136 // push stores a new blob transaction into the limbo, waiting until finality for 137 // it to be automatically evicted. 138 func (l *limbo) push(tx *types.Transaction, block uint64) error { 139 // If the blobs are already tracked by the limbo, consider it a programming 140 // error. There's not much to do against it, but be loud. 141 if _, ok := l.index[tx.Hash()]; ok { 142 log.Error("Limbo cannot push already tracked blobs", "tx", tx) 143 return errors.New("already tracked blob transaction") 144 } 145 if err := l.setAndIndex(tx, block); err != nil { 146 log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err) 147 return err 148 } 149 return nil 150 } 151 152 // pull retrieves a previously pushed set of blobs back from the limbo, removing 153 // it at the same time. This method should be used when a previously included blob 154 // transaction gets reorged out. 155 func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) { 156 // If the blobs are not tracked by the limbo, there's not much to do. This 157 // can happen for example if a blob transaction is mined without pushing it 158 // into the network first. 159 id, ok := l.index[tx] 160 if !ok { 161 log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx) 162 return nil, errors.New("unseen blob transaction") 163 } 164 item, err := l.getAndDrop(id) 165 if err != nil { 166 log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err) 167 return nil, err 168 } 169 return item.Tx, nil 170 } 171 172 // update changes the block number under which a blob transaction is tracked. This 173 // method should be used when a reorg changes a transaction's inclusion block. 174 // 175 // The method may log errors for various unexpected scenarios but will not return 176 // any of it since there's no clear error case. Some errors may be due to coding 177 // issues, others caused by signers mining MEV stuff or swapping transactions. In 178 // all cases, the pool needs to continue operating. 179 func (l *limbo) update(txhash common.Hash, block uint64) { 180 // If the blobs are not tracked by the limbo, there's not much to do. This 181 // can happen for example if a blob transaction is mined without pushing it 182 // into the network first. 183 id, ok := l.index[txhash] 184 if !ok { 185 log.Trace("Limbo cannot update non-tracked blobs", "tx", txhash) 186 return 187 } 188 // If there was no change in the blob's inclusion block, don't mess around 189 // with heavy database operations. 190 if _, ok := l.groups[block][id]; ok { 191 log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block) 192 return 193 } 194 // Retrieve the old blobs from the data store and write them back with a new 195 // block number. IF anything fails, there's not much to do, go on. 196 item, err := l.getAndDrop(id) 197 if err != nil { 198 log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err) 199 return 200 } 201 if err := l.setAndIndex(item.Tx, block); err != nil { 202 log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err) 203 return 204 } 205 log.Trace("Blob transaction updated in limbo", "tx", txhash, "old-block", item.Block, "new-block", block) 206 } 207 208 // getAndDrop retrieves a blob item from the limbo store and deletes it both from 209 // the store and indices. 210 func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { 211 data, err := l.store.Get(id) 212 if err != nil { 213 return nil, err 214 } 215 item := new(limboBlob) 216 if err = rlp.DecodeBytes(data, item); err != nil { 217 return nil, err 218 } 219 delete(l.index, item.TxHash) 220 delete(l.groups[item.Block], id) 221 if len(l.groups[item.Block]) == 0 { 222 delete(l.groups, item.Block) 223 } 224 if err := l.store.Delete(id); err != nil { 225 return nil, err 226 } 227 return item, nil 228 } 229 230 // setAndIndex assembles a limbo blob database entry and stores it, also updating 231 // the in-memory indices. 232 func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error { 233 txhash := tx.Hash() 234 item := &limboBlob{ 235 TxHash: txhash, 236 Block: block, 237 Tx: tx, 238 } 239 data, err := rlp.EncodeToBytes(item) 240 if err != nil { 241 panic(err) // cannot happen runtime, dev error 242 } 243 id, err := l.store.Put(data) 244 if err != nil { 245 return err 246 } 247 l.index[txhash] = id 248 if _, ok := l.groups[block]; !ok { 249 l.groups[block] = make(map[uint64]common.Hash) 250 } 251 l.groups[block][id] = txhash 252 return nil 253 }