github.com/cgcardona/r-subnet-evm@v0.1.5/core/tx_journal.go (about)

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