github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/maintenance.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  	errMissingFileContract = errors.New("storage proof submitted for non existing file contract")
    16  	errOutputAlreadyMature = errors.New("delayed siacoin output is already in the matured outputs set")
    17  	errPayoutsAlreadyPaid  = errors.New("payouts are already in the consensus set")
    18  	errStorageProofTiming  = errors.New("missed proof triggered for file contract that is not expiring")
    19  )
    20  
    21  // applyMinerPayouts adds a block's miner payouts to the consensus set as
    22  // delayed siacoin outputs.
    23  func applyMinerPayouts(tx *bolt.Tx, pb *processedBlock) {
    24  	for i := range pb.Block.MinerPayouts {
    25  		mpid := pb.Block.MinerPayoutID(uint64(i))
    26  		dscod := modules.DelayedSiacoinOutputDiff{
    27  			Direction:      modules.DiffApply,
    28  			ID:             mpid,
    29  			SiacoinOutput:  pb.Block.MinerPayouts[i],
    30  			MaturityHeight: pb.Height + types.MaturityDelay,
    31  		}
    32  		pb.DelayedSiacoinOutputDiffs = append(pb.DelayedSiacoinOutputDiffs, dscod)
    33  		commitDelayedSiacoinOutputDiff(tx, dscod, modules.DiffApply)
    34  	}
    35  }
    36  
    37  // applyMaturedSiacoinOutputs goes through the list of siacoin outputs that
    38  // have matured and adds them to the consensus set. This also updates the block
    39  // node diff set.
    40  func applyMaturedSiacoinOutputs(tx *bolt.Tx, pb *processedBlock) {
    41  	// Skip this step if the blockchain is not old enough to have maturing
    42  	// outputs.
    43  	if pb.Height < types.MaturityDelay {
    44  		return
    45  	}
    46  
    47  	// Iterate through the list of delayed siacoin outputs. Sometimes boltdb
    48  	// has trouble if you delete elements in a bucket while iterating through
    49  	// the bucket (and sometimes not - nondeterministic), so all of the
    50  	// elements are collected into an array and then deleted after the bucket
    51  	// scan is complete.
    52  	bucketID := append(prefixDSCO, encoding.Marshal(pb.Height)...)
    53  	var scods []modules.SiacoinOutputDiff
    54  	var dscods []modules.DelayedSiacoinOutputDiff
    55  	dbErr := tx.Bucket(bucketID).ForEach(func(idBytes, scoBytes []byte) error {
    56  		// Decode the key-value pair into an id and a siacoin output.
    57  		var id types.SiacoinOutputID
    58  		var sco types.SiacoinOutput
    59  		copy(id[:], idBytes)
    60  		encErr := encoding.Unmarshal(scoBytes, &sco)
    61  		if build.DEBUG && encErr != nil {
    62  			panic(encErr)
    63  		}
    64  
    65  		// Sanity check - the output should not already be in siacoinOuptuts.
    66  		if build.DEBUG && isSiacoinOutput(tx, id) {
    67  			panic(errOutputAlreadyMature)
    68  		}
    69  
    70  		// Add the output to the ConsensusSet and record the diff in the
    71  		// blockNode.
    72  		scod := modules.SiacoinOutputDiff{
    73  			Direction:     modules.DiffApply,
    74  			ID:            id,
    75  			SiacoinOutput: sco,
    76  		}
    77  		scods = append(scods, scod)
    78  
    79  		// Create the dscod and add it to the list of dscods that should be
    80  		// deleted.
    81  		dscod := modules.DelayedSiacoinOutputDiff{
    82  			Direction:      modules.DiffRevert,
    83  			ID:             id,
    84  			SiacoinOutput:  sco,
    85  			MaturityHeight: pb.Height,
    86  		}
    87  		dscods = append(dscods, dscod)
    88  		return nil
    89  	})
    90  	if build.DEBUG && dbErr != nil {
    91  		panic(dbErr)
    92  	}
    93  	for _, scod := range scods {
    94  		pb.SiacoinOutputDiffs = append(pb.SiacoinOutputDiffs, scod)
    95  		commitSiacoinOutputDiff(tx, scod, modules.DiffApply)
    96  	}
    97  	for _, dscod := range dscods {
    98  		pb.DelayedSiacoinOutputDiffs = append(pb.DelayedSiacoinOutputDiffs, dscod)
    99  		commitDelayedSiacoinOutputDiff(tx, dscod, modules.DiffApply)
   100  	}
   101  	deleteDSCOBucket(tx, pb.Height)
   102  }
   103  
   104  // applyMissedStorageProof adds the outputs and diffs that result from a file
   105  // contract expiring.
   106  func applyMissedStorageProof(tx *bolt.Tx, pb *processedBlock, fcid types.FileContractID) (dscods []modules.DelayedSiacoinOutputDiff, fcd modules.FileContractDiff) {
   107  	// Sanity checks.
   108  	fc, err := getFileContract(tx, fcid)
   109  	if build.DEBUG && err != nil {
   110  		panic(err)
   111  	}
   112  	if build.DEBUG {
   113  		// Check that the file contract in question expires at pb.Height.
   114  		if fc.WindowEnd != pb.Height {
   115  			panic(errStorageProofTiming)
   116  		}
   117  	}
   118  
   119  	// Add all of the outputs in the missed proof outputs to the consensus set.
   120  	for i, mpo := range fc.MissedProofOutputs {
   121  		// Sanity check - output should not already exist.
   122  		spoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(i))
   123  		if build.DEBUG && isSiacoinOutput(tx, spoid) {
   124  			panic(errPayoutsAlreadyPaid)
   125  		}
   126  
   127  		// Don't add the output if the value is zero.
   128  		dscod := modules.DelayedSiacoinOutputDiff{
   129  			Direction:      modules.DiffApply,
   130  			ID:             spoid,
   131  			SiacoinOutput:  mpo,
   132  			MaturityHeight: pb.Height + types.MaturityDelay,
   133  		}
   134  		dscods = append(dscods, dscod)
   135  	}
   136  
   137  	// Remove the file contract from the consensus set and record the diff in
   138  	// the blockNode.
   139  	fcd = modules.FileContractDiff{
   140  		Direction:    modules.DiffRevert,
   141  		ID:           fcid,
   142  		FileContract: fc,
   143  	}
   144  	return dscods, fcd
   145  }
   146  
   147  // applyFileContractMaintenance looks for all of the file contracts that have
   148  // expired without an appropriate storage proof, and calls 'applyMissedProof'
   149  // for the file contract.
   150  func applyFileContractMaintenance(tx *bolt.Tx, pb *processedBlock) {
   151  	// Get the bucket pointing to all of the expiring file contracts.
   152  	fceBucketID := append(prefixFCEX, encoding.Marshal(pb.Height)...)
   153  	fceBucket := tx.Bucket(fceBucketID)
   154  	// Finish if there are no expiring file contracts.
   155  	if fceBucket == nil {
   156  		return
   157  	}
   158  
   159  	var dscods []modules.DelayedSiacoinOutputDiff
   160  	var fcds []modules.FileContractDiff
   161  	err := fceBucket.ForEach(func(keyBytes, valBytes []byte) error {
   162  		var id types.FileContractID
   163  		copy(id[:], keyBytes)
   164  		amspDSCODS, fcd := applyMissedStorageProof(tx, pb, id)
   165  		fcds = append(fcds, fcd)
   166  		dscods = append(dscods, amspDSCODS...)
   167  		return nil
   168  	})
   169  	if build.DEBUG && err != nil {
   170  		panic(err)
   171  	}
   172  	for _, dscod := range dscods {
   173  		pb.DelayedSiacoinOutputDiffs = append(pb.DelayedSiacoinOutputDiffs, dscod)
   174  		commitDelayedSiacoinOutputDiff(tx, dscod, modules.DiffApply)
   175  	}
   176  	for _, fcd := range fcds {
   177  		pb.FileContractDiffs = append(pb.FileContractDiffs, fcd)
   178  		commitFileContractDiff(tx, fcd, modules.DiffApply)
   179  	}
   180  	err = tx.DeleteBucket(fceBucketID)
   181  	if build.DEBUG && err != nil {
   182  		panic(err)
   183  	}
   184  }
   185  
   186  // applyMaintenance applies block-level alterations to the consensus set.
   187  // Maintenance is applied after all of the transcations for the block have been
   188  // applied.
   189  func applyMaintenance(tx *bolt.Tx, pb *processedBlock) {
   190  	applyMinerPayouts(tx, pb)
   191  	applyMaturedSiacoinOutputs(tx, pb)
   192  	applyFileContractMaintenance(tx, pb)
   193  }