github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/core/tx_journal.go (about)

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