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  }