github.com/NebulousLabs/Sia@v1.3.7/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/coreos/bbolt" 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("committing 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).Equals(sfpd.Previous) { 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).Equals(sfpd.Adjusted) { 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 }