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