github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/transactionbuilder.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "errors" 6 "sort" 7 8 "SiaPrime/crypto" 9 "SiaPrime/encoding" 10 "SiaPrime/modules" 11 "SiaPrime/types" 12 13 "gitlab.com/NebulousLabs/bolt" 14 ) 15 16 var ( 17 // errBuilderAlreadySigned indicates that the transaction builder has 18 // already added at least one successful signature to the transaction, 19 // meaning that future calls to Sign will result in an invalid transaction. 20 errBuilderAlreadySigned = errors.New("sign has already been called on this transaction builder, multiple calls can cause issues") 21 22 // errDustOutput indicates an output is not spendable because it is dust. 23 errDustOutput = errors.New("output is too small") 24 25 // errOutputTimelock indicates an output's timelock is still active. 26 errOutputTimelock = errors.New("wallet consensus set height is lower than the output timelock") 27 28 // errSpendHeightTooHigh indicates an output's spend height is greater than 29 // the allowed height. 30 errSpendHeightTooHigh = errors.New("output spend height exceeds the allowed height") 31 ) 32 33 // transactionBuilder allows transactions to be manually constructed, including 34 // the ability to fund transactions with siacoins and siafunds from the wallet. 35 type transactionBuilder struct { 36 // 'signed' indicates that at least one transaction signature has been 37 // added to the wallet, meaning that future calls to 'Sign' will fail. 38 parents []types.Transaction 39 signed bool 40 transaction types.Transaction 41 42 newParents []int 43 siacoinInputs []int 44 siafundInputs []int 45 transactionSignatures []int 46 47 wallet *Wallet 48 } 49 50 // addSignatures will sign a transaction using a spendable key, with support 51 // for multisig spendable keys. Because of the restricted input, the function 52 // is compatible with both siacoin inputs and siafund inputs. 53 func addSignatures(txn *types.Transaction, cf types.CoveredFields, uc types.UnlockConditions, parentID crypto.Hash, spendKey spendableKey, height types.BlockHeight) (newSigIndices []int) { 54 // Try to find the matching secret key for each public key - some public 55 // keys may not have a match. Some secret keys may be used multiple times, 56 // which is why public keys are used as the outer loop. 57 totalSignatures := uint64(0) 58 for i, siaPubKey := range uc.PublicKeys { 59 // Search for the matching secret key to the public key. 60 for j := range spendKey.SecretKeys { 61 pubKey := spendKey.SecretKeys[j].PublicKey() 62 if !bytes.Equal(siaPubKey.Key, pubKey[:]) { 63 continue 64 } 65 66 // Found the right secret key, add a signature. 67 sig := types.TransactionSignature{ 68 ParentID: parentID, 69 CoveredFields: cf, 70 PublicKeyIndex: uint64(i), 71 } 72 newSigIndices = append(newSigIndices, len(txn.TransactionSignatures)) 73 txn.TransactionSignatures = append(txn.TransactionSignatures, sig) 74 sigIndex := len(txn.TransactionSignatures) - 1 75 sigHash := txn.SigHash(sigIndex, height) 76 encodedSig := crypto.SignHash(sigHash, spendKey.SecretKeys[j]) 77 txn.TransactionSignatures[sigIndex].Signature = encodedSig[:] 78 79 // Count that the signature has been added, and break out of the 80 // secret key loop. 81 totalSignatures++ 82 break 83 } 84 85 // If there are enough signatures to satisfy the unlock conditions, 86 // break out of the outer loop. 87 if totalSignatures == uc.SignaturesRequired { 88 break 89 } 90 } 91 return newSigIndices 92 } 93 94 // checkOutput is a helper function used to determine if an output is usable. 95 func (w *Wallet) checkOutput(tx *bolt.Tx, currentHeight types.BlockHeight, id types.SiacoinOutputID, output types.SiacoinOutput, dustThreshold types.Currency) error { 96 // Check that an output is not dust 97 if output.Value.Cmp(dustThreshold) < 0 { 98 return errDustOutput 99 } 100 // Check that this output has not recently been spent by the wallet. 101 spendHeight, err := dbGetSpentOutput(tx, types.OutputID(id)) 102 if err == nil { 103 if spendHeight+RespendTimeout > currentHeight { 104 return errSpendHeightTooHigh 105 } 106 } 107 outputUnlockConditions := w.keys[output.UnlockHash].UnlockConditions 108 if currentHeight < outputUnlockConditions.Timelock { 109 return errOutputTimelock 110 } 111 112 return nil 113 } 114 115 // FundSiacoinsForOutputs will add enough inputs to cover the outputs to be 116 // sent in the transaction. In contrast to FundSiacoins, FundSiacoinsForOutputs 117 // does not aggregate inputs into one output equaling 'amount' - with a refund, 118 // potentially - for later use by an output or other transaction fee. Rather, 119 // it aggregates enough inputs to cover the outputs, adds the inputs and outputs 120 // to the transaction, and also generates a refund output if necessary. A miner 121 // fee of 0 or greater is also taken into account in the input aggregation and 122 // added to the transaction if necessary. 123 func (tb *transactionBuilder) FundSiacoinsForOutputs(outputs []types.SiacoinOutput, fee types.Currency) error { 124 // dustThreshold has to be obtained separate from the lock 125 dustThreshold, err := tb.wallet.DustThreshold() 126 if err != nil { 127 return err 128 } 129 130 tb.wallet.mu.Lock() 131 defer tb.wallet.mu.Unlock() 132 133 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 134 if err != nil { 135 return err 136 } 137 138 // Calculate the total amount we need to send 139 var amount types.Currency 140 for i := range outputs { 141 output := outputs[i] 142 amount = amount.Add(output.Value) 143 } 144 145 // Add a miner fee if the passed fee was greater than 0. The fee also 146 // needs to be added to the input amount we need to aggregate. 147 if fee.Cmp64(0) > 0 { 148 tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee) 149 amount = amount.Add(fee) 150 } 151 152 // Collect a value-sorted set of siacoin outputs. 153 var so sortedOutputs 154 err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { 155 so.ids = append(so.ids, scoid) 156 so.outputs = append(so.outputs, sco) 157 }) 158 if err != nil { 159 return err 160 } 161 // Add all of the unconfirmed outputs as well. 162 for _, upt := range tb.wallet.unconfirmedProcessedTransactions { 163 for i, sco := range upt.Transaction.SiacoinOutputs { 164 // Determine if the output belongs to the wallet. 165 _, exists := tb.wallet.keys[sco.UnlockHash] 166 if !exists { 167 continue 168 } 169 so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i))) 170 so.outputs = append(so.outputs, sco) 171 } 172 } 173 sort.Sort(sort.Reverse(so)) 174 175 var fund types.Currency 176 // potentialFund tracks the balance of the wallet including outputs that 177 // have been spent in other unconfirmed transactions recently. This is to 178 // provide the user with a more useful error message in the event that they 179 // are overspending. 180 var potentialFund types.Currency 181 var spentScoids []types.SiacoinOutputID 182 for i := range so.ids { 183 scoid := so.ids[i] 184 sco := so.outputs[i] 185 // Check that the output can be spent. 186 if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil { 187 if err == errSpendHeightTooHigh { 188 potentialFund = potentialFund.Add(sco.Value) 189 } 190 continue 191 } 192 193 // Add a siacoin input for this output. 194 sci := types.SiacoinInput{ 195 ParentID: scoid, 196 UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions, 197 } 198 tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs)) 199 tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, sci) 200 spentScoids = append(spentScoids, scoid) 201 202 // Add the output to the total fund 203 fund = fund.Add(sco.Value) 204 potentialFund = potentialFund.Add(sco.Value) 205 if fund.Cmp(amount) >= 0 { 206 break 207 } 208 } 209 if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 { 210 return modules.ErrIncompleteTransactions 211 } 212 if fund.Cmp(amount) < 0 { 213 return modules.ErrLowBalance 214 } 215 216 // Add the outputs to the transaction 217 for i := range outputs { 218 output := outputs[i] 219 tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output) 220 } 221 222 // Create a refund output if needed. 223 if !amount.Equals(fund) { 224 refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 225 if err != nil { 226 return err 227 } 228 refundOutput := types.SiacoinOutput{ 229 Value: fund.Sub(amount), 230 UnlockHash: refundUnlockConditions.UnlockHash(), 231 } 232 tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, refundOutput) 233 } 234 235 // Mark all outputs that were spent as spent. 236 for _, scoid := range spentScoids { 237 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight) 238 if err != nil { 239 return err 240 } 241 } 242 return nil 243 } 244 245 // FundSiacoins will add a siacoin input of exactly 'amount' to the 246 // transaction. A parent transaction may be needed to achieve an input with the 247 // correct value. The siacoin input will not be signed until 'Sign' is called 248 // on the transaction builder. 249 func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error { 250 // dustThreshold has to be obtained separate from the lock 251 dustThreshold, err := tb.wallet.DustThreshold() 252 if err != nil { 253 return err 254 } 255 256 tb.wallet.mu.Lock() 257 defer tb.wallet.mu.Unlock() 258 259 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 260 if err != nil { 261 return err 262 } 263 264 // Collect a value-sorted set of siacoin outputs. 265 var so sortedOutputs 266 err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { 267 so.ids = append(so.ids, scoid) 268 so.outputs = append(so.outputs, sco) 269 }) 270 if err != nil { 271 return err 272 } 273 // Add all of the unconfirmed outputs as well. 274 for _, upt := range tb.wallet.unconfirmedProcessedTransactions { 275 for i, sco := range upt.Transaction.SiacoinOutputs { 276 // Determine if the output belongs to the wallet. 277 _, exists := tb.wallet.keys[sco.UnlockHash] 278 if !exists { 279 continue 280 } 281 so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i))) 282 so.outputs = append(so.outputs, sco) 283 } 284 } 285 sort.Sort(sort.Reverse(so)) 286 287 // Create and fund a parent transaction that will add the correct amount of 288 // siacoins to the transaction. 289 var fund types.Currency 290 // potentialFund tracks the balance of the wallet including outputs that 291 // have been spent in other unconfirmed transactions recently. This is to 292 // provide the user with a more useful error message in the event that they 293 // are overspending. 294 var potentialFund types.Currency 295 parentTxn := types.Transaction{} 296 var spentScoids []types.SiacoinOutputID 297 for i := range so.ids { 298 scoid := so.ids[i] 299 sco := so.outputs[i] 300 // Check that the output can be spent. 301 if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil { 302 if err == errSpendHeightTooHigh { 303 potentialFund = potentialFund.Add(sco.Value) 304 } 305 continue 306 } 307 308 // Add a siacoin input for this output. 309 sci := types.SiacoinInput{ 310 ParentID: scoid, 311 UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions, 312 } 313 parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci) 314 spentScoids = append(spentScoids, scoid) 315 316 // Add the output to the total fund 317 fund = fund.Add(sco.Value) 318 potentialFund = potentialFund.Add(sco.Value) 319 if fund.Cmp(amount) >= 0 { 320 break 321 } 322 } 323 if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 { 324 return modules.ErrIncompleteTransactions 325 } 326 if fund.Cmp(amount) < 0 { 327 return modules.ErrLowBalance 328 } 329 330 // Create and add the output that will be used to fund the standard 331 // transaction. 332 parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 333 if err != nil { 334 return err 335 } 336 337 exactOutput := types.SiacoinOutput{ 338 Value: amount, 339 UnlockHash: parentUnlockConditions.UnlockHash(), 340 } 341 parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput) 342 343 // Create a refund output if needed. 344 if !amount.Equals(fund) { 345 refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 346 if err != nil { 347 return err 348 } 349 refundOutput := types.SiacoinOutput{ 350 Value: fund.Sub(amount), 351 UnlockHash: refundUnlockConditions.UnlockHash(), 352 } 353 parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput) 354 } 355 356 // Sign all of the inputs to the parent transaction. 357 for _, sci := range parentTxn.SiacoinInputs { 358 addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()], consensusHeight) 359 } 360 // Mark the parent output as spent. Must be done after the transaction is 361 // finished because otherwise the txid and output id will change. 362 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight) 363 if err != nil { 364 return err 365 } 366 367 // Add the exact output. 368 newInput := types.SiacoinInput{ 369 ParentID: parentTxn.SiacoinOutputID(0), 370 UnlockConditions: parentUnlockConditions, 371 } 372 tb.newParents = append(tb.newParents, len(tb.parents)) 373 tb.parents = append(tb.parents, parentTxn) 374 tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs)) 375 tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput) 376 377 // Mark all outputs that were spent as spent. 378 for _, scoid := range spentScoids { 379 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight) 380 if err != nil { 381 return err 382 } 383 } 384 return nil 385 } 386 387 // FundSiafunds will add a siafund input of exactly 'amount' to the 388 // transaction. A parent transaction may be needed to achieve an input with the 389 // correct value. The siafund input will not be signed until 'Sign' is called 390 // on the transaction builder. 391 func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error { 392 tb.wallet.mu.Lock() 393 defer tb.wallet.mu.Unlock() 394 395 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 396 if err != nil { 397 return err 398 } 399 400 // Create and fund a parent transaction that will add the correct amount of 401 // siafunds to the transaction. 402 var fund types.Currency 403 var potentialFund types.Currency 404 parentTxn := types.Transaction{} 405 var spentSfoids []types.SiafundOutputID 406 c := tb.wallet.dbTx.Bucket(bucketSiafundOutputs).Cursor() 407 for idBytes, sfoBytes := c.First(); idBytes != nil; idBytes, sfoBytes = c.Next() { 408 var sfoid types.SiafundOutputID 409 var sfo types.SiafundOutput 410 if err := encoding.Unmarshal(idBytes, &sfoid); err != nil { 411 return err 412 } else if err := encoding.Unmarshal(sfoBytes, &sfo); err != nil { 413 return err 414 } 415 416 // Check that this output has not recently been spent by the wallet. 417 spendHeight, err := dbGetSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid)) 418 if err != nil { 419 // mimic map behavior: no entry means zero value 420 spendHeight = 0 421 } 422 // Prevent an underflow error. 423 allowedHeight := consensusHeight - RespendTimeout 424 if consensusHeight < RespendTimeout { 425 allowedHeight = 0 426 } 427 if spendHeight > allowedHeight { 428 potentialFund = potentialFund.Add(sfo.Value) 429 continue 430 } 431 outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions 432 if consensusHeight < outputUnlockConditions.Timelock { 433 continue 434 } 435 436 // Add a siafund input for this output. 437 parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 438 if err != nil { 439 return err 440 } 441 sfi := types.SiafundInput{ 442 ParentID: sfoid, 443 UnlockConditions: outputUnlockConditions, 444 ClaimUnlockHash: parentClaimUnlockConditions.UnlockHash(), 445 } 446 parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi) 447 spentSfoids = append(spentSfoids, sfoid) 448 449 // Add the output to the total fund 450 fund = fund.Add(sfo.Value) 451 potentialFund = potentialFund.Add(sfo.Value) 452 if fund.Cmp(amount) >= 0 { 453 break 454 } 455 } 456 if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 { 457 return modules.ErrIncompleteTransactions 458 } 459 if fund.Cmp(amount) < 0 { 460 return modules.ErrLowBalance 461 } 462 463 // Create and add the output that will be used to fund the standard 464 // transaction. 465 parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 466 if err != nil { 467 return err 468 } 469 exactOutput := types.SiafundOutput{ 470 Value: amount, 471 UnlockHash: parentUnlockConditions.UnlockHash(), 472 } 473 parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput) 474 475 // Create a refund output if needed. 476 if !amount.Equals(fund) { 477 refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 478 if err != nil { 479 return err 480 } 481 refundOutput := types.SiafundOutput{ 482 Value: fund.Sub(amount), 483 UnlockHash: refundUnlockConditions.UnlockHash(), 484 } 485 parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput) 486 } 487 488 // Sign all of the inputs to the parent transaction. 489 for _, sfi := range parentTxn.SiafundInputs { 490 addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()], consensusHeight) 491 } 492 493 // Add the exact output. 494 claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 495 if err != nil { 496 return err 497 } 498 newInput := types.SiafundInput{ 499 ParentID: parentTxn.SiafundOutputID(0), 500 UnlockConditions: parentUnlockConditions, 501 ClaimUnlockHash: claimUnlockConditions.UnlockHash(), 502 } 503 tb.newParents = append(tb.newParents, len(tb.parents)) 504 tb.parents = append(tb.parents, parentTxn) 505 tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs)) 506 tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput) 507 508 // Mark all outputs that were spent as spent. 509 for _, sfoid := range spentSfoids { 510 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid), consensusHeight) 511 if err != nil { 512 return err 513 } 514 } 515 return nil 516 } 517 518 // UnconfirmedParents returns the unconfirmed parents of the transaction set 519 // that is being constructed by the transaction builder. 520 func (tb *transactionBuilder) UnconfirmedParents() (parents []types.Transaction, err error) { 521 // Currently we don't need to call UnconfirmedParents after the transaction 522 // was signed so we don't allow doing that. If for some reason our 523 // requirements change, we can remove this check. The only downside is, 524 // that it might lead to transactions being returned that are not actually 525 // parents in case the signed transaction already has child transactions. 526 if tb.signed { 527 return nil, errBuilderAlreadySigned 528 } 529 addedParents := make(map[types.TransactionID]struct{}) 530 for _, p := range tb.parents { 531 for _, sci := range p.SiacoinInputs { 532 tSet := tb.wallet.tpool.TransactionSet(crypto.Hash(sci.ParentID)) 533 for _, txn := range tSet { 534 // Add the transaction to the parents. 535 txnID := txn.ID() 536 if _, exists := addedParents[txnID]; exists { 537 continue 538 } 539 addedParents[txnID] = struct{}{} 540 parents = append(parents, txn) 541 542 // When we found the transaction that contains the output that 543 // is spent by sci we stop to avoid adding child transactions. 544 for i := range txn.SiacoinOutputs { 545 if txn.SiacoinOutputID(uint64(i)) == sci.ParentID { 546 break 547 } 548 } 549 } 550 } 551 } 552 return 553 } 554 555 // AddParents adds a set of parents to the transaction. 556 func (tb *transactionBuilder) AddParents(newParents []types.Transaction) { 557 tb.parents = append(tb.parents, newParents...) 558 } 559 560 // AddMinerFee adds a miner fee to the transaction, returning the index of the 561 // miner fee within the transaction. 562 func (tb *transactionBuilder) AddMinerFee(fee types.Currency) uint64 { 563 tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee) 564 return uint64(len(tb.transaction.MinerFees) - 1) 565 } 566 567 // AddSiacoinInput adds a siacoin input to the transaction, returning the index 568 // of the siacoin input within the transaction. When 'Sign' gets called, this 569 // input will be left unsigned. 570 func (tb *transactionBuilder) AddSiacoinInput(input types.SiacoinInput) uint64 { 571 tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, input) 572 return uint64(len(tb.transaction.SiacoinInputs) - 1) 573 } 574 575 // AddSiacoinOutput adds a siacoin output to the transaction, returning the 576 // index of the siacoin output within the transaction. 577 func (tb *transactionBuilder) AddSiacoinOutput(output types.SiacoinOutput) uint64 { 578 tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output) 579 return uint64(len(tb.transaction.SiacoinOutputs) - 1) 580 } 581 582 // AddFileContract adds a file contract to the transaction, returning the index 583 // of the file contract within the transaction. 584 func (tb *transactionBuilder) AddFileContract(fc types.FileContract) uint64 { 585 tb.transaction.FileContracts = append(tb.transaction.FileContracts, fc) 586 return uint64(len(tb.transaction.FileContracts) - 1) 587 } 588 589 // AddFileContractRevision adds a file contract revision to the transaction, 590 // returning the index of the file contract revision within the transaction. 591 // When 'Sign' gets called, this revision will be left unsigned. 592 func (tb *transactionBuilder) AddFileContractRevision(fcr types.FileContractRevision) uint64 { 593 tb.transaction.FileContractRevisions = append(tb.transaction.FileContractRevisions, fcr) 594 return uint64(len(tb.transaction.FileContractRevisions) - 1) 595 } 596 597 // AddStorageProof adds a storage proof to the transaction, returning the index 598 // of the storage proof within the transaction. 599 func (tb *transactionBuilder) AddStorageProof(sp types.StorageProof) uint64 { 600 tb.transaction.StorageProofs = append(tb.transaction.StorageProofs, sp) 601 return uint64(len(tb.transaction.StorageProofs) - 1) 602 } 603 604 // AddSiafundInput adds a siafund input to the transaction, returning the index 605 // of the siafund input within the transaction. When 'Sign' is called, this 606 // input will be left unsigned. 607 func (tb *transactionBuilder) AddSiafundInput(input types.SiafundInput) uint64 { 608 tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, input) 609 return uint64(len(tb.transaction.SiafundInputs) - 1) 610 } 611 612 // AddSiafundOutput adds a siafund output to the transaction, returning the 613 // index of the siafund output within the transaction. 614 func (tb *transactionBuilder) AddSiafundOutput(output types.SiafundOutput) uint64 { 615 tb.transaction.SiafundOutputs = append(tb.transaction.SiafundOutputs, output) 616 return uint64(len(tb.transaction.SiafundOutputs) - 1) 617 } 618 619 // AddArbitraryData adds arbitrary data to the transaction, returning the index 620 // of the data within the transaction. 621 func (tb *transactionBuilder) AddArbitraryData(arb []byte) uint64 { 622 tb.transaction.ArbitraryData = append(tb.transaction.ArbitraryData, arb) 623 return uint64(len(tb.transaction.ArbitraryData) - 1) 624 } 625 626 // AddTransactionSignature adds a transaction signature to the transaction, 627 // returning the index of the signature within the transaction. The signature 628 // should already be valid, and shouldn't sign any of the inputs that were 629 // added by calling 'FundSiacoins' or 'FundSiafunds'. 630 func (tb *transactionBuilder) AddTransactionSignature(sig types.TransactionSignature) uint64 { 631 tb.transaction.TransactionSignatures = append(tb.transaction.TransactionSignatures, sig) 632 return uint64(len(tb.transaction.TransactionSignatures) - 1) 633 } 634 635 // Drop discards all of the outputs in a transaction, returning them to the 636 // pool so that other transactions may use them. 'Drop' should only be called 637 // if a transaction is both unsigned and will not be used any further. 638 func (tb *transactionBuilder) Drop() { 639 tb.wallet.mu.Lock() 640 defer tb.wallet.mu.Unlock() 641 642 // Iterate through all parents and the transaction itself and restore all 643 // outputs to the list of available outputs. 644 txns := append(tb.parents, tb.transaction) 645 for _, txn := range txns { 646 for _, sci := range txn.SiacoinInputs { 647 dbDeleteSpentOutput(tb.wallet.dbTx, types.OutputID(sci.ParentID)) 648 } 649 } 650 651 tb.parents = nil 652 tb.signed = false 653 tb.transaction = types.Transaction{} 654 655 tb.newParents = nil 656 tb.siacoinInputs = nil 657 tb.siafundInputs = nil 658 tb.transactionSignatures = nil 659 } 660 661 // Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds' and 662 // return a transaction set that contains all parents prepended to the 663 // transaction. If more fields need to be added, a new transaction builder will 664 // need to be created. 665 // 666 // If the whole transaction flag is set to true, then the whole transaction 667 // flag will be set in the covered fields object. If the whole transaction flag 668 // is set to false, then the covered fields object will cover all fields that 669 // have already been added to the transaction, but will also leave room for 670 // more fields to be added. 671 // 672 // Sign should not be called more than once. If, for some reason, there is an 673 // error while calling Sign, the builder should be dropped. 674 func (tb *transactionBuilder) Sign(wholeTransaction bool) ([]types.Transaction, error) { 675 if tb.signed { 676 return nil, errBuilderAlreadySigned 677 } 678 679 tb.wallet.mu.Lock() 680 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 681 tb.wallet.mu.Unlock() 682 if err != nil { 683 return nil, err 684 } 685 686 // Create the coveredfields struct. 687 var coveredFields types.CoveredFields 688 if wholeTransaction { 689 coveredFields = types.CoveredFields{WholeTransaction: true} 690 } else { 691 for i := range tb.transaction.MinerFees { 692 coveredFields.MinerFees = append(coveredFields.MinerFees, uint64(i)) 693 } 694 for i := range tb.transaction.SiacoinInputs { 695 coveredFields.SiacoinInputs = append(coveredFields.SiacoinInputs, uint64(i)) 696 } 697 for i := range tb.transaction.SiacoinOutputs { 698 coveredFields.SiacoinOutputs = append(coveredFields.SiacoinOutputs, uint64(i)) 699 } 700 for i := range tb.transaction.FileContracts { 701 coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i)) 702 } 703 for i := range tb.transaction.FileContractRevisions { 704 coveredFields.FileContractRevisions = append(coveredFields.FileContractRevisions, uint64(i)) 705 } 706 for i := range tb.transaction.StorageProofs { 707 coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i)) 708 } 709 for i := range tb.transaction.SiafundInputs { 710 coveredFields.SiafundInputs = append(coveredFields.SiafundInputs, uint64(i)) 711 } 712 for i := range tb.transaction.SiafundOutputs { 713 coveredFields.SiafundOutputs = append(coveredFields.SiafundOutputs, uint64(i)) 714 } 715 for i := range tb.transaction.ArbitraryData { 716 coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i)) 717 } 718 } 719 // TransactionSignatures don't get covered by the 'WholeTransaction' flag, 720 // and must be covered manually. 721 for i := range tb.transaction.TransactionSignatures { 722 coveredFields.TransactionSignatures = append(coveredFields.TransactionSignatures, uint64(i)) 723 } 724 725 // For each siacoin input in the transaction that we added, provide a 726 // signature. 727 tb.wallet.mu.RLock() 728 defer tb.wallet.mu.RUnlock() 729 for _, inputIndex := range tb.siacoinInputs { 730 input := tb.transaction.SiacoinInputs[inputIndex] 731 key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()] 732 if !ok { 733 return nil, errors.New("transaction builder added an input that it cannot sign") 734 } 735 newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key, consensusHeight) 736 tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...) 737 tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues. 738 } 739 for _, inputIndex := range tb.siafundInputs { 740 input := tb.transaction.SiafundInputs[inputIndex] 741 key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()] 742 if !ok { 743 return nil, errors.New("transaction builder added an input that it cannot sign") 744 } 745 newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key, consensusHeight) 746 tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...) 747 tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues. 748 } 749 750 // Get the transaction set and delete the transaction from the registry. 751 txnSet := append(tb.parents, tb.transaction) 752 return txnSet, nil 753 } 754 755 // ViewTransaction returns a transaction-in-progress along with all of its 756 // parents, specified by id. An error is returned if the id is invalid. Note 757 // that ids become invalid for a transaction after 'SignTransaction' has been 758 // called because the transaction gets deleted. 759 func (tb *transactionBuilder) View() (types.Transaction, []types.Transaction) { 760 return tb.transaction, tb.parents 761 } 762 763 // ViewAdded returns all of the siacoin inputs, siafund inputs, and parent 764 // transactions that have been automatically added by the builder. 765 func (tb *transactionBuilder) ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int) { 766 return tb.newParents, tb.siacoinInputs, tb.siafundInputs, tb.transactionSignatures 767 } 768 769 // registerTransaction takes a transaction and its parents and returns a 770 // wallet.TransactionBuilder which can be used to expand the transaction. The 771 // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which 772 // registers a new transaction without parents. 773 func (w *Wallet) registerTransaction(t types.Transaction, parents []types.Transaction) *transactionBuilder { 774 // Create a deep copy of the transaction and parents by encoding them. A 775 // deep copy ensures that there are no pointer or slice related errors - 776 // the builder will be working directly on the transaction, and the 777 // transaction may be in use elsewhere (in this case, the host is using the 778 // transaction. 779 pBytes := encoding.Marshal(parents) 780 var pCopy []types.Transaction 781 err := encoding.Unmarshal(pBytes, &pCopy) 782 if err != nil { 783 panic(err) 784 } 785 tBytes := encoding.Marshal(t) 786 var tCopy types.Transaction 787 err = encoding.Unmarshal(tBytes, &tCopy) 788 if err != nil { 789 panic(err) 790 } 791 return &transactionBuilder{ 792 parents: pCopy, 793 transaction: tCopy, 794 795 wallet: w, 796 } 797 } 798 799 // RegisterTransaction takes a transaction and its parents and returns a 800 // modules.TransactionBuilder which can be used to expand the transaction. The 801 // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which 802 // registers a new transaction without parents. 803 func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) (modules.TransactionBuilder, error) { 804 if err := w.tg.Add(); err != nil { 805 return nil, err 806 } 807 defer w.tg.Done() 808 w.mu.Lock() 809 defer w.mu.Unlock() 810 return w.registerTransaction(t, parents), nil 811 } 812 813 // StartTransaction is a convenience function that calls 814 // RegisterTransaction(types.Transaction{}, nil). 815 func (w *Wallet) StartTransaction() (modules.TransactionBuilder, error) { 816 if err := w.tg.Add(); err != nil { 817 return nil, err 818 } 819 defer w.tg.Done() 820 return w.RegisterTransaction(types.Transaction{}, nil) 821 }