gitlab.com/SiaPrime/SiaPrime@v1.4.1/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  	bolt "github.com/coreos/bbolt"
    21  	"gitlab.com/SiaPrime/SiaPrime/build"
    22  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    23  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    24  	"gitlab.com/SiaPrime/SiaPrime/modules"
    25  	"gitlab.com/SiaPrime/SiaPrime/types"
    26  )
    27  
    28  var (
    29  	// ChangeLog contains a list of atomic changes that have happened to the
    30  	// consensus set so that subscribers can subscribe from the most recent
    31  	// change they have seen.
    32  	ChangeLog = []byte("ChangeLog")
    33  
    34  	// ChangeLogTailID is a key that points to the id of the current changelog
    35  	// tail.
    36  	ChangeLogTailID = []byte("ChangeLogTailID")
    37  )
    38  
    39  type (
    40  	// changeEntry records a single atomic change to the consensus set.
    41  	changeEntry struct {
    42  		RevertedBlocks []types.BlockID
    43  		AppliedBlocks  []types.BlockID
    44  	}
    45  
    46  	// changeNode contains a change entry and a pointer to the next change
    47  	// entry, and is the object that gets stored in the database.
    48  	changeNode struct {
    49  		Entry changeEntry
    50  		Next  modules.ConsensusChangeID
    51  	}
    52  )
    53  
    54  // appendChangeLog adds a new change entry to the change log.
    55  func appendChangeLog(tx *bolt.Tx, ce changeEntry) error {
    56  	// Insert the change entry.
    57  	cl := tx.Bucket(ChangeLog)
    58  	ceid := ce.ID()
    59  	cn := changeNode{Entry: ce, Next: modules.ConsensusChangeID{}}
    60  	err := cl.Put(ceid[:], encoding.Marshal(cn))
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	// Update the tail node to point to the new change entry as the next entry.
    66  	var tailID modules.ConsensusChangeID
    67  	copy(tailID[:], cl.Get(ChangeLogTailID))
    68  	if tailID != (modules.ConsensusChangeID{}) {
    69  		// Get the old tail node.
    70  		var tailCN changeNode
    71  		tailCNBytes := cl.Get(tailID[:])
    72  		err = encoding.Unmarshal(tailCNBytes, &tailCN)
    73  		if err != nil {
    74  			return err
    75  		}
    76  
    77  		// Point the 'next' of the old tail node to the new tail node and
    78  		// insert.
    79  		tailCN.Next = ceid
    80  		err = cl.Put(tailID[:], encoding.Marshal(tailCN))
    81  		if err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	// Update the tail id.
    87  	err = cl.Put(ChangeLogTailID, ceid[:])
    88  	if err != nil {
    89  		return err
    90  	}
    91  	return nil
    92  }
    93  
    94  // getEntry returns the change entry with a given id, using a bool to indicate
    95  // existence.
    96  func getEntry(tx *bolt.Tx, id modules.ConsensusChangeID) (ce changeEntry, exists bool) {
    97  	var cn changeNode
    98  	cl := tx.Bucket(ChangeLog)
    99  	changeNodeBytes := cl.Get(id[:])
   100  	if changeNodeBytes == nil {
   101  		return changeEntry{}, false
   102  	}
   103  	err := encoding.Unmarshal(changeNodeBytes, &cn)
   104  	if build.DEBUG && err != nil {
   105  		panic(err)
   106  	}
   107  	return cn.Entry, true
   108  }
   109  
   110  // ID returns the id of a change entry.
   111  func (ce *changeEntry) ID() modules.ConsensusChangeID {
   112  	return modules.ConsensusChangeID(crypto.HashObject(ce))
   113  }
   114  
   115  // NextEntry returns the entry after the current entry.
   116  func (ce *changeEntry) NextEntry(tx *bolt.Tx) (nextEntry changeEntry, exists bool) {
   117  	// Get the change node associated with the provided change entry.
   118  	ceid := ce.ID()
   119  	var cn changeNode
   120  	cl := tx.Bucket(ChangeLog)
   121  	changeNodeBytes := cl.Get(ceid[:])
   122  	err := encoding.Unmarshal(changeNodeBytes, &cn)
   123  	if err != nil {
   124  		build.Critical(err)
   125  	}
   126  
   127  	return getEntry(tx, cn.Next)
   128  }
   129  
   130  // createChangeLog assumes that no change log exists and creates a new one.
   131  func (cs *ConsensusSet) createChangeLog(tx *bolt.Tx) error {
   132  	// Create the changelog bucket.
   133  	cl, err := tx.CreateBucket(ChangeLog)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	// Add the genesis block as the first entry of the change log.
   139  	ge := cs.genesisEntry()
   140  	geid := ge.ID()
   141  	cn := changeNode{
   142  		Entry: ge,
   143  		Next:  modules.ConsensusChangeID{},
   144  	}
   145  	err = cl.Put(geid[:], encoding.Marshal(cn))
   146  	if err != nil {
   147  		return err
   148  	}
   149  	err = cl.Put(ChangeLogTailID, geid[:])
   150  	if err != nil {
   151  		return err
   152  	}
   153  	return nil
   154  }
   155  
   156  // genesisEntry returns the id of the genesis block log entry.
   157  func (cs *ConsensusSet) genesisEntry() changeEntry {
   158  	return changeEntry{
   159  		AppliedBlocks: []types.BlockID{cs.blockRoot.Block.ID()},
   160  	}
   161  }