github.com/klaytn/klaytn@v1.12.1/blockchain/tx_journal.go (about)

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