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