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