github.com/klaytn/klaytn@v1.12.1/blockchain/tx_journal.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2017 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from core/tx_journal.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package blockchain 22 23 import ( 24 "errors" 25 "io" 26 "os" 27 28 "github.com/klaytn/klaytn/blockchain/types" 29 "github.com/klaytn/klaytn/common" 30 "github.com/klaytn/klaytn/rlp" 31 ) 32 33 // errNoActiveJournal is returned if a transaction is attempted to be inserted 34 // into the journal, but no such file is currently open. 35 var errNoActiveJournal = errors.New("no active journal") 36 37 // devNull is a WriteCloser that just discards anything written into it. Its 38 // goal is to allow the transaction journal to write into a fake journal when 39 // loading transactions on startup without printing warnings due to no file 40 // being readt for write. 41 type devNull struct{} 42 43 func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil } 44 func (*devNull) Close() error { return nil } 45 46 // txJournal is a rotating log of transactions with the aim of storing locally 47 // created transactions to allow non-executed ones to survive node restarts. 48 type txJournal struct { 49 path string // Filesystem path to store the transactions at 50 writer io.WriteCloser // Output stream to write new transactions into 51 } 52 53 // newTxJournal creates a new transaction journal to 54 func newTxJournal(path string) *txJournal { 55 return &txJournal{ 56 path: path, 57 } 58 } 59 60 // load parses a transaction journal dump from disk, loading its contents into 61 // the specified pool. 62 func (journal *txJournal) load(add func([]*types.Transaction) []error) error { 63 // Skip the parsing if the journal file doens't exist at all 64 if _, err := os.Stat(journal.path); os.IsNotExist(err) { 65 return nil 66 } 67 // Open the journal for loading any past transactions 68 input, err := os.Open(journal.path) 69 if err != nil { 70 return err 71 } 72 defer input.Close() 73 74 // Temporarily discard any journal additions (don't double add on load) 75 journal.writer = new(devNull) 76 defer func() { journal.writer = nil }() 77 78 // Inject all transactions from the journal into the pool 79 stream := rlp.NewStream(input, 0) 80 total, dropped := 0, 0 81 82 // Create a method to load a limited batch of transactions and bump the 83 // appropriate progress counters. Then use this method to load all the 84 // journalled transactions in small-ish batches. 85 loadBatch := func(txs types.Transactions) { 86 for _, err := range add(txs) { 87 if err != nil { 88 logger.Debug("Failed to add journaled transaction", "err", err) 89 dropped++ 90 } 91 } 92 } 93 var ( 94 failure error 95 batch types.Transactions 96 ) 97 for { 98 // Parse the next transaction and terminate on error 99 tx := new(types.Transaction) 100 if err = stream.Decode(tx); err != nil { 101 if err != io.EOF { 102 failure = err 103 } 104 if batch.Len() > 0 { 105 loadBatch(batch) 106 } 107 break 108 } 109 // New transaction parsed, queue up for later, import if threnshold is reached 110 total++ 111 112 if batch = append(batch, tx); batch.Len() > 1024 { 113 loadBatch(batch) 114 batch = batch[:0] 115 } 116 } 117 logger.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped) 118 119 return failure 120 } 121 122 // insert adds the specified transaction to the local disk journal. 123 func (journal *txJournal) insert(tx *types.Transaction) error { 124 if journal.writer == nil { 125 return errNoActiveJournal 126 } 127 if err := rlp.Encode(journal.writer, tx); err != nil { 128 return err 129 } 130 return nil 131 } 132 133 // rotate regenerates the transaction journal based on the current contents of 134 // the transaction pool. 135 func (journal *txJournal) rotate(all map[common.Address]types.Transactions, signer types.Signer) error { 136 // Close the current journal (if any is open) 137 if journal.writer != nil { 138 if err := journal.writer.Close(); err != nil { 139 return err 140 } 141 journal.writer = nil 142 } 143 // Generate a new journal with the contents of the current pool 144 replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) 145 if err != nil { 146 return err 147 } 148 journaled := 0 149 150 txSetByTime := types.NewTransactionsByTimeAndNonce(signer, all) 151 for tx := txSetByTime.Peek(); tx != nil; tx = txSetByTime.Peek() { 152 if err = rlp.Encode(replacement, tx); err != nil { 153 replacement.Close() 154 return err 155 } 156 journaled++ 157 txSetByTime.Shift() 158 } 159 160 replacement.Close() 161 162 // Replace the live journal with the newly generated one 163 if err = os.Rename(journal.path+".new", journal.path); err != nil { 164 return err 165 } 166 sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0o755) 167 if err != nil { 168 return err 169 } 170 journal.writer = sink 171 logger.Info("Regenerated local transaction journal", "transactions", journaled, "accounts", len(all)) 172 173 return nil 174 } 175 176 // close flushes the transaction journal contents to disk and closes the file. 177 func (journal *txJournal) close() error { 178 var err error 179 180 if journal.writer != nil { 181 err = journal.writer.Close() 182 journal.writer = nil 183 } 184 return err 185 }