github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/diffs.go (about)

     1  package consensus
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/encoding"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/types"
    10  
    11  	"github.com/NebulousLabs/bolt"
    12  )
    13  
    14  var (
    15  	errApplySiafundPoolDiffMismatch  = errors.New("committing a siafund pool diff with an invalid 'previous' field")
    16  	errDiffsNotGenerated             = errors.New("applying diff set before generating errors")
    17  	errInvalidSuccessor              = errors.New("generating diffs for a block that's an invalid successsor to the current block")
    18  	errNegativePoolAdjustment        = errors.New("committing a siafund pool diff with a negative adjustment")
    19  	errNonApplySiafundPoolDiff       = errors.New("commiting a siafund pool diff that doesn't have the 'apply' direction")
    20  	errRevertSiafundPoolDiffMismatch = errors.New("committing a siafund pool diff with an invalid 'adjusted' field")
    21  	errWrongAppliedDiffSet           = errors.New("applying a diff set that isn't the current block")
    22  	errWrongRevertDiffSet            = errors.New("reverting a diff set that isn't the current block")
    23  )
    24  
    25  // commitDiffSetSanity performs a series of sanity checks before committing a
    26  // diff set.
    27  func commitDiffSetSanity(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
    28  	// This function is purely sanity checks.
    29  	if !build.DEBUG {
    30  		return
    31  	}
    32  
    33  	// Diffs should have already been generated for this node.
    34  	if !pb.DiffsGenerated {
    35  		panic(errDiffsNotGenerated)
    36  	}
    37  
    38  	// Current node must be the input node's parent if applying, and
    39  	// current node must be the input node if reverting.
    40  	if dir == modules.DiffApply {
    41  		parent, err := getBlockMap(tx, pb.Block.ParentID)
    42  		if build.DEBUG && err != nil {
    43  			panic(err)
    44  		}
    45  		if parent.Block.ID() != currentBlockID(tx) {
    46  			panic(errWrongAppliedDiffSet)
    47  		}
    48  	} else {
    49  		if pb.Block.ID() != currentBlockID(tx) {
    50  			panic(errWrongRevertDiffSet)
    51  		}
    52  	}
    53  }
    54  
    55  // commitSiacoinOutputDiff applies or reverts a SiacoinOutputDiff.
    56  func commitSiacoinOutputDiff(tx *bolt.Tx, scod modules.SiacoinOutputDiff, dir modules.DiffDirection) {
    57  	if scod.Direction == dir {
    58  		addSiacoinOutput(tx, scod.ID, scod.SiacoinOutput)
    59  	} else {
    60  		removeSiacoinOutput(tx, scod.ID)
    61  	}
    62  }
    63  
    64  // commitFileContractDiff applies or reverts a FileContractDiff.
    65  func commitFileContractDiff(tx *bolt.Tx, fcd modules.FileContractDiff, dir modules.DiffDirection) {
    66  	if fcd.Direction == dir {
    67  		addFileContract(tx, fcd.ID, fcd.FileContract)
    68  	} else {
    69  		removeFileContract(tx, fcd.ID)
    70  	}
    71  }
    72  
    73  // commitSiafundOutputDiff applies or reverts a Siafund output diff.
    74  func commitSiafundOutputDiff(tx *bolt.Tx, sfod modules.SiafundOutputDiff, dir modules.DiffDirection) {
    75  	if sfod.Direction == dir {
    76  		addSiafundOutput(tx, sfod.ID, sfod.SiafundOutput)
    77  	} else {
    78  		removeSiafundOutput(tx, sfod.ID)
    79  	}
    80  }
    81  
    82  // commitDelayedSiacoinOutputDiff applies or reverts a delayedSiacoinOutputDiff.
    83  func commitDelayedSiacoinOutputDiff(tx *bolt.Tx, dscod modules.DelayedSiacoinOutputDiff, dir modules.DiffDirection) {
    84  	if dscod.Direction == dir {
    85  		addDSCO(tx, dscod.MaturityHeight, dscod.ID, dscod.SiacoinOutput)
    86  	} else {
    87  		removeDSCO(tx, dscod.MaturityHeight, dscod.ID)
    88  	}
    89  }
    90  
    91  // commitSiafundPoolDiff applies or reverts a SiafundPoolDiff.
    92  func commitSiafundPoolDiff(tx *bolt.Tx, sfpd modules.SiafundPoolDiff, dir modules.DiffDirection) {
    93  	// Sanity check - siafund pool should only ever increase.
    94  	if build.DEBUG {
    95  		if sfpd.Adjusted.Cmp(sfpd.Previous) < 0 {
    96  			panic(errNegativePoolAdjustment)
    97  		}
    98  		if sfpd.Direction != modules.DiffApply {
    99  			panic(errNonApplySiafundPoolDiff)
   100  		}
   101  	}
   102  
   103  	if dir == modules.DiffApply {
   104  		// Sanity check - sfpd.Previous should equal the current siafund pool.
   105  		if build.DEBUG && getSiafundPool(tx).Cmp(sfpd.Previous) != 0 {
   106  			panic(errApplySiafundPoolDiffMismatch)
   107  		}
   108  		setSiafundPool(tx, sfpd.Adjusted)
   109  	} else {
   110  		// Sanity check - sfpd.Adjusted should equal the current siafund pool.
   111  		if build.DEBUG && getSiafundPool(tx).Cmp(sfpd.Adjusted) != 0 {
   112  			panic(errRevertSiafundPoolDiffMismatch)
   113  		}
   114  		setSiafundPool(tx, sfpd.Previous)
   115  	}
   116  }
   117  
   118  // createUpcomingDelayeOutputdMaps creates the delayed siacoin output maps that
   119  // will be used when applying delayed siacoin outputs in the diff set.
   120  func createUpcomingDelayedOutputMaps(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
   121  	if dir == modules.DiffApply {
   122  		createDSCOBucket(tx, pb.Height+types.MaturityDelay)
   123  	} else if pb.Height >= types.MaturityDelay {
   124  		createDSCOBucket(tx, pb.Height)
   125  	}
   126  }
   127  
   128  // commitNodeDiffs commits all of the diffs in a block node.
   129  func commitNodeDiffs(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
   130  	if dir == modules.DiffApply {
   131  		for _, scod := range pb.SiacoinOutputDiffs {
   132  			commitSiacoinOutputDiff(tx, scod, dir)
   133  		}
   134  		for _, fcd := range pb.FileContractDiffs {
   135  			commitFileContractDiff(tx, fcd, dir)
   136  		}
   137  		for _, sfod := range pb.SiafundOutputDiffs {
   138  			commitSiafundOutputDiff(tx, sfod, dir)
   139  		}
   140  		for _, dscod := range pb.DelayedSiacoinOutputDiffs {
   141  			commitDelayedSiacoinOutputDiff(tx, dscod, dir)
   142  		}
   143  		for _, sfpd := range pb.SiafundPoolDiffs {
   144  			commitSiafundPoolDiff(tx, sfpd, dir)
   145  		}
   146  	} else {
   147  		for i := len(pb.SiacoinOutputDiffs) - 1; i >= 0; i-- {
   148  			commitSiacoinOutputDiff(tx, pb.SiacoinOutputDiffs[i], dir)
   149  		}
   150  		for i := len(pb.FileContractDiffs) - 1; i >= 0; i-- {
   151  			commitFileContractDiff(tx, pb.FileContractDiffs[i], dir)
   152  		}
   153  		for i := len(pb.SiafundOutputDiffs) - 1; i >= 0; i-- {
   154  			commitSiafundOutputDiff(tx, pb.SiafundOutputDiffs[i], dir)
   155  		}
   156  		for i := len(pb.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- {
   157  			commitDelayedSiacoinOutputDiff(tx, pb.DelayedSiacoinOutputDiffs[i], dir)
   158  		}
   159  		for i := len(pb.SiafundPoolDiffs) - 1; i >= 0; i-- {
   160  			commitSiafundPoolDiff(tx, pb.SiafundPoolDiffs[i], dir)
   161  		}
   162  	}
   163  }
   164  
   165  // deleteObsoleteDelayedOutputMaps deletes the delayed siacoin output maps that
   166  // are no longer in use.
   167  func deleteObsoleteDelayedOutputMaps(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
   168  	// There are no outputs that mature in the first MaturityDelay blocks.
   169  	if dir == modules.DiffApply && pb.Height >= types.MaturityDelay {
   170  		deleteDSCOBucket(tx, pb.Height)
   171  	} else if dir == modules.DiffRevert {
   172  		deleteDSCOBucket(tx, pb.Height+types.MaturityDelay)
   173  	}
   174  }
   175  
   176  // updateCurrentPath updates the current path after applying a diff set.
   177  func updateCurrentPath(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
   178  	// Update the current path.
   179  	if dir == modules.DiffApply {
   180  		pushPath(tx, pb.Block.ID())
   181  	} else {
   182  		popPath(tx)
   183  	}
   184  }
   185  
   186  // commitDiffSet applies or reverts the diffs in a blockNode.
   187  func commitDiffSet(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
   188  	// Sanity checks - there are a few so they were moved to another function.
   189  	if build.DEBUG {
   190  		commitDiffSetSanity(tx, pb, dir)
   191  	}
   192  
   193  	createUpcomingDelayedOutputMaps(tx, pb, dir)
   194  	commitNodeDiffs(tx, pb, dir)
   195  	deleteObsoleteDelayedOutputMaps(tx, pb, dir)
   196  	updateCurrentPath(tx, pb, dir)
   197  }
   198  
   199  // generateAndApplyDiff will verify the block and then integrate it into the
   200  // consensus state. These two actions must happen at the same time because
   201  // transactions are allowed to depend on each other. We can't be sure that a
   202  // transaction is valid unless we have applied all of the previous transactions
   203  // in the block, which means we need to apply while we verify.
   204  func generateAndApplyDiff(tx *bolt.Tx, pb *processedBlock) error {
   205  	// Sanity check - the block being applied should have the current block as
   206  	// a parent.
   207  	if build.DEBUG && pb.Block.ParentID != currentBlockID(tx) {
   208  		panic(errInvalidSuccessor)
   209  	}
   210  
   211  	// Create the bucket to hold all of the delayed siacoin outputs created by
   212  	// transactions this block. Needs to happen before any transactions are
   213  	// applied.
   214  	createDSCOBucket(tx, pb.Height+types.MaturityDelay)
   215  
   216  	// Validate and apply each transaction in the block. They cannot be
   217  	// validated all at once because some transactions may not be valid until
   218  	// previous transactions have been applied.
   219  	for _, txn := range pb.Block.Transactions {
   220  		err := validTransaction(tx, txn)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		applyTransaction(tx, pb, txn)
   225  	}
   226  
   227  	// After all of the transactions have been applied, 'maintenance' is
   228  	// applied on the block. This includes adding any outputs that have reached
   229  	// maturity, applying any contracts with missed storage proofs, and adding
   230  	// the miner payouts to the list of delayed outputs.
   231  	applyMaintenance(tx, pb)
   232  
   233  	// DiffsGenerated are only set to true after the block has been fully
   234  	// validated and integrated. This is required to prevent later blocks from
   235  	// being accepted on top of an invalid block - if the consensus set ever
   236  	// forks over an invalid block, 'DiffsGenerated' will be set to 'false',
   237  	// requiring validation to occur again. when 'DiffsGenerated' is set to
   238  	// true, validation is skipped, therefore the flag should only be set to
   239  	// true on fully validated blocks.
   240  	pb.DiffsGenerated = true
   241  
   242  	// Add the block to the current path and block map.
   243  	bid := pb.Block.ID()
   244  	blockMap := tx.Bucket(BlockMap)
   245  	updateCurrentPath(tx, pb, modules.DiffApply)
   246  
   247  	// Sanity check preparation - set the consensus hash at this height so that
   248  	// during reverting a check can be performed to assure consistency when
   249  	// adding and removing blocks. Must happen after the block is added to the
   250  	// path.
   251  	if build.DEBUG {
   252  		pb.ConsensusChecksum = consensusChecksum(tx)
   253  	}
   254  
   255  	return blockMap.Put(bid[:], encoding.Marshal(*pb))
   256  }