github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/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  	"io/fs"
    23  	"os"
    24  
    25  	"github.com/ethxdao/go-ethereum/common"
    26  	"github.com/ethxdao/go-ethereum/core/types"
    27  	"github.com/ethxdao/go-ethereum/log"
    28  	"github.com/ethxdao/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  // txJournal 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 txJournal 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) *txJournal {
    53  	return &txJournal{
    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 *txJournal) 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 *txJournal) 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 *txJournal) 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 *txJournal) 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  }