github.com/NebulousLabs/Sia@v1.3.7/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/coreos/bbolt" 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.Equals(t.SiacoinOutputSum()) { 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 // validStorageProofsPre100e3 runs the code that was running before height 102 // 100e3, which contains a hardforking bug, fixed at block 100e3. 103 // 104 // HARDFORK 100,000 105 // 106 // Originally, it was impossible to provide a storage proof for data of length 107 // zero. A hardfork was added triggering at block 100,000 to enable an 108 // optimization where hosts could submit empty storage proofs for files of size 109 // 0, saving space on the blockchain in conditions where the renter is content. 110 func validStorageProofs100e3(tx *bolt.Tx, t types.Transaction) error { 111 for _, sp := range t.StorageProofs { 112 // Check that the storage proof itself is valid. 113 segmentIndex, err := storageProofSegment(tx, sp.ParentID) 114 if err != nil { 115 return err 116 } 117 118 fc, err := getFileContract(tx, sp.ParentID) 119 if err != nil { 120 return err 121 } 122 leaves := crypto.CalculateLeaves(fc.FileSize) 123 segmentLen := uint64(crypto.SegmentSize) 124 if segmentIndex == leaves-1 { 125 segmentLen = fc.FileSize % crypto.SegmentSize 126 } 127 128 // HARDFORK 21,000 129 // 130 // Originally, the code used the entire segment to verify the 131 // correctness of the storage proof. This made the code incompatible 132 // with data sizes that did not fill an entire segment. 133 // 134 // This was patched with a hardfork in block 21,000. The new code made 135 // it possible to perform successful storage proofs on the final 136 // segment of a file if the final segment was not crypto.SegmentSize 137 // bytes. 138 // 139 // Unfortunately, a new bug was introduced where storage proofs on the 140 // final segment would fail if the final segment was selected and was 141 // crypto.SegmentSize bytes, because the segmentLen would be set to 0 142 // instead of crypto.SegmentSize, due to an error with the modulus 143 // math. This new error has been fixed with the block 100,000 hardfork. 144 if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) { 145 segmentLen = uint64(crypto.SegmentSize) 146 } 147 148 verified := crypto.VerifySegment( 149 sp.Segment[:segmentLen], 150 sp.HashSet, 151 leaves, 152 segmentIndex, 153 fc.FileMerkleRoot, 154 ) 155 if !verified { 156 return errInvalidStorageProof 157 } 158 } 159 160 return nil 161 } 162 163 // validStorageProofs checks that the storage proofs are valid in the context 164 // of the consensus set. 165 func validStorageProofs(tx *bolt.Tx, t types.Transaction) error { 166 if (build.Release == "standard" && blockHeight(tx) < 100e3) || (build.Release == "testing" && blockHeight(tx) < 10) { 167 return validStorageProofs100e3(tx, t) 168 } 169 170 for _, sp := range t.StorageProofs { 171 // Check that the storage proof itself is valid. 172 segmentIndex, err := storageProofSegment(tx, sp.ParentID) 173 if err != nil { 174 return err 175 } 176 177 fc, err := getFileContract(tx, sp.ParentID) 178 if err != nil { 179 return err 180 } 181 leaves := crypto.CalculateLeaves(fc.FileSize) 182 segmentLen := uint64(crypto.SegmentSize) 183 184 // If this segment chosen is the final segment, it should only be as 185 // long as necessary to complete the filesize. 186 if segmentIndex == leaves-1 { 187 segmentLen = fc.FileSize % crypto.SegmentSize 188 } 189 if segmentLen == 0 { 190 segmentLen = uint64(crypto.SegmentSize) 191 } 192 193 verified := crypto.VerifySegment( 194 sp.Segment[:segmentLen], 195 sp.HashSet, 196 leaves, 197 segmentIndex, 198 fc.FileMerkleRoot, 199 ) 200 if !verified && fc.FileSize > 0 { 201 return errInvalidStorageProof 202 } 203 } 204 205 return nil 206 } 207 208 // validFileContractRevision checks that each file contract revision is valid 209 // in the context of the current consensus set. 210 func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error { 211 for _, fcr := range t.FileContractRevisions { 212 fc, err := getFileContract(tx, fcr.ParentID) 213 if err != nil { 214 return err 215 } 216 217 // Check that the height is less than fc.WindowStart - revisions are 218 // not allowed to be submitted once the storage proof window has 219 // opened. This reduces complexity for unconfirmed transactions. 220 if blockHeight(tx) > fc.WindowStart { 221 return errLateRevision 222 } 223 224 // Check that the revision number of the revision is greater than the 225 // revision number of the existing file contract. 226 if fc.RevisionNumber >= fcr.NewRevisionNumber { 227 return errLowRevisionNumber 228 } 229 230 // Check that the unlock conditions match the unlock hash. 231 if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash { 232 return errWrongUnlockConditions 233 } 234 235 // Check that the payout of the revision matches the payout of the 236 // original, and that the payouts match each other. 237 var validPayout, missedPayout, oldPayout types.Currency 238 for _, output := range fcr.NewValidProofOutputs { 239 validPayout = validPayout.Add(output.Value) 240 } 241 for _, output := range fcr.NewMissedProofOutputs { 242 missedPayout = missedPayout.Add(output.Value) 243 } 244 for _, output := range fc.ValidProofOutputs { 245 oldPayout = oldPayout.Add(output.Value) 246 } 247 if !validPayout.Equals(oldPayout) { 248 return errAlteredRevisionPayouts 249 } 250 if !missedPayout.Equals(oldPayout) { 251 return errAlteredRevisionPayouts 252 } 253 } 254 return nil 255 } 256 257 // validSiafunds checks that the siafund portions of the transaction are valid 258 // in the context of the consensus set. 259 func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) { 260 // Compare the number of input siafunds to the output siafunds. 261 var siafundInputSum types.Currency 262 var siafundOutputSum types.Currency 263 for _, sfi := range t.SiafundInputs { 264 sfo, err := getSiafundOutput(tx, sfi.ParentID) 265 if err != nil { 266 return err 267 } 268 269 // Check the unlock conditions match the unlock hash. 270 if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash { 271 return errWrongUnlockConditions 272 } 273 274 siafundInputSum = siafundInputSum.Add(sfo.Value) 275 } 276 for _, sfo := range t.SiafundOutputs { 277 siafundOutputSum = siafundOutputSum.Add(sfo.Value) 278 } 279 if !siafundOutputSum.Equals(siafundInputSum) { 280 return errSiafundInputOutputMismatch 281 } 282 return 283 } 284 285 // validTransaction checks that all fields are valid within the current 286 // consensus state. If not an error is returned. 287 func validTransaction(tx *bolt.Tx, t types.Transaction) error { 288 // StandaloneValid will check things like signatures and properties that 289 // should be inherent to the transaction. (storage proof rules, etc.) 290 err := t.StandaloneValid(blockHeight(tx)) 291 if err != nil { 292 return err 293 } 294 295 // Check that each portion of the transaction is legal given the current 296 // consensus set. 297 err = validSiacoins(tx, t) 298 if err != nil { 299 return err 300 } 301 err = validStorageProofs(tx, t) 302 if err != nil { 303 return err 304 } 305 err = validFileContractRevisions(tx, t) 306 if err != nil { 307 return err 308 } 309 err = validSiafunds(tx, t) 310 if err != nil { 311 return err 312 } 313 return nil 314 } 315 316 // tryTransactionSet applies the input transactions to the consensus set to 317 // determine if they are valid. An error is returned IFF they are not a valid 318 // set in the current consensus set. The size of the transactions and the set 319 // is not checked. After the transactions have been validated, a consensus 320 // change is returned detailing the diffs that the transactions set would have. 321 func (cs *ConsensusSet) tryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) { 322 // applyTransaction will apply the diffs from a transaction and store them 323 // in a block node. diffHolder is the blockNode that tracks the temporary 324 // changes. At the end of the function, all changes that were made to the 325 // consensus set get reverted. 326 diffHolder := new(processedBlock) 327 328 // Boltdb will only roll back a tx if an error is returned. In the case of 329 // TryTransactionSet, we want to roll back the tx even if there is no 330 // error. So errSuccess is returned. An alternate method would be to 331 // manually manage the tx instead of using 'Update', but that has safety 332 // concerns and is more difficult to implement correctly. 333 errSuccess := errors.New("success") 334 err := cs.db.Update(func(tx *bolt.Tx) error { 335 diffHolder.Height = blockHeight(tx) 336 for _, txn := range txns { 337 err := validTransaction(tx, txn) 338 if err != nil { 339 return err 340 } 341 applyTransaction(tx, diffHolder, txn) 342 } 343 return errSuccess 344 }) 345 if err != errSuccess { 346 return modules.ConsensusChange{}, err 347 } 348 cc := modules.ConsensusChange{ 349 SiacoinOutputDiffs: diffHolder.SiacoinOutputDiffs, 350 FileContractDiffs: diffHolder.FileContractDiffs, 351 SiafundOutputDiffs: diffHolder.SiafundOutputDiffs, 352 DelayedSiacoinOutputDiffs: diffHolder.DelayedSiacoinOutputDiffs, 353 SiafundPoolDiffs: diffHolder.SiafundPoolDiffs, 354 } 355 return cc, nil 356 } 357 358 // TryTransactionSet applies the input transactions to the consensus set to 359 // determine if they are valid. An error is returned IFF they are not a valid 360 // set in the current consensus set. The size of the transactions and the set 361 // is not checked. After the transactions have been validated, a consensus 362 // change is returned detailing the diffs that the transactions set would have. 363 func (cs *ConsensusSet) TryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) { 364 err := cs.tg.Add() 365 if err != nil { 366 return modules.ConsensusChange{}, err 367 } 368 defer cs.tg.Done() 369 cs.mu.RLock() 370 defer cs.mu.RUnlock() 371 return cs.tryTransactionSet(txns) 372 } 373 374 // LockedTryTransactionSet calls fn while under read-lock, passing it a 375 // version of TryTransactionSet that can be called under read-lock. This fixes 376 // an edge case in the transaction pool. 377 func (cs *ConsensusSet) LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error { 378 err := cs.tg.Add() 379 if err != nil { 380 return err 381 } 382 defer cs.tg.Done() 383 cs.mu.RLock() 384 defer cs.mu.RUnlock() 385 return fn(cs.tryTransactionSet) 386 }