github.com/klaytn/klaytn@v1.12.1/node/sc/bridgepool/bridge_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 bridgepool
    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  // bridgeTxJournal 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 bridgeTxJournal 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  // newBridgeTxJournal creates a new transaction journal to
    54  func newBridgeTxJournal(path string) *bridgeTxJournal {
    55  	return &bridgeTxJournal{
    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 *bridgeTxJournal) 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 *bridgeTxJournal) 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 *bridgeTxJournal) rotate(all map[common.Address]types.Transactions) 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  	for _, txs := range all {
   150  		for _, tx := range txs {
   151  			if err = rlp.Encode(replacement, tx); err != nil {
   152  				replacement.Close()
   153  				return err
   154  			}
   155  		}
   156  		journaled += len(txs)
   157  	}
   158  	replacement.Close()
   159  
   160  	// Replace the live journal with the newly generated one
   161  	if err = os.Rename(journal.path+".new", journal.path); err != nil {
   162  		return err
   163  	}
   164  	sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0o755)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	journal.writer = sink
   169  	logger.Info("Regenerated local transaction journal", "transactions", journaled, "accounts", len(all))
   170  
   171  	return nil
   172  }
   173  
   174  // close flushes the transaction journal contents to disk and closes the file.
   175  func (journal *bridgeTxJournal) close() error {
   176  	var err error
   177  
   178  	if journal.writer != nil {
   179  		err = journal.writer.Close()
   180  		journal.writer = nil
   181  	}
   182  	return err
   183  }