github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/changelog.go (about)

     1  package consensus
     2  
     3  // changelog.go implements a persistent changelog in the consenus database
     4  // tracking all of the atomic changes to the consensus set. The primary use of
     5  // the changelog is for subscribers that have persistence - instead of
     6  // subscribing from the very beginning and receiving all changes from genesis
     7  // each time the daemon starts up, the subscribers can start from the most
     8  // recent change that they are familiar with.
     9  //
    10  // The changelog is set up as a singley linked list where each change points
    11  // forward to the next change. In bolt, the key is a hash of the changeEntry
    12  // and the value is a struct containing the changeEntry and the key of the next
    13  // changeEntry. The empty hash key leads to the 'changeTail', which contains
    14  // the id of the most recent changeEntry.
    15  //
    16  // Initialization only needs to worry about creating the blank change entry,
    17  // the genesis block will call 'append' later on during initialization.
    18  
    19  import (
    20  	"github.com/NebulousLabs/Sia/build"
    21  	"github.com/NebulousLabs/Sia/crypto"
    22  	"github.com/NebulousLabs/Sia/encoding"
    23  	"github.com/NebulousLabs/Sia/modules"
    24  	"github.com/NebulousLabs/Sia/types"
    25  
    26  	"github.com/coreos/bbolt"
    27  )
    28  
    29  var (
    30  	// ChangeLog contains a list of atomic changes that have happened to the
    31  	// consensus set so that subscribers can subscribe from the most recent
    32  	// change they have seen.
    33  	ChangeLog = []byte("ChangeLog")
    34  
    35  	// ChangeLogTailID is a key that points to the id of the current changelog
    36  	// tail.
    37  	ChangeLogTailID = []byte("ChangeLogTailID")
    38  )
    39  
    40  type (
    41  	// changeEntry records a single atomic change to the consensus set.
    42  	changeEntry struct {
    43  		RevertedBlocks []types.BlockID
    44  		AppliedBlocks  []types.BlockID
    45  	}
    46  
    47  	// changeNode contains a change entry and a pointer to the next change
    48  	// entry, and is the object that gets stored in the database.
    49  	changeNode struct {
    50  		Entry changeEntry
    51  		Next  modules.ConsensusChangeID
    52  	}
    53  )
    54  
    55  // appendChangeLog adds a new change entry to the change log.
    56  func appendChangeLog(tx *bolt.Tx, ce changeEntry) error {
    57  	// Insert the change entry.
    58  	cl := tx.Bucket(ChangeLog)
    59  	ceid := ce.ID()
    60  	cn := changeNode{Entry: ce, Next: modules.ConsensusChangeID{}}
    61  	err := cl.Put(ceid[:], encoding.Marshal(cn))
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	// Update the tail node to point to the new change entry as the next entry.
    67  	var tailID modules.ConsensusChangeID
    68  	copy(tailID[:], cl.Get(ChangeLogTailID))
    69  	if tailID != (modules.ConsensusChangeID{}) {
    70  		// Get the old tail node.
    71  		var tailCN changeNode
    72  		tailCNBytes := cl.Get(tailID[:])
    73  		err = encoding.Unmarshal(tailCNBytes, &tailCN)
    74  		if err != nil {
    75  			return err
    76  		}
    77  
    78  		// Point the 'next' of the old tail node to the new tail node and
    79  		// insert.
    80  		tailCN.Next = ceid
    81  		err = cl.Put(tailID[:], encoding.Marshal(tailCN))
    82  		if err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	// Update the tail id.
    88  	err = cl.Put(ChangeLogTailID, ceid[:])
    89  	if err != nil {
    90  		return err
    91  	}
    92  	return nil
    93  }
    94  
    95  // getEntry returns the change entry with a given id, using a bool to indicate
    96  // existence.
    97  func getEntry(tx *bolt.Tx, id modules.ConsensusChangeID) (ce changeEntry, exists bool) {
    98  	var cn changeNode
    99  	cl := tx.Bucket(ChangeLog)
   100  	changeNodeBytes := cl.Get(id[:])
   101  	if changeNodeBytes == nil {
   102  		return changeEntry{}, false
   103  	}
   104  	err := encoding.Unmarshal(changeNodeBytes, &cn)
   105  	if build.DEBUG && err != nil {
   106  		panic(err)
   107  	}
   108  	return cn.Entry, true
   109  }
   110  
   111  // ID returns the id of a change entry.
   112  func (ce *changeEntry) ID() modules.ConsensusChangeID {
   113  	return modules.ConsensusChangeID(crypto.HashObject(ce))
   114  }
   115  
   116  // NextEntry returns the entry after the current entry.
   117  func (ce *changeEntry) NextEntry(tx *bolt.Tx) (nextEntry changeEntry, exists bool) {
   118  	// Get the change node associated with the provided change entry.
   119  	ceid := ce.ID()
   120  	var cn changeNode
   121  	cl := tx.Bucket(ChangeLog)
   122  	changeNodeBytes := cl.Get(ceid[:])
   123  	err := encoding.Unmarshal(changeNodeBytes, &cn)
   124  	if err != nil {
   125  		build.Critical(err)
   126  	}
   127  
   128  	return getEntry(tx, cn.Next)
   129  }
   130  
   131  // createChangeLog assumes that no change log exists and creates a new one.
   132  func (cs *ConsensusSet) createChangeLog(tx *bolt.Tx) error {
   133  	// Create the changelog bucket.
   134  	cl, err := tx.CreateBucket(ChangeLog)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	// Add the genesis block as the first entry of the change log.
   140  	ge := cs.genesisEntry()
   141  	geid := ge.ID()
   142  	cn := changeNode{
   143  		Entry: ge,
   144  		Next:  modules.ConsensusChangeID{},
   145  	}
   146  	err = cl.Put(geid[:], encoding.Marshal(cn))
   147  	if err != nil {
   148  		return err
   149  	}
   150  	err = cl.Put(ChangeLogTailID, geid[:])
   151  	if err != nil {
   152  		return err
   153  	}
   154  	return nil
   155  }
   156  
   157  // genesisEntry returns the id of the genesis block log entry.
   158  func (cs *ConsensusSet) genesisEntry() changeEntry {
   159  	return changeEntry{
   160  		AppliedBlocks: []types.BlockID{cs.blockRoot.Block.ID()},
   161  	}
   162  }