github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/validtransaction.go (about) 1 package consensus 2 3 import ( 4 "errors" 5 "math/big" 6 7 "github.com/NebulousLabs/Sia/build" 8 "github.com/NebulousLabs/Sia/crypto" 9 "github.com/NebulousLabs/Sia/encoding" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/types" 12 13 "github.com/NebulousLabs/bolt" 14 ) 15 16 var ( 17 errAlteredRevisionPayouts = errors.New("file contract revision has altered payout volume") 18 errInvalidStorageProof = errors.New("provided storage proof is invalid") 19 errLateRevision = errors.New("file contract revision submitted after deadline") 20 errLowRevisionNumber = errors.New("transaction has a file contract with an outdated revision number") 21 errMissingSiacoinOutput = errors.New("transaction spends a nonexisting siacoin output") 22 errMissingSiafundOutput = errors.New("transaction spends a nonexisting siafund output") 23 errSiacoinInputOutputMismatch = errors.New("siacoin inputs do not equal siacoin outputs for transaction") 24 errSiafundInputOutputMismatch = errors.New("siafund inputs do not equal siafund outputs for transaction") 25 errUnfinishedFileContract = errors.New("file contract window has not yet openend") 26 errUnrecognizedFileContractID = errors.New("cannot fetch storage proof segment for unknown file contract") 27 errWrongUnlockConditions = errors.New("transaction contains incorrect unlock conditions") 28 ) 29 30 // validSiacoins checks that the siacoin inputs and outputs are valid in the 31 // context of the current consensus set. 32 func validSiacoins(tx *bolt.Tx, t types.Transaction) error { 33 scoBucket := tx.Bucket(SiacoinOutputs) 34 var inputSum types.Currency 35 for _, sci := range t.SiacoinInputs { 36 // Check that the input spends an existing output. 37 scoBytes := scoBucket.Get(sci.ParentID[:]) 38 if scoBytes == nil { 39 return errMissingSiacoinOutput 40 } 41 42 // Check that the unlock conditions match the required unlock hash. 43 var sco types.SiacoinOutput 44 err := encoding.Unmarshal(scoBytes, &sco) 45 if build.DEBUG && err != nil { 46 panic(err) 47 } 48 if sci.UnlockConditions.UnlockHash() != sco.UnlockHash { 49 return errWrongUnlockConditions 50 } 51 52 inputSum = inputSum.Add(sco.Value) 53 } 54 if inputSum.Cmp(t.SiacoinOutputSum()) != 0 { 55 return errSiacoinInputOutputMismatch 56 } 57 return nil 58 } 59 60 // storageProofSegment returns the index of the segment that needs to be proven 61 // exists in a file contract. 62 func storageProofSegment(tx *bolt.Tx, fcid types.FileContractID) (uint64, error) { 63 // Check that the parent file contract exists. 64 fcBucket := tx.Bucket(FileContracts) 65 fcBytes := fcBucket.Get(fcid[:]) 66 if fcBytes == nil { 67 return 0, errUnrecognizedFileContractID 68 } 69 70 // Decode the file contract. 71 var fc types.FileContract 72 err := encoding.Unmarshal(fcBytes, &fc) 73 if build.DEBUG && err != nil { 74 panic(err) 75 } 76 77 // Get the trigger block id. 78 blockPath := tx.Bucket(BlockPath) 79 triggerHeight := fc.WindowStart - 1 80 if triggerHeight > blockHeight(tx) { 81 return 0, errUnfinishedFileContract 82 } 83 var triggerID types.BlockID 84 copy(triggerID[:], blockPath.Get(encoding.EncUint64(uint64(triggerHeight)))) 85 86 // Get the index by appending the file contract ID to the trigger block and 87 // taking the hash, then converting the hash to a numerical value and 88 // modding it against the number of segments in the file. The result is a 89 // random number in range [0, numSegments]. The probability is very 90 // slightly weighted towards the beginning of the file, but because the 91 // size difference between the number of segments and the random number 92 // being modded, the difference is too small to make any practical 93 // difference. 94 seed := crypto.HashAll(triggerID, fcid) 95 numSegments := int64(crypto.CalculateLeaves(fc.FileSize)) 96 seedInt := new(big.Int).SetBytes(seed[:]) 97 index := seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64() 98 return index, nil 99 } 100 101 // validStorageProofs checks that the storage proofs are valid in the context 102 // of the consensus set. 103 func validStorageProofs(tx *bolt.Tx, t types.Transaction) error { 104 for _, sp := range t.StorageProofs { 105 // Check that the storage proof itself is valid. 106 segmentIndex, err := storageProofSegment(tx, sp.ParentID) 107 if err != nil { 108 return err 109 } 110 111 fc, err := getFileContract(tx, sp.ParentID) 112 if err != nil { 113 return err 114 } 115 leaves := crypto.CalculateLeaves(fc.FileSize) 116 segmentLen := uint64(crypto.SegmentSize) 117 if segmentIndex == leaves-1 { 118 segmentLen = fc.FileSize % crypto.SegmentSize 119 } 120 121 // COMPATv0.4.0 122 // 123 // Fixing the padding situation resulted in a hardfork. The below code 124 // will stop the hardfork from triggering before block 21,000. 125 if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) { 126 segmentLen = uint64(crypto.SegmentSize) 127 } 128 129 verified := crypto.VerifySegment( 130 sp.Segment[:segmentLen], 131 sp.HashSet, 132 leaves, 133 segmentIndex, 134 fc.FileMerkleRoot, 135 ) 136 if !verified { 137 return errInvalidStorageProof 138 } 139 } 140 141 return nil 142 } 143 144 // validFileContractRevision checks that each file contract revision is valid 145 // in the context of the current consensus set. 146 func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error { 147 for _, fcr := range t.FileContractRevisions { 148 fc, err := getFileContract(tx, fcr.ParentID) 149 if err != nil { 150 return err 151 } 152 153 // Check that the height is less than fc.WindowStart - revisions are 154 // not allowed to be submitted once the storage proof window has 155 // opened. This reduces complexity for unconfirmed transactions. 156 if blockHeight(tx) > fc.WindowStart { 157 return errLateRevision 158 } 159 160 // Check that the revision number of the revision is greater than the 161 // revision number of the existing file contract. 162 if fc.RevisionNumber >= fcr.NewRevisionNumber { 163 return errLowRevisionNumber 164 } 165 166 // Check that the unlock conditions match the unlock hash. 167 if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash { 168 return errWrongUnlockConditions 169 } 170 171 // Check that the payout of the revision matches the payout of the 172 // original, and that the payouts match eachother. 173 var validPayout, missedPayout, oldPayout types.Currency 174 for _, output := range fcr.NewValidProofOutputs { 175 validPayout = validPayout.Add(output.Value) 176 } 177 for _, output := range fcr.NewMissedProofOutputs { 178 missedPayout = missedPayout.Add(output.Value) 179 } 180 for _, output := range fc.ValidProofOutputs { 181 oldPayout = oldPayout.Add(output.Value) 182 } 183 if validPayout.Cmp(oldPayout) != 0 { 184 return errAlteredRevisionPayouts 185 } 186 if missedPayout.Cmp(oldPayout) != 0 { 187 return errAlteredRevisionPayouts 188 } 189 } 190 return nil 191 } 192 193 // validSiafunds checks that the siafund portions of the transaction are valid 194 // in the context of the consensus set. 195 func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) { 196 // Compare the number of input siafunds to the output siafunds. 197 var siafundInputSum types.Currency 198 var siafundOutputSum types.Currency 199 for _, sfi := range t.SiafundInputs { 200 sfo, err := getSiafundOutput(tx, sfi.ParentID) 201 if err != nil { 202 return err 203 } 204 205 // Check the unlock conditions match the unlock hash. 206 if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash { 207 return errWrongUnlockConditions 208 } 209 210 siafundInputSum = siafundInputSum.Add(sfo.Value) 211 } 212 for _, sfo := range t.SiafundOutputs { 213 siafundOutputSum = siafundOutputSum.Add(sfo.Value) 214 } 215 if siafundOutputSum.Cmp(siafundInputSum) != 0 { 216 return errSiafundInputOutputMismatch 217 } 218 return 219 } 220 221 // validTransaction checks that all fields are valid within the current 222 // consensus state. If not an error is returned. 223 func validTransaction(tx *bolt.Tx, t types.Transaction) error { 224 // StandaloneValid will check things like signatures and properties that 225 // should be inherent to the transaction. (storage proof rules, etc.) 226 err := t.StandaloneValid(blockHeight(tx)) 227 if err != nil { 228 return err 229 } 230 231 // Check that each portion of the transaction is legal given the current 232 // consensus set. 233 err = validSiacoins(tx, t) 234 if err != nil { 235 return err 236 } 237 err = validStorageProofs(tx, t) 238 if err != nil { 239 return err 240 } 241 err = validFileContractRevisions(tx, t) 242 if err != nil { 243 return err 244 } 245 err = validSiafunds(tx, t) 246 if err != nil { 247 return err 248 } 249 return nil 250 } 251 252 // TryTransactionSet applies the input transactions to the consensus set to 253 // determine if they are valid. An error is returned IFF they are not a valid 254 // set in the current consensus set. The size of the transactions and the set 255 // is not checked. After the transactions have been validated, a consensus 256 // change is returned detailing the diffs that the transaciton set would have. 257 func (cs *ConsensusSet) TryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) { 258 cs.mu.RLock() 259 defer cs.mu.RUnlock() 260 261 // applyTransaction will apply the diffs from a transaction and store them 262 // in a block node. diffHolder is the blockNode that tracks the temporary 263 // changes. At the end of the function, all changes that were made to the 264 // consensus set get reverted. 265 diffHolder := new(processedBlock) 266 267 // Boltdb will only roll back a tx if an error is returned. In the case of 268 // TryTransactionSet, we want to roll back the tx even if there is no 269 // error. So errSuccess is returned. An alternate method would be to 270 // manually manage the tx instead of using 'Update', but that has safety 271 // concerns and is more difficult to implement correctly. 272 errSuccess := errors.New("success") 273 err := cs.db.Update(func(tx *bolt.Tx) error { 274 diffHolder.Height = blockHeight(tx) 275 for _, txn := range txns { 276 err := validTransaction(tx, txn) 277 if err != nil { 278 return err 279 } 280 applyTransaction(tx, diffHolder, txn) 281 } 282 return errSuccess 283 }) 284 if err != errSuccess { 285 return modules.ConsensusChange{}, err 286 } 287 cc := modules.ConsensusChange{ 288 SiacoinOutputDiffs: diffHolder.SiacoinOutputDiffs, 289 FileContractDiffs: diffHolder.FileContractDiffs, 290 SiafundOutputDiffs: diffHolder.SiafundOutputDiffs, 291 DelayedSiacoinOutputDiffs: diffHolder.DelayedSiacoinOutputDiffs, 292 SiafundPoolDiffs: diffHolder.SiafundPoolDiffs, 293 } 294 return cc, nil 295 }