github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/transactionbuilder.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "errors" 6 "sort" 7 8 "github.com/Synthesix/Sia/crypto" 9 "github.com/Synthesix/Sia/encoding" 10 "github.com/Synthesix/Sia/modules" 11 "github.com/Synthesix/Sia/types" 12 13 "github.com/coreos/bbolt" 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) (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) 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 // FundSiacoins will add a siacoin input of exactly 'amount' to the 116 // transaction. A parent transaction may be needed to achieve an input with the 117 // correct value. The siacoin input will not be signed until 'Sign' is called 118 // on the transaction builder. 119 func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error { 120 // dustThreshold has to be obtained separate from the lock 121 dustThreshold := tb.wallet.DustThreshold() 122 123 tb.wallet.mu.Lock() 124 defer tb.wallet.mu.Unlock() 125 126 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 127 if err != nil { 128 return err 129 } 130 131 // Collect a value-sorted set of siacoin outputs. 132 var so sortedOutputs 133 err = dbForEachSiacoinOutput(tb.wallet.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { 134 so.ids = append(so.ids, scoid) 135 so.outputs = append(so.outputs, sco) 136 }) 137 if err != nil { 138 return err 139 } 140 // Add all of the unconfirmed outputs as well. 141 for _, upt := range tb.wallet.unconfirmedProcessedTransactions { 142 for i, sco := range upt.Transaction.SiacoinOutputs { 143 // Determine if the output belongs to the wallet. 144 _, exists := tb.wallet.keys[sco.UnlockHash] 145 if !exists { 146 continue 147 } 148 so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i))) 149 so.outputs = append(so.outputs, sco) 150 } 151 } 152 sort.Sort(sort.Reverse(so)) 153 154 // Create and fund a parent transaction that will add the correct amount of 155 // siacoins to the transaction. 156 var fund types.Currency 157 // potentialFund tracks the balance of the wallet including outputs that 158 // have been spent in other unconfirmed transactions recently. This is to 159 // provide the user with a more useful error message in the event that they 160 // are overspending. 161 var potentialFund types.Currency 162 parentTxn := types.Transaction{} 163 var spentScoids []types.SiacoinOutputID 164 for i := range so.ids { 165 scoid := so.ids[i] 166 sco := so.outputs[i] 167 // Check that the output can be spent. 168 if err := tb.wallet.checkOutput(tb.wallet.dbTx, consensusHeight, scoid, sco, dustThreshold); err != nil { 169 if err == errSpendHeightTooHigh { 170 potentialFund = potentialFund.Add(sco.Value) 171 } 172 continue 173 } 174 175 // Add a siacoin input for this output. 176 sci := types.SiacoinInput{ 177 ParentID: scoid, 178 UnlockConditions: tb.wallet.keys[sco.UnlockHash].UnlockConditions, 179 } 180 parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci) 181 spentScoids = append(spentScoids, scoid) 182 183 // Add the output to the total fund 184 fund = fund.Add(sco.Value) 185 potentialFund = potentialFund.Add(sco.Value) 186 if fund.Cmp(amount) >= 0 { 187 break 188 } 189 } 190 if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 { 191 return modules.ErrIncompleteTransactions 192 } 193 if fund.Cmp(amount) < 0 { 194 return modules.ErrLowBalance 195 } 196 197 // Create and add the output that will be used to fund the standard 198 // transaction. 199 parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 200 if err != nil { 201 return err 202 } 203 204 exactOutput := types.SiacoinOutput{ 205 Value: amount, 206 UnlockHash: parentUnlockConditions.UnlockHash(), 207 } 208 parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput) 209 210 // Create a refund output if needed. 211 if !amount.Equals(fund) { 212 refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 213 if err != nil { 214 return err 215 } 216 refundOutput := types.SiacoinOutput{ 217 Value: fund.Sub(amount), 218 UnlockHash: refundUnlockConditions.UnlockHash(), 219 } 220 parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput) 221 } 222 223 // Sign all of the inputs to the parent transaction. 224 for _, sci := range parentTxn.SiacoinInputs { 225 addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()]) 226 } 227 // Mark the parent output as spent. Must be done after the transaction is 228 // finished because otherwise the txid and output id will change. 229 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(parentTxn.SiacoinOutputID(0)), consensusHeight) 230 if err != nil { 231 return err 232 } 233 234 // Add the exact output. 235 newInput := types.SiacoinInput{ 236 ParentID: parentTxn.SiacoinOutputID(0), 237 UnlockConditions: parentUnlockConditions, 238 } 239 tb.newParents = append(tb.newParents, len(tb.parents)) 240 tb.parents = append(tb.parents, parentTxn) 241 tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs)) 242 tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput) 243 244 // Mark all outputs that were spent as spent. 245 for _, scoid := range spentScoids { 246 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(scoid), consensusHeight) 247 if err != nil { 248 return err 249 } 250 } 251 return nil 252 } 253 254 // FundSiafunds will add a siafund input of exactly 'amount' to the 255 // transaction. A parent transaction may be needed to achieve an input with the 256 // correct value. The siafund input will not be signed until 'Sign' is called 257 // on the transaction builder. 258 func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error { 259 tb.wallet.mu.Lock() 260 defer tb.wallet.mu.Unlock() 261 262 consensusHeight, err := dbGetConsensusHeight(tb.wallet.dbTx) 263 if err != nil { 264 return err 265 } 266 267 // Create and fund a parent transaction that will add the correct amount of 268 // siafunds to the transaction. 269 var fund types.Currency 270 var potentialFund types.Currency 271 parentTxn := types.Transaction{} 272 var spentSfoids []types.SiafundOutputID 273 c := tb.wallet.dbTx.Bucket(bucketSiafundOutputs).Cursor() 274 for idBytes, sfoBytes := c.First(); idBytes != nil; idBytes, sfoBytes = c.Next() { 275 var sfoid types.SiafundOutputID 276 var sfo types.SiafundOutput 277 if err := encoding.Unmarshal(idBytes, &sfoid); err != nil { 278 return err 279 } else if err := encoding.Unmarshal(sfoBytes, &sfo); err != nil { 280 return err 281 } 282 283 // Check that this output has not recently been spent by the wallet. 284 spendHeight, err := dbGetSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid)) 285 if err != nil { 286 // mimic map behavior: no entry means zero value 287 spendHeight = 0 288 } 289 // Prevent an underflow error. 290 allowedHeight := consensusHeight - RespendTimeout 291 if consensusHeight < RespendTimeout { 292 allowedHeight = 0 293 } 294 if spendHeight > allowedHeight { 295 potentialFund = potentialFund.Add(sfo.Value) 296 continue 297 } 298 outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions 299 if consensusHeight < outputUnlockConditions.Timelock { 300 continue 301 } 302 303 // Add a siafund input for this output. 304 parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 305 if err != nil { 306 return err 307 } 308 sfi := types.SiafundInput{ 309 ParentID: sfoid, 310 UnlockConditions: outputUnlockConditions, 311 ClaimUnlockHash: parentClaimUnlockConditions.UnlockHash(), 312 } 313 parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi) 314 spentSfoids = append(spentSfoids, sfoid) 315 316 // Add the output to the total fund 317 fund = fund.Add(sfo.Value) 318 potentialFund = potentialFund.Add(sfo.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 exactOutput := types.SiafundOutput{ 337 Value: amount, 338 UnlockHash: parentUnlockConditions.UnlockHash(), 339 } 340 parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput) 341 342 // Create a refund output if needed. 343 if !amount.Equals(fund) { 344 refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 345 if err != nil { 346 return err 347 } 348 refundOutput := types.SiafundOutput{ 349 Value: fund.Sub(amount), 350 UnlockHash: refundUnlockConditions.UnlockHash(), 351 } 352 parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput) 353 } 354 355 // Sign all of the inputs to the parent transaction. 356 for _, sfi := range parentTxn.SiafundInputs { 357 addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()]) 358 } 359 360 // Add the exact output. 361 claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress(tb.wallet.dbTx) 362 if err != nil { 363 return err 364 } 365 newInput := types.SiafundInput{ 366 ParentID: parentTxn.SiafundOutputID(0), 367 UnlockConditions: parentUnlockConditions, 368 ClaimUnlockHash: claimUnlockConditions.UnlockHash(), 369 } 370 tb.newParents = append(tb.newParents, len(tb.parents)) 371 tb.parents = append(tb.parents, parentTxn) 372 tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs)) 373 tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput) 374 375 // Mark all outputs that were spent as spent. 376 for _, sfoid := range spentSfoids { 377 err = dbPutSpentOutput(tb.wallet.dbTx, types.OutputID(sfoid), consensusHeight) 378 if err != nil { 379 return err 380 } 381 } 382 return nil 383 } 384 385 // AddParents adds a set of parents to the transaction. 386 func (tb *transactionBuilder) AddParents(newParents []types.Transaction) { 387 tb.parents = append(tb.parents, newParents...) 388 } 389 390 // AddMinerFee adds a miner fee to the transaction, returning the index of the 391 // miner fee within the transaction. 392 func (tb *transactionBuilder) AddMinerFee(fee types.Currency) uint64 { 393 tb.transaction.MinerFees = append(tb.transaction.MinerFees, fee) 394 return uint64(len(tb.transaction.MinerFees) - 1) 395 } 396 397 // AddSiacoinInput adds a siacoin input to the transaction, returning the index 398 // of the siacoin input within the transaction. When 'Sign' gets called, this 399 // input will be left unsigned. 400 func (tb *transactionBuilder) AddSiacoinInput(input types.SiacoinInput) uint64 { 401 tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, input) 402 return uint64(len(tb.transaction.SiacoinInputs) - 1) 403 } 404 405 // AddSiacoinOutput adds a siacoin output to the transaction, returning the 406 // index of the siacoin output within the transaction. 407 func (tb *transactionBuilder) AddSiacoinOutput(output types.SiacoinOutput) uint64 { 408 tb.transaction.SiacoinOutputs = append(tb.transaction.SiacoinOutputs, output) 409 return uint64(len(tb.transaction.SiacoinOutputs) - 1) 410 } 411 412 // AddFileContract adds a file contract to the transaction, returning the index 413 // of the file contract within the transaction. 414 func (tb *transactionBuilder) AddFileContract(fc types.FileContract) uint64 { 415 tb.transaction.FileContracts = append(tb.transaction.FileContracts, fc) 416 return uint64(len(tb.transaction.FileContracts) - 1) 417 } 418 419 // AddFileContractRevision adds a file contract revision to the transaction, 420 // returning the index of the file contract revision within the transaction. 421 // When 'Sign' gets called, this revision will be left unsigned. 422 func (tb *transactionBuilder) AddFileContractRevision(fcr types.FileContractRevision) uint64 { 423 tb.transaction.FileContractRevisions = append(tb.transaction.FileContractRevisions, fcr) 424 return uint64(len(tb.transaction.FileContractRevisions) - 1) 425 } 426 427 // AddStorageProof adds a storage proof to the transaction, returning the index 428 // of the storage proof within the transaction. 429 func (tb *transactionBuilder) AddStorageProof(sp types.StorageProof) uint64 { 430 tb.transaction.StorageProofs = append(tb.transaction.StorageProofs, sp) 431 return uint64(len(tb.transaction.StorageProofs) - 1) 432 } 433 434 // AddSiafundInput adds a siafund input to the transaction, returning the index 435 // of the siafund input within the transaction. When 'Sign' is called, this 436 // input will be left unsigned. 437 func (tb *transactionBuilder) AddSiafundInput(input types.SiafundInput) uint64 { 438 tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, input) 439 return uint64(len(tb.transaction.SiafundInputs) - 1) 440 } 441 442 // AddSiafundOutput adds a siafund output to the transaction, returning the 443 // index of the siafund output within the transaction. 444 func (tb *transactionBuilder) AddSiafundOutput(output types.SiafundOutput) uint64 { 445 tb.transaction.SiafundOutputs = append(tb.transaction.SiafundOutputs, output) 446 return uint64(len(tb.transaction.SiafundOutputs) - 1) 447 } 448 449 // AddArbitraryData adds arbitrary data to the transaction, returning the index 450 // of the data within the transaction. 451 func (tb *transactionBuilder) AddArbitraryData(arb []byte) uint64 { 452 tb.transaction.ArbitraryData = append(tb.transaction.ArbitraryData, arb) 453 return uint64(len(tb.transaction.ArbitraryData) - 1) 454 } 455 456 // AddTransactionSignature adds a transaction signature to the transaction, 457 // returning the index of the signature within the transaction. The signature 458 // should already be valid, and shouldn't sign any of the inputs that were 459 // added by calling 'FundSiacoins' or 'FundSiafunds'. 460 func (tb *transactionBuilder) AddTransactionSignature(sig types.TransactionSignature) uint64 { 461 tb.transaction.TransactionSignatures = append(tb.transaction.TransactionSignatures, sig) 462 return uint64(len(tb.transaction.TransactionSignatures) - 1) 463 } 464 465 // Drop discards all of the outputs in a transaction, returning them to the 466 // pool so that other transactions may use them. 'Drop' should only be called 467 // if a transaction is both unsigned and will not be used any further. 468 func (tb *transactionBuilder) Drop() { 469 tb.wallet.mu.Lock() 470 defer tb.wallet.mu.Unlock() 471 472 // Iterate through all parents and the transaction itself and restore all 473 // outputs to the list of available outputs. 474 txns := append(tb.parents, tb.transaction) 475 for _, txn := range txns { 476 for _, sci := range txn.SiacoinInputs { 477 dbDeleteSpentOutput(tb.wallet.dbTx, types.OutputID(sci.ParentID)) 478 } 479 } 480 481 tb.parents = nil 482 tb.signed = false 483 tb.transaction = types.Transaction{} 484 485 tb.newParents = nil 486 tb.siacoinInputs = nil 487 tb.siafundInputs = nil 488 tb.transactionSignatures = nil 489 } 490 491 // Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds' and 492 // return a transaction set that contains all parents prepended to the 493 // transaction. If more fields need to be added, a new transaction builder will 494 // need to be created. 495 // 496 // If the whole transaction flag is set to true, then the whole transaction 497 // flag will be set in the covered fields object. If the whole transaction flag 498 // is set to false, then the covered fields object will cover all fields that 499 // have already been added to the transaction, but will also leave room for 500 // more fields to be added. 501 // 502 // Sign should not be called more than once. If, for some reason, there is an 503 // error while calling Sign, the builder should be dropped. 504 func (tb *transactionBuilder) Sign(wholeTransaction bool) ([]types.Transaction, error) { 505 if tb.signed { 506 return nil, errBuilderAlreadySigned 507 } 508 509 // Create the coveredfields struct. 510 var coveredFields types.CoveredFields 511 if wholeTransaction { 512 coveredFields = types.CoveredFields{WholeTransaction: true} 513 } else { 514 for i := range tb.transaction.MinerFees { 515 coveredFields.MinerFees = append(coveredFields.MinerFees, uint64(i)) 516 } 517 for i := range tb.transaction.SiacoinInputs { 518 coveredFields.SiacoinInputs = append(coveredFields.SiacoinInputs, uint64(i)) 519 } 520 for i := range tb.transaction.SiacoinOutputs { 521 coveredFields.SiacoinOutputs = append(coveredFields.SiacoinOutputs, uint64(i)) 522 } 523 for i := range tb.transaction.FileContracts { 524 coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i)) 525 } 526 for i := range tb.transaction.FileContractRevisions { 527 coveredFields.FileContractRevisions = append(coveredFields.FileContractRevisions, uint64(i)) 528 } 529 for i := range tb.transaction.StorageProofs { 530 coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i)) 531 } 532 for i := range tb.transaction.SiafundInputs { 533 coveredFields.SiafundInputs = append(coveredFields.SiafundInputs, uint64(i)) 534 } 535 for i := range tb.transaction.SiafundOutputs { 536 coveredFields.SiafundOutputs = append(coveredFields.SiafundOutputs, uint64(i)) 537 } 538 for i := range tb.transaction.ArbitraryData { 539 coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i)) 540 } 541 } 542 // TransactionSignatures don't get covered by the 'WholeTransaction' flag, 543 // and must be covered manually. 544 for i := range tb.transaction.TransactionSignatures { 545 coveredFields.TransactionSignatures = append(coveredFields.TransactionSignatures, uint64(i)) 546 } 547 548 // For each siacoin input in the transaction that we added, provide a 549 // signature. 550 tb.wallet.mu.RLock() 551 defer tb.wallet.mu.RUnlock() 552 for _, inputIndex := range tb.siacoinInputs { 553 input := tb.transaction.SiacoinInputs[inputIndex] 554 key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()] 555 if !ok { 556 return nil, errors.New("transaction builder added an input that it cannot sign") 557 } 558 newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key) 559 tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...) 560 tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues. 561 } 562 for _, inputIndex := range tb.siafundInputs { 563 input := tb.transaction.SiafundInputs[inputIndex] 564 key, ok := tb.wallet.keys[input.UnlockConditions.UnlockHash()] 565 if !ok { 566 return nil, errors.New("transaction builder added an input that it cannot sign") 567 } 568 newSigIndices := addSignatures(&tb.transaction, coveredFields, input.UnlockConditions, crypto.Hash(input.ParentID), key) 569 tb.transactionSignatures = append(tb.transactionSignatures, newSigIndices...) 570 tb.signed = true // Signed is set to true after one successful signature to indicate that future signings can cause issues. 571 } 572 573 // Get the transaction set and delete the transaction from the registry. 574 txnSet := append(tb.parents, tb.transaction) 575 return txnSet, nil 576 } 577 578 // ViewTransaction returns a transaction-in-progress along with all of its 579 // parents, specified by id. An error is returned if the id is invalid. Note 580 // that ids become invalid for a transaction after 'SignTransaction' has been 581 // called because the transaction gets deleted. 582 func (tb *transactionBuilder) View() (types.Transaction, []types.Transaction) { 583 return tb.transaction, tb.parents 584 } 585 586 // ViewAdded returns all of the siacoin inputs, siafund inputs, and parent 587 // transactions that have been automatically added by the builder. 588 func (tb *transactionBuilder) ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int) { 589 return tb.newParents, tb.siacoinInputs, tb.siafundInputs, tb.transactionSignatures 590 } 591 592 // registerTransaction takes a transaction and its parents and returns a 593 // wallet.TransactionBuilder which can be used to expand the transaction. The 594 // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which 595 // registers a new transaction without parents. 596 func (w *Wallet) registerTransaction(t types.Transaction, parents []types.Transaction) *transactionBuilder { 597 // Create a deep copy of the transaction and parents by encoding them. A 598 // deep copy ensures that there are no pointer or slice related errors - 599 // the builder will be working directly on the transaction, and the 600 // transaction may be in use elsewhere (in this case, the host is using the 601 // transaction. 602 pBytes := encoding.Marshal(parents) 603 var pCopy []types.Transaction 604 err := encoding.Unmarshal(pBytes, &pCopy) 605 if err != nil { 606 panic(err) 607 } 608 tBytes := encoding.Marshal(t) 609 var tCopy types.Transaction 610 err = encoding.Unmarshal(tBytes, &tCopy) 611 if err != nil { 612 panic(err) 613 } 614 return &transactionBuilder{ 615 parents: pCopy, 616 transaction: tCopy, 617 618 wallet: w, 619 } 620 } 621 622 // RegisterTransaction takes a transaction and its parents and returns a 623 // modules.TransactionBuilder which can be used to expand the transaction. The 624 // most typical call is 'RegisterTransaction(types.Transaction{}, nil)', which 625 // registers a new transaction without parents. 626 func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) modules.TransactionBuilder { 627 w.mu.Lock() 628 defer w.mu.Unlock() 629 return w.registerTransaction(t, parents) 630 } 631 632 // StartTransaction is a convenience function that calls 633 // RegisterTransaction(types.Transaction{}, nil). 634 func (w *Wallet) StartTransaction() modules.TransactionBuilder { 635 return w.RegisterTransaction(types.Transaction{}, nil) 636 }