github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/core/txpool/journal.go (about) 1 // Copyright 2017 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 "io" 22 "io/fs" 23 "os" 24 25 "github.com/tirogen/go-ethereum/common" 26 "github.com/tirogen/go-ethereum/core/types" 27 "github.com/tirogen/go-ethereum/log" 28 "github.com/tirogen/go-ethereum/rlp" 29 ) 30 31 // errNoActiveJournal is returned if a transaction is attempted to be inserted 32 // into the journal, but no such file is currently open. 33 var errNoActiveJournal = errors.New("no active journal") 34 35 // devNull is a WriteCloser that just discards anything written into it. Its 36 // goal is to allow the transaction journal to write into a fake journal when 37 // loading transactions on startup without printing warnings due to no file 38 // being read for write. 39 type devNull struct{} 40 41 func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil } 42 func (*devNull) Close() error { return nil } 43 44 // journal is a rotating log of transactions with the aim of storing locally 45 // created transactions to allow non-executed ones to survive node restarts. 46 type journal struct { 47 path string // Filesystem path to store the transactions at 48 writer io.WriteCloser // Output stream to write new transactions into 49 } 50 51 // newTxJournal creates a new transaction journal to 52 func newTxJournal(path string) *journal { 53 return &journal{ 54 path: path, 55 } 56 } 57 58 // load parses a transaction journal dump from disk, loading its contents into 59 // the specified pool. 60 func (journal *journal) load(add func([]*types.Transaction) []error) error { 61 // Open the journal for loading any past transactions 62 input, err := os.Open(journal.path) 63 if errors.Is(err, fs.ErrNotExist) { 64 // Skip the parsing if the journal file doesn't exist at all 65 return nil 66 } 67 if err != nil { 68 return err 69 } 70 defer input.Close() 71 72 // Temporarily discard any journal additions (don't double add on load) 73 journal.writer = new(devNull) 74 defer func() { journal.writer = nil }() 75 76 // Inject all transactions from the journal into the pool 77 stream := rlp.NewStream(input, 0) 78 total, dropped := 0, 0 79 80 // Create a method to load a limited batch of transactions and bump the 81 // appropriate progress counters. Then use this method to load all the 82 // journaled transactions in small-ish batches. 83 loadBatch := func(txs types.Transactions) { 84 for _, err := range add(txs) { 85 if err != nil { 86 log.Debug("Failed to add journaled transaction", "err", err) 87 dropped++ 88 } 89 } 90 } 91 var ( 92 failure error 93 batch types.Transactions 94 ) 95 for { 96 // Parse the next transaction and terminate on error 97 tx := new(types.Transaction) 98 if err = stream.Decode(tx); err != nil { 99 if err != io.EOF { 100 failure = err 101 } 102 if batch.Len() > 0 { 103 loadBatch(batch) 104 } 105 break 106 } 107 // New transaction parsed, queue up for later, import if threshold is reached 108 total++ 109 110 if batch = append(batch, tx); batch.Len() > 1024 { 111 loadBatch(batch) 112 batch = batch[:0] 113 } 114 } 115 log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped) 116 117 return failure 118 } 119 120 // insert adds the specified transaction to the local disk journal. 121 func (journal *journal) insert(tx *types.Transaction) error { 122 if journal.writer == nil { 123 return errNoActiveJournal 124 } 125 if err := rlp.Encode(journal.writer, tx); err != nil { 126 return err 127 } 128 return nil 129 } 130 131 // rotate regenerates the transaction journal based on the current contents of 132 // the transaction pool. 133 func (journal *journal) rotate(all map[common.Address]types.Transactions) error { 134 // Close the current journal (if any is open) 135 if journal.writer != nil { 136 if err := journal.writer.Close(); err != nil { 137 return err 138 } 139 journal.writer = nil 140 } 141 // Generate a new journal with the contents of the current pool 142 replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 143 if err != nil { 144 return err 145 } 146 journaled := 0 147 for _, txs := range all { 148 for _, tx := range txs { 149 if err = rlp.Encode(replacement, tx); err != nil { 150 replacement.Close() 151 return err 152 } 153 } 154 journaled += len(txs) 155 } 156 replacement.Close() 157 158 // Replace the live journal with the newly generated one 159 if err = os.Rename(journal.path+".new", journal.path); err != nil { 160 return err 161 } 162 sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0644) 163 if err != nil { 164 return err 165 } 166 journal.writer = sink 167 log.Info("Regenerated local transaction journal", "transactions", journaled, "accounts", len(all)) 168 169 return nil 170 } 171 172 // close flushes the transaction journal contents to disk and closes the file. 173 func (journal *journal) close() error { 174 var err error 175 176 if journal.writer != nil { 177 err = journal.writer.Close() 178 journal.writer = nil 179 } 180 return err 181 }