gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/consensus/validtransaction.go (about) 1 package consensus 2 3 import ( 4 "errors" 5 "math/big" 6 7 bolt "github.com/coreos/bbolt" 8 "gitlab.com/SiaPrime/SiaPrime/build" 9 "gitlab.com/SiaPrime/SiaPrime/crypto" 10 "gitlab.com/SiaPrime/SiaPrime/encoding" 11 "gitlab.com/SiaPrime/SiaPrime/modules" 12 "gitlab.com/SiaPrime/SiaPrime/types" 13 ) 14 15 var ( 16 errAlteredRevisionPayouts = errors.New("file contract revision has altered payout volume") 17 errInvalidStorageProof = errors.New("provided storage proof is invalid") 18 errLateRevision = errors.New("file contract revision submitted after deadline") 19 errLowRevisionNumber = errors.New("transaction has a file contract with an outdated revision number") 20 errMissingSiacoinOutput = errors.New("transaction spends a nonexisting ScP coin output") 21 errMissingSiafundOutput = errors.New("transaction spends a nonexisting ScP fund output") 22 errSiacoinInputOutputMismatch = errors.New("ScP coin inputs do not equal ScP coin outputs for transaction") 23 errSiafundInputOutputMismatch = errors.New("ScP fund inputs do not equal ScP fund outputs for transaction") 24 errUnfinishedFileContract = errors.New("file contract window has not yet openend") 25 errUnrecognizedFileContractID = errors.New("cannot fetch storage proof segment for unknown file contract") 26 errWrongUnlockConditions = errors.New("transaction contains incorrect unlock conditions") 27 ) 28 29 // validSiacoins checks that the ScP coin inputs and outputs are valid in the 30 // context of the current consensus set. 31 func validSiacoins(tx *bolt.Tx, t types.Transaction) error { 32 scoBucket := tx.Bucket(SiacoinOutputs) 33 var inputSum types.Currency 34 for _, sci := range t.SiacoinInputs { 35 // Check that the input spends an existing output. 36 scoBytes := scoBucket.Get(sci.ParentID[:]) 37 if scoBytes == nil { 38 return errMissingSiacoinOutput 39 } 40 41 // Check that the unlock conditions match the required unlock hash. 42 var sco types.SiacoinOutput 43 err := encoding.Unmarshal(scoBytes, &sco) 44 if build.DEBUG && err != nil { 45 panic(err) 46 } 47 if sci.UnlockConditions.UnlockHash() != sco.UnlockHash { 48 return errWrongUnlockConditions 49 } 50 51 inputSum = inputSum.Add(sco.Value) 52 } 53 if !inputSum.Equals(t.SiacoinOutputSum()) { 54 return errSiacoinInputOutputMismatch 55 } 56 return nil 57 } 58 59 // storageProofSegment returns the index of the segment that needs to be proven 60 // exists in a file contract. 61 func storageProofSegment(tx *bolt.Tx, fcid types.FileContractID) (uint64, error) { 62 // Check that the parent file contract exists. 63 fcBucket := tx.Bucket(FileContracts) 64 fcBytes := fcBucket.Get(fcid[:]) 65 if fcBytes == nil { 66 return 0, errUnrecognizedFileContractID 67 } 68 69 // Decode the file contract. 70 var fc types.FileContract 71 err := encoding.Unmarshal(fcBytes, &fc) 72 if build.DEBUG && err != nil { 73 panic(err) 74 } 75 76 // Get the trigger block id. 77 blockPath := tx.Bucket(BlockPath) 78 triggerHeight := fc.WindowStart - 1 79 if triggerHeight > blockHeight(tx) { 80 return 0, errUnfinishedFileContract 81 } 82 var triggerID types.BlockID 83 copy(triggerID[:], blockPath.Get(encoding.EncUint64(uint64(triggerHeight)))) 84 85 // Get the index by appending the file contract ID to the trigger block and 86 // taking the hash, then converting the hash to a numerical value and 87 // modding it against the number of segments in the file. The result is a 88 // random number in range [0, numSegments]. The probability is very 89 // slightly weighted towards the beginning of the file, but because the 90 // size difference between the number of segments and the random number 91 // being modded, the difference is too small to make any practical 92 // difference. 93 seed := crypto.HashAll(triggerID, fcid) 94 numSegments := int64(crypto.CalculateLeaves(fc.FileSize)) 95 seedInt := new(big.Int).SetBytes(seed[:]) 96 index := seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64() 97 return index, nil 98 } 99 100 // validStorageProofsPre100e3 runs the code that was running before height 101 // 100e3, which contains a hardforking bug, fixed at block 100e3. 102 // 103 // HARDFORK 100,000 104 // 105 // Originally, it was impossible to provide a storage proof for data of length 106 // zero. A hardfork was added triggering at block 100,000 to enable an 107 // optimization where hosts could submit empty storage proofs for files of size 108 // 0, saving space on the blockchain in conditions where the renter is content. 109 func validStorageProofs100e3(tx *bolt.Tx, t types.Transaction) error { 110 for _, sp := range t.StorageProofs { 111 // Check that the storage proof itself is valid. 112 segmentIndex, err := storageProofSegment(tx, sp.ParentID) 113 if err != nil { 114 return err 115 } 116 117 fc, err := getFileContract(tx, sp.ParentID) 118 if err != nil { 119 return err 120 } 121 leaves := crypto.CalculateLeaves(fc.FileSize) 122 segmentLen := uint64(crypto.SegmentSize) 123 if segmentIndex == leaves-1 { 124 segmentLen = fc.FileSize % crypto.SegmentSize 125 } 126 127 // HARDFORK 21,000 128 // 129 // Originally, the code used the entire segment to verify the 130 // correctness of the storage proof. This made the code incompatible 131 // with data sizes that did not fill an entire segment. 132 // 133 // This was patched with a hardfork in block 21,000. The new code made 134 // it possible to perform successful storage proofs on the final 135 // segment of a file if the final segment was not crypto.SegmentSize 136 // bytes. 137 // 138 // Unfortunately, a new bug was introduced where storage proofs on the 139 // final segment would fail if the final segment was selected and was 140 // crypto.SegmentSize bytes, because the segmentLen would be set to 0 141 // instead of crypto.SegmentSize, due to an error with the modulus 142 // math. This new error has been fixed with the block 100,000 hardfork. 143 if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) { 144 segmentLen = uint64(crypto.SegmentSize) 145 } 146 147 verified := crypto.VerifySegment( 148 sp.Segment[:segmentLen], 149 sp.HashSet, 150 leaves, 151 segmentIndex, 152 fc.FileMerkleRoot, 153 ) 154 if !verified { 155 return errInvalidStorageProof 156 } 157 } 158 159 return nil 160 } 161 162 // validStorageProofs checks that the storage proofs are valid in the context 163 // of the consensus set. 164 func validStorageProofs(tx *bolt.Tx, t types.Transaction) error { 165 if (build.Release == "standard" && blockHeight(tx) < 100e3) || (build.Release == "testing" && blockHeight(tx) < 10) { 166 return validStorageProofs100e3(tx, t) 167 } 168 169 for _, sp := range t.StorageProofs { 170 // Check that the storage proof itself is valid. 171 segmentIndex, err := storageProofSegment(tx, sp.ParentID) 172 if err != nil { 173 return err 174 } 175 176 fc, err := getFileContract(tx, sp.ParentID) 177 if err != nil { 178 return err 179 } 180 leaves := crypto.CalculateLeaves(fc.FileSize) 181 segmentLen := uint64(crypto.SegmentSize) 182 183 // If this segment chosen is the final segment, it should only be as 184 // long as necessary to complete the filesize. 185 if segmentIndex == leaves-1 { 186 segmentLen = fc.FileSize % crypto.SegmentSize 187 } 188 if segmentLen == 0 { 189 segmentLen = uint64(crypto.SegmentSize) 190 } 191 192 verified := crypto.VerifySegment( 193 sp.Segment[:segmentLen], 194 sp.HashSet, 195 leaves, 196 segmentIndex, 197 fc.FileMerkleRoot, 198 ) 199 if !verified && fc.FileSize > 0 { 200 return errInvalidStorageProof 201 } 202 } 203 204 return nil 205 } 206 207 // validFileContractRevision checks that each file contract revision is valid 208 // in the context of the current consensus set. 209 func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error { 210 for _, fcr := range t.FileContractRevisions { 211 fc, err := getFileContract(tx, fcr.ParentID) 212 if err != nil { 213 return err 214 } 215 216 // Check that the height is less than fc.WindowStart - revisions are 217 // not allowed to be submitted once the storage proof window has 218 // opened. This reduces complexity for unconfirmed transactions. 219 if blockHeight(tx) > fc.WindowStart { 220 return errLateRevision 221 } 222 223 // Check that the revision number of the revision is greater than the 224 // revision number of the existing file contract. 225 if fc.RevisionNumber >= fcr.NewRevisionNumber { 226 return errLowRevisionNumber 227 } 228 229 // Check that the unlock conditions match the unlock hash. 230 if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash { 231 return errWrongUnlockConditions 232 } 233 234 // Check that the payout of the revision matches the payout of the 235 // original, and that the payouts match each other. 236 var validPayout, missedPayout, oldPayout types.Currency 237 for _, output := range fcr.NewValidProofOutputs { 238 validPayout = validPayout.Add(output.Value) 239 } 240 for _, output := range fcr.NewMissedProofOutputs { 241 missedPayout = missedPayout.Add(output.Value) 242 } 243 for _, output := range fc.ValidProofOutputs { 244 oldPayout = oldPayout.Add(output.Value) 245 } 246 if !validPayout.Equals(oldPayout) { 247 return errAlteredRevisionPayouts 248 } 249 if !missedPayout.Equals(oldPayout) { 250 return errAlteredRevisionPayouts 251 } 252 } 253 return nil 254 } 255 256 // validSiafunds checks that the siafund portions of the transaction are valid 257 // in the context of the consensus set. 258 func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) { 259 // Compare the number of input siafunds to the output siafunds. 260 var siafundInputSum types.Currency 261 var siafundOutputSum types.Currency 262 for _, sfi := range t.SiafundInputs { 263 sfo, err := getSiafundOutput(tx, sfi.ParentID) 264 if err != nil { 265 return err 266 } 267 268 // Check the unlock conditions match the unlock hash. 269 if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash { 270 return errWrongUnlockConditions 271 } 272 273 siafundInputSum = siafundInputSum.Add(sfo.Value) 274 } 275 for _, sfo := range t.SiafundOutputs { 276 siafundOutputSum = siafundOutputSum.Add(sfo.Value) 277 } 278 if !siafundOutputSum.Equals(siafundInputSum) { 279 return errSiafundInputOutputMismatch 280 } 281 return 282 } 283 284 // validTransaction checks that all fields are valid within the current 285 // consensus state. If not an error is returned. 286 func validTransaction(tx *bolt.Tx, t types.Transaction) error { 287 // StandaloneValid will check things like signatures and properties that 288 // should be inherent to the transaction. (storage proof rules, etc.) 289 err := t.StandaloneValid(blockHeight(tx)) 290 if err != nil { 291 return err 292 } 293 294 // Check that each portion of the transaction is legal given the current 295 // consensus set. 296 err = validSiacoins(tx, t) 297 if err != nil { 298 return err 299 } 300 err = validStorageProofs(tx, t) 301 if err != nil { 302 return err 303 } 304 err = validFileContractRevisions(tx, t) 305 if err != nil { 306 return err 307 } 308 err = validSiafunds(tx, t) 309 if err != nil { 310 return err 311 } 312 return nil 313 } 314 315 // tryTransactionSet applies the input transactions to the consensus set to 316 // determine if they are valid. An error is returned IFF they are not a valid 317 // set in the current consensus set. The size of the transactions and the set 318 // is not checked. After the transactions have been validated, a consensus 319 // change is returned detailing the diffs that the transactions set would have. 320 func (cs *ConsensusSet) tryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) { 321 // applyTransaction will apply the diffs from a transaction and store them 322 // in a block node. diffHolder is the blockNode that tracks the temporary 323 // changes. At the end of the function, all changes that were made to the 324 // consensus set get reverted. 325 diffHolder := new(processedBlock) 326 327 // Boltdb will only roll back a tx if an error is returned. In the case of 328 // TryTransactionSet, we want to roll back the tx even if there is no 329 // error. So errSuccess is returned. An alternate method would be to 330 // manually manage the tx instead of using 'Update', but that has safety 331 // concerns and is more difficult to implement correctly. 332 errSuccess := errors.New("success") 333 err := cs.db.Update(func(tx *bolt.Tx) error { 334 diffHolder.Height = blockHeight(tx) 335 for _, txn := range txns { 336 err := validTransaction(tx, txn) 337 if err != nil { 338 return err 339 } 340 applyTransaction(tx, diffHolder, txn) 341 } 342 return errSuccess 343 }) 344 if err != errSuccess { 345 return modules.ConsensusChange{}, err 346 } 347 cc := modules.ConsensusChange{ 348 SiacoinOutputDiffs: diffHolder.SiacoinOutputDiffs, 349 FileContractDiffs: diffHolder.FileContractDiffs, 350 SiafundOutputDiffs: diffHolder.SiafundOutputDiffs, 351 DelayedSiacoinOutputDiffs: diffHolder.DelayedSiacoinOutputDiffs, 352 SiafundPoolDiffs: diffHolder.SiafundPoolDiffs, 353 } 354 return cc, nil 355 } 356 357 // TryTransactionSet applies the input transactions to the consensus set to 358 // determine if they are valid. An error is returned IFF they are not a valid 359 // set in the current consensus set. The size of the transactions and the set 360 // is not checked. After the transactions have been validated, a consensus 361 // change is returned detailing the diffs that the transactions set would have. 362 func (cs *ConsensusSet) TryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) { 363 err := cs.tg.Add() 364 if err != nil { 365 return modules.ConsensusChange{}, err 366 } 367 defer cs.tg.Done() 368 cs.mu.RLock() 369 defer cs.mu.RUnlock() 370 return cs.tryTransactionSet(txns) 371 } 372 373 // LockedTryTransactionSet calls fn while under read-lock, passing it a 374 // version of TryTransactionSet that can be called under read-lock. This fixes 375 // an edge case in the transaction pool. 376 func (cs *ConsensusSet) LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error { 377 err := cs.tg.Add() 378 if err != nil { 379 return err 380 } 381 defer cs.tg.Done() 382 cs.mu.RLock() 383 defer cs.mu.RUnlock() 384 return fn(cs.tryTransactionSet) 385 }