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