github.com/dmmcquay/sia@v1.3.1-0.20180712220038-9f8d535311b9/types/validtransaction.go (about) 1 package types 2 3 // validtransaction.go has functions for checking whether a transaction is 4 // valid outside of the context of a consensus set. This means checking the 5 // size of the transaction, the content of the signatures, and a large set of 6 // other rules that are inherent to how a transaction should be constructed. 7 8 import ( 9 "errors" 10 ) 11 12 var ( 13 // ErrDoubleSpend is an error when a transaction uses a parent object 14 // twice 15 ErrDoubleSpend = errors.New("transaction uses a parent object twice") 16 // ErrFileContractOutputSumViolation is an error when a file contract 17 // has invalid output sums 18 ErrFileContractOutputSumViolation = errors.New("file contract has invalid output sums") 19 // ErrFileContractWindowEndViolation is an error when a file contract 20 // window must end at least one block after it starts 21 ErrFileContractWindowEndViolation = errors.New("file contract window must end at least one block after it starts") 22 // ErrFileContractWindowStartViolation is an error when a file contract 23 // window must start in the future 24 ErrFileContractWindowStartViolation = errors.New("file contract window must start in the future") 25 // ErrNonZeroClaimStart is an error when a transaction has a siafund 26 // output with a non-zero siafund claim 27 ErrNonZeroClaimStart = errors.New("transaction has a siafund output with a non-zero siafund claim") 28 // ErrNonZeroRevision is an error when a new file contract has a 29 // nonzero revision number 30 ErrNonZeroRevision = errors.New("new file contract has a nonzero revision number") 31 // ErrStorageProofWithOutputs is an error when a transaction has both 32 // a storage proof and other outputs 33 ErrStorageProofWithOutputs = errors.New("transaction has both a storage proof and other outputs") 34 // ErrTimelockNotSatisfied is an error when a timelock has not been met 35 ErrTimelockNotSatisfied = errors.New("timelock has not been met") 36 // ErrTransactionTooLarge is an error when a transaction is too large 37 // to fit in a block 38 ErrTransactionTooLarge = errors.New("transaction is too large to fit in a block") 39 // ErrZeroMinerFee is an error when a transaction has a zero value miner 40 // fee 41 ErrZeroMinerFee = errors.New("transaction has a zero value miner fee") 42 // ErrZeroOutput is an error when a transaction cannot have an output 43 // or payout that has zero value 44 ErrZeroOutput = errors.New("transaction cannot have an output or payout that has zero value") 45 // ErrZeroRevision is an error when a transaction has a file contract 46 // revision with RevisionNumber=0 47 ErrZeroRevision = errors.New("transaction has a file contract revision with RevisionNumber=0") 48 ) 49 50 // correctFileContracts checks that the file contracts adhere to the file 51 // contract rules. 52 func (t Transaction) correctFileContracts(currentHeight BlockHeight) error { 53 // Check that FileContract rules are being followed. 54 for _, fc := range t.FileContracts { 55 // Check that start and expiration are reasonable values. 56 if fc.WindowStart <= currentHeight { 57 return ErrFileContractWindowStartViolation 58 } 59 if fc.WindowEnd <= fc.WindowStart { 60 return ErrFileContractWindowEndViolation 61 } 62 63 // Check that the proof outputs sum to the payout after the 64 // siafund fee has been applied. 65 var validProofOutputSum, missedProofOutputSum Currency 66 for _, output := range fc.ValidProofOutputs { 67 /* - Future hardforking code. 68 if output.Value.IsZero() { 69 return ErrZeroOutput 70 } 71 */ 72 validProofOutputSum = validProofOutputSum.Add(output.Value) 73 } 74 for _, output := range fc.MissedProofOutputs { 75 /* - Future hardforking code. 76 if output.Value.IsZero() { 77 return ErrZeroOutput 78 } 79 */ 80 missedProofOutputSum = missedProofOutputSum.Add(output.Value) 81 } 82 outputPortion := PostTax(currentHeight, fc.Payout) 83 if validProofOutputSum.Cmp(outputPortion) != 0 { 84 return ErrFileContractOutputSumViolation 85 } 86 if missedProofOutputSum.Cmp(outputPortion) != 0 { 87 return ErrFileContractOutputSumViolation 88 } 89 } 90 return nil 91 } 92 93 // correctFileContractRevisions checks that any file contract revisions adhere 94 // to the revision rules. 95 func (t Transaction) correctFileContractRevisions(currentHeight BlockHeight) error { 96 for _, fcr := range t.FileContractRevisions { 97 // Check that start and expiration are reasonable values. 98 if fcr.NewWindowStart <= currentHeight { 99 return ErrFileContractWindowStartViolation 100 } 101 if fcr.NewWindowEnd <= fcr.NewWindowStart { 102 return ErrFileContractWindowEndViolation 103 } 104 105 // Check that the valid outputs and missed outputs sum to the same 106 // value. 107 var validProofOutputSum, missedProofOutputSum Currency 108 for _, output := range fcr.NewValidProofOutputs { 109 /* - Future hardforking code. 110 if output.Value.IsZero() { 111 return ErrZeroOutput 112 } 113 */ 114 validProofOutputSum = validProofOutputSum.Add(output.Value) 115 } 116 for _, output := range fcr.NewMissedProofOutputs { 117 /* - Future hardforking code. 118 if output.Value.IsZero() { 119 return ErrZeroOutput 120 } 121 */ 122 missedProofOutputSum = missedProofOutputSum.Add(output.Value) 123 } 124 if validProofOutputSum.Cmp(missedProofOutputSum) != 0 { 125 return ErrFileContractOutputSumViolation 126 } 127 } 128 return nil 129 } 130 131 // fitsInABlock checks if the transaction is likely to fit in a block. After 132 // OakHardforkHeight, transactions must be smaller than 64 KiB. 133 func (t Transaction) fitsInABlock(currentHeight BlockHeight) error { 134 // Check that the transaction will fit inside of a block, leaving 5kb for 135 // overhead. 136 size := uint64(t.MarshalSiaSize()) 137 if size > BlockSizeLimit-5e3 { 138 return ErrTransactionTooLarge 139 } 140 if currentHeight >= OakHardforkBlock { 141 if size > OakHardforkTxnSizeLimit { 142 return ErrTransactionTooLarge 143 } 144 } 145 return nil 146 } 147 148 // followsMinimumValues checks that all outputs adhere to the rules for the 149 // minimum allowed value (generally 1). 150 func (t Transaction) followsMinimumValues() error { 151 for _, sco := range t.SiacoinOutputs { 152 if sco.Value.IsZero() { 153 return ErrZeroOutput 154 } 155 } 156 for _, fc := range t.FileContracts { 157 if fc.Payout.IsZero() { 158 return ErrZeroOutput 159 } 160 } 161 for _, sfo := range t.SiafundOutputs { 162 // SiafundOutputs are special in that they have a reserved field, the 163 // ClaimStart, which gets sent over the wire but must always be set to 164 // 0. The Value must always be greater than 0. 165 if !sfo.ClaimStart.IsZero() { 166 return ErrNonZeroClaimStart 167 } 168 if sfo.Value.IsZero() { 169 return ErrZeroOutput 170 } 171 } 172 for _, fee := range t.MinerFees { 173 if fee.IsZero() { 174 return ErrZeroMinerFee 175 } 176 } 177 return nil 178 } 179 180 // FollowsStorageProofRules checks that a transaction follows the limitations 181 // placed on transactions that have storage proofs. 182 func (t Transaction) followsStorageProofRules() error { 183 // No storage proofs, no problems. 184 if len(t.StorageProofs) == 0 { 185 return nil 186 } 187 188 // If there are storage proofs, there can be no siacoin outputs, siafund 189 // outputs, new file contracts, or file contract terminations. These 190 // restrictions are in place because a storage proof can be invalidated by 191 // a simple reorg, which will also invalidate the rest of the transaction. 192 // These restrictions minimize blockchain turbulence. These other types 193 // cannot be invalidated by a simple reorg, and must instead by replaced by 194 // a conflicting transaction. 195 if len(t.SiacoinOutputs) != 0 { 196 return ErrStorageProofWithOutputs 197 } 198 if len(t.FileContracts) != 0 { 199 return ErrStorageProofWithOutputs 200 } 201 if len(t.FileContractRevisions) != 0 { 202 return ErrStorageProofWithOutputs 203 } 204 if len(t.SiafundOutputs) != 0 { 205 return ErrStorageProofWithOutputs 206 } 207 208 return nil 209 } 210 211 // noRepeats checks that a transaction does not spend multiple outputs twice, 212 // submit two valid storage proofs for the same file contract, etc. We 213 // frivolously check that a file contract termination and storage proof don't 214 // act on the same file contract. There is very little overhead for doing so, 215 // and the check is only frivolous because of the current rule that file 216 // contract terminations are not valid after the proof window opens. 217 func (t Transaction) noRepeats() error { 218 // Check that there are no repeat instances of siacoin outputs, storage 219 // proofs, contract terminations, or siafund outputs. 220 siacoinInputs := make(map[SiacoinOutputID]struct{}) 221 for _, sci := range t.SiacoinInputs { 222 _, exists := siacoinInputs[sci.ParentID] 223 if exists { 224 return ErrDoubleSpend 225 } 226 siacoinInputs[sci.ParentID] = struct{}{} 227 } 228 doneFileContracts := make(map[FileContractID]struct{}) 229 for _, sp := range t.StorageProofs { 230 _, exists := doneFileContracts[sp.ParentID] 231 if exists { 232 return ErrDoubleSpend 233 } 234 doneFileContracts[sp.ParentID] = struct{}{} 235 } 236 for _, fcr := range t.FileContractRevisions { 237 _, exists := doneFileContracts[fcr.ParentID] 238 if exists { 239 return ErrDoubleSpend 240 } 241 doneFileContracts[fcr.ParentID] = struct{}{} 242 } 243 siafundInputs := make(map[SiafundOutputID]struct{}) 244 for _, sfi := range t.SiafundInputs { 245 _, exists := siafundInputs[sfi.ParentID] 246 if exists { 247 return ErrDoubleSpend 248 } 249 siafundInputs[sfi.ParentID] = struct{}{} 250 } 251 return nil 252 } 253 254 // validUnlockConditions checks that the conditions of uc have been met. The 255 // height is taken as input so that modules who might be at a different height 256 // can do the verification without needing to use their own function. 257 // Additionally, it means that the function does not need to be a method of the 258 // consensus set. 259 func validUnlockConditions(uc UnlockConditions, currentHeight BlockHeight) (err error) { 260 if uc.Timelock > currentHeight { 261 return ErrTimelockNotSatisfied 262 } 263 return 264 } 265 266 // validUnlockConditions checks that all of the unlock conditions in the 267 // transaction are valid. 268 func (t Transaction) validUnlockConditions(currentHeight BlockHeight) (err error) { 269 for _, sci := range t.SiacoinInputs { 270 err = validUnlockConditions(sci.UnlockConditions, currentHeight) 271 if err != nil { 272 return 273 } 274 } 275 for _, fcr := range t.FileContractRevisions { 276 err = validUnlockConditions(fcr.UnlockConditions, currentHeight) 277 if err != nil { 278 return 279 } 280 } 281 for _, sfi := range t.SiafundInputs { 282 err = validUnlockConditions(sfi.UnlockConditions, currentHeight) 283 if err != nil { 284 return 285 } 286 } 287 return 288 } 289 290 // StandaloneValid returns an error if a transaction is not valid in any 291 // context, for example if the same output is spent twice in the same 292 // transaction. StandaloneValid will not check that all outputs being spent are 293 // legal outputs, as it has no confirmed or unconfirmed set to look at. 294 func (t Transaction) StandaloneValid(currentHeight BlockHeight) (err error) { 295 err = t.fitsInABlock(currentHeight) 296 if err != nil { 297 return 298 } 299 err = t.followsStorageProofRules() 300 if err != nil { 301 return 302 } 303 err = t.noRepeats() 304 if err != nil { 305 return 306 } 307 err = t.followsMinimumValues() 308 if err != nil { 309 return 310 } 311 err = t.correctFileContracts(currentHeight) 312 if err != nil { 313 return 314 } 315 err = t.correctFileContractRevisions(currentHeight) 316 if err != nil { 317 return 318 } 319 err = t.validUnlockConditions(currentHeight) 320 if err != nil { 321 return 322 } 323 err = t.validSignatures(currentHeight) 324 if err != nil { 325 return 326 } 327 return 328 }