// spendNestedWitnessPubKey generates both a sigScript, and valid witness for // spending the passed pkScript with the specified input amount. The generated // sigScript is the version 0 p2wkh witness program corresponding to the queried // key. The witness stack is identical to that of one which spends a regular // p2wkh output. The input amount *must* correspond to the output value of the // previous pkScript, or else verification will fail since the new sighash // digest algorithm defined in BIP0143 includes the input value in the sighash. func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte, inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource, tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error { // First we need to obtain the key pair related to this p2sh output. _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, chainParams) if err != nil { return err } privKey, compressed, err := secrets.GetKey(addrs[0]) if err != nil { return err } pubKey := privKey.PubKey() var pubKeyHash []byte if compressed { pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed()) } else { pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed()) } // Next, we'll generate a valid sigScript that'll allow us to spend // the p2sh output. The sigScript will contain only a single push of // the p2wkh witness program corresponding to the matching public key // of this address. p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams) if err != nil { return err } witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr) if err != nil { return err } bldr := txscript.NewScriptBuilder() bldr.AddData(witnessProgram) sigScript, err := bldr.Script() if err != nil { return err } txIn.SignatureScript = sigScript // With the sigScript in place, we'll next generate the proper witness // that'll allow us to spend the p2wkh output. witnessScript, err := txscript.WitnessScript(tx, hashCache, idx, inputValue, witnessProgram, txscript.SigHashAll, privKey, compressed) if err != nil { return err } txIn.Witness = witnessScript return nil }
// This example demonstrates creating a script which pays to a bitcoin address. // It also prints the created script hex and uses the DisasmString function to // display the disassembled script. func ExamplePayToAddrScript() { // Parse the address to send the coins to into a btcutil.Address // which is useful to ensure the accuracy of the address and determine // the address type. It is also required for the upcoming call to // PayToAddrScript. addressStr := "12gpXQVcCL2qhTNQgyLVdCFG2Qs2px98nV" address, err := btcutil.DecodeAddress(addressStr, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } // Create a public key script that pays to the address. script, err := txscript.PayToAddrScript(address) if err != nil { fmt.Println(err) return } fmt.Printf("Script Hex: %x\n", script) disasm, err := txscript.DisasmString(script) if err != nil { fmt.Println(err) return } fmt.Println("Script Disassembly:", disasm) // Output: // Script Hex: 76a914128004ff2fcaf13b2b91eb654b1dc2b674f7ec6188ac // Script Disassembly: OP_DUP OP_HASH160 128004ff2fcaf13b2b91eb654b1dc2b674f7ec61 OP_EQUALVERIFY OP_CHECKSIG }
// TstCreateSeriesCredits creates a new credit for every item in the amounts // slice, locked to the given series' address with branch==1 and index==0. func TstCreateSeriesCredits(t *testing.T, pool *Pool, seriesID uint32, amounts []int64) []credit { addr := TstNewWithdrawalAddress(t, pool, seriesID, Branch(1), Index(0)) pkScript, err := txscript.PayToAddrScript(addr.addr) if err != nil { t.Fatal(err) } msgTx := createMsgTx(pkScript, amounts) txSha := msgTx.TxSha() credits := make([]credit, len(amounts)) for i := range msgTx.TxOut { c := wtxmgr.Credit{ OutPoint: wire.OutPoint{ Hash: txSha, Index: uint32(i), }, BlockMeta: wtxmgr.BlockMeta{ Block: wtxmgr.Block{Height: TstInputsBlock}, }, Amount: btcutil.Amount(msgTx.TxOut[i].Value), PkScript: msgTx.TxOut[i].PkScript, } credits[i] = newCredit(c, *addr) } return credits }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy // based on the passed block height to the provided address. When the address // is nil, the coinbase transaction will instead be redeemable by anyone. // // See the comment for NewBlockTemplate for more information about why the nil // address handling is useful. func createCoinbaseTx(params *chaincfg.Params, coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address if one was // specified. Otherwise create a script that allows the coinbase to be // redeemable by anyone. var pkScript []byte if addr != nil { var err error pkScript, err = txscript.PayToAddrScript(addr) if err != nil { return nil, err } } else { var err error scriptBuilder := txscript.NewScriptBuilder() pkScript, err = scriptBuilder.AddOp(txscript.OP_TRUE).Script() if err != nil { return nil, err } } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, params), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// selectCoinsAndChange performs coin selection in order to obtain witness // outputs which sum to at least 'numCoins' amount of satoshis. If coin // selection is successful/possible, then the selected coins are available // within the passed contribution's inputs. If necessary, a change address will // also be generated. // TODO(roasbeef): remove hardcoded fees and req'd confs for outputs. func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount, contribution *ChannelContribution) error { // We hold the coin select mutex while querying for outputs, and // performing coin selection in order to avoid inadvertent double // spends across funding transactions. l.coinSelectMtx.Lock() defer l.coinSelectMtx.Unlock() // Find all unlocked unspent witness outputs with greater than 1 // confirmation. // TODO(roasbeef): make num confs a configuration paramter coins, err := l.ListUnspentWitness(1) if err != nil { return err } // Perform coin selection over our available, unlocked unspent outputs // in order to find enough coins to meet the funding amount // requirements. selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins) if err != nil { return err } // Lock the selected coins. These coins are now "reserved", this // prevents concurrent funding requests from referring to and this // double-spending the same set of coins. contribution.Inputs = make([]*wire.TxIn, len(selectedCoins)) for i, coin := range selectedCoins { l.lockedOutPoints[*coin] = struct{}{} l.LockOutpoint(*coin) // Empty sig script, we'll actually sign if this reservation is // queued up to be completed (the other side accepts). contribution.Inputs[i] = wire.NewTxIn(coin, nil, nil) } // Record any change output(s) generated as a result of the coin // selection. if changeAmt != 0 { changeAddr, err := l.NewAddress(WitnessPubKey, true) if err != nil { return err } changeScript, err := txscript.PayToAddrScript(changeAddr) if err != nil { return err } contribution.ChangeOutputs = make([]*wire.TxOut, 1) contribution.ChangeOutputs[0] = &wire.TxOut{ Value: int64(changeAmt), PkScript: changeScript, } } return nil }
func getTestTxId(miner *rpctest.Harness) (*wire.ShaHash, error) { script, err := txscript.PayToAddrScript(testAddr) if err != nil { return nil, err } outputs := []*wire.TxOut{&wire.TxOut{2e8, script}} return miner.CoinbaseSpend(outputs) }
// makeDestinationScriptSource creates a ChangeSource which is used to receive // all correlated previous input value. A non-change address is created by this // function. func makeDestinationScriptSource(rpcClient *btcrpcclient.Client, accountName string) txauthor.ChangeSource { return func() ([]byte, error) { destinationAddress, err := rpcClient.GetNewAddress(accountName) if err != nil { return nil, err } return txscript.PayToAddrScript(destinationAddress) } }
// makeTestOutput creates an on-chain output paying to a freshly generated // p2pkh output with the specified amount. func makeTestOutput(r *rpctest.Harness, t *testing.T, amt btcutil.Amount) (*btcec.PrivateKey, *wire.OutPoint, []byte, error) { // Create a fresh key, then send some coins to an address spendable by // that key. key, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { return nil, nil, nil, err } // Using the key created above, generate a pkScript which it's able to // spend. a, err := btcutil.NewAddressPubKey(key.PubKey().SerializeCompressed(), r.ActiveNet) if err != nil { return nil, nil, nil, err } selfAddrScript, err := txscript.PayToAddrScript(a.AddressPubKeyHash()) if err != nil { return nil, nil, nil, err } output := &wire.TxOut{PkScript: selfAddrScript, Value: 1e8} // Next, create and broadcast a transaction paying to the output. fundTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { return nil, nil, nil, err } txHash, err := r.Node.SendRawTransaction(fundTx, true) if err != nil { return nil, nil, nil, err } // The transaction created above should be included within the next // generated block. blockHash, err := r.Node.Generate(1) if err != nil { return nil, nil, nil, err } assertTxInBlock(r, t, blockHash[0], txHash) // Locate the output index of the coins spendable by the key we // generated above, this is needed in order to create a proper utxo for // this output. var outputIndex uint32 if bytes.Equal(fundTx.TxOut[0].PkScript, selfAddrScript) { outputIndex = 0 } else { outputIndex = 1 } utxo := &wire.OutPoint{ Hash: fundTx.TxHash(), Index: outputIndex, } return key, utxo, selfAddrScript, nil }
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, numOutputs, btcPerOutput int) error { // Using the mining node, spend from a coinbase output numOutputs to // give us btcPerOutput with each output. satoshiPerOutput := int64(btcPerOutput * 1e8) addrs := make([]btcutil.Address, 0, numOutputs) for i := 0; i < numOutputs; i++ { // Grab a fresh address from the wallet to house this output. walletAddr, err := w.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return err } script, err := txscript.PayToAddrScript(walletAddr) if err != nil { return err } addrs = append(addrs, walletAddr) output := &wire.TxOut{satoshiPerOutput, script} if _, err := miner.CoinbaseSpend([]*wire.TxOut{output}); err != nil { return err } } // TODO(roasbeef): shouldn't hardcode 10, use config param that dictates // how many confs we wait before opening a channel. // Generate 10 blocks with the mining node, this should mine all // numOutputs transactions created above. We generate 10 blocks here // in order to give all the outputs a "sufficient" number of confirmations. if _, err := miner.Node.Generate(10); err != nil { return err } // Wait until the wallet has finished syncing up to the main chain. ticker := time.NewTicker(100 * time.Millisecond) expectedBalance := btcutil.Amount(satoshiPerOutput * int64(numOutputs)) out: for { select { case <-ticker.C: balance, err := w.ConfirmedBalance(1, false) if err != nil { return err } if balance == expectedBalance { break out } } } ticker.Stop() return nil }
// createCSVOutput creates an output paying to a trivially redeemable CSV // pkScript with the specified time-lock. func createCSVOutput(r *rpctest.Harness, t *testing.T, numSatoshis btcutil.Amount, timeLock int32, isSeconds bool) ([]byte, *wire.OutPoint, *wire.MsgTx, error) { // Convert the time-lock to the proper sequence lock based according to // if the lock is seconds or time based. sequenceLock := blockchain.LockTimeToSequence(isSeconds, uint32(timeLock)) // Our CSV script is simply: <sequenceLock> OP_CSV OP_DROP b := txscript.NewScriptBuilder(). AddInt64(int64(sequenceLock)). AddOp(txscript.OP_CHECKSEQUENCEVERIFY). AddOp(txscript.OP_DROP) csvScript, err := b.Script() if err != nil { return nil, nil, nil, err } // Using the script generated above, create a P2SH output which will be // accepted into the mempool. p2shAddr, err := btcutil.NewAddressScriptHash(csvScript, r.ActiveNet) if err != nil { return nil, nil, nil, err } p2shScript, err := txscript.PayToAddrScript(p2shAddr) if err != nil { return nil, nil, nil, err } output := &wire.TxOut{ PkScript: p2shScript, Value: int64(numSatoshis), } // Finally create a valid transaction which creates the output crafted // above. tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { return nil, nil, nil, err } var outputIndex uint32 if !bytes.Equal(tx.TxOut[0].PkScript, p2shScript) { outputIndex = 1 } utxo := &wire.OutPoint{ Hash: tx.TxHash(), Index: outputIndex, } return csvScript, utxo, tx, nil }
func TstCreatePkScript(t *testing.T, p *Pool, seriesID uint32, branch Branch, idx Index) []byte { script := TstEnsureUsedAddr(t, p, seriesID, branch, idx) addr, err := p.addressFor(script) if err != nil { t.Fatal(err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatal(err) } return pkScript }
func TestSignMultiSigUTXOPkScriptNotP2SH(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() mgr := pool.Manager() tx := createWithdrawalTx(t, pool, []int64{4e6}, []int64{}) addr, _ := btcutil.DecodeAddress("1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", mgr.ChainParams()) pubKeyHashPkScript, _ := txscript.PayToAddrScript(addr.(*btcutil.AddressPubKeyHash)) msgtx := tx.toMsgTx() err := signMultiSigUTXO(mgr, msgtx, 0, pubKeyHashPkScript, []RawSig{RawSig{}}) TstCheckError(t, "", err, ErrTxSigning) }
// finalizeCurrentTx finalizes the transaction in w.current, moves it to the // list of finalized transactions and replaces w.current with a new empty // transaction. func (w *withdrawal) finalizeCurrentTx() error { log.Debug("Finalizing current transaction") tx := w.current if len(tx.outputs) == 0 { log.Debug("Current transaction has no outputs, doing nothing") return nil } pkScript, err := txscript.PayToAddrScript(w.status.nextChangeAddr.addr) if err != nil { return newError(ErrWithdrawalProcessing, "failed to generate pkScript for change address", err) } if tx.addChange(pkScript) { var err error w.status.nextChangeAddr, err = nextChangeAddress(w.status.nextChangeAddr) if err != nil { return newError(ErrWithdrawalProcessing, "failed to get next change address", err) } } ntxid := tx.ntxid() for i, txOut := range tx.outputs { outputStatus := w.status.outputs[txOut.request.outBailmentID()] outputStatus.addOutpoint( OutBailmentOutpoint{ntxid: ntxid, index: uint32(i), amount: txOut.amount}) } // Check that WithdrawalOutput entries with status==success have the sum of // their outpoint amounts matching the requested amount. for _, txOut := range tx.outputs { // Look up the original request we received because txOut.request may // represent a split request and thus have a different amount from the // original one. outputStatus := w.status.outputs[txOut.request.outBailmentID()] origRequest := outputStatus.request amtFulfilled := btcutil.Amount(0) for _, outpoint := range outputStatus.outpoints { amtFulfilled += outpoint.amount } if outputStatus.status == statusSuccess && amtFulfilled != origRequest.Amount { msg := fmt.Sprintf("%s was not completely fulfilled; only %v fulfilled", origRequest, amtFulfilled) return newError(ErrWithdrawalProcessing, msg, nil) } } w.transactions = append(w.transactions, tx) w.current = newWithdrawalTx(w.txOptions) return nil }
// spendWitnessKeyHash generates, and sets a valid witness for spending the // passed pkScript with the specified input amount. The input amount *must* // correspond to the output value of the previous pkScript, or else verification // will fail since the new sighash digest algorithm defined in BIP0143 includes // the input value in the sighash. func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte, inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource, tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error { // First obtain the key pair associated with this p2wkh address. _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, chainParams) if err != nil { return err } privKey, compressed, err := secrets.GetKey(addrs[0]) if err != nil { return err } pubKey := privKey.PubKey() // Once we have the key pair, generate a p2wkh address type, respecting // the compression type of the generated key. var pubKeyHash []byte if compressed { pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed()) } else { pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed()) } p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams) if err != nil { return err } // With the concrete address type, we can now generate the // corresponding witness program to be used to generate a valid witness // which will allow us to spend this output. witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr) if err != nil { return err } witnessScript, err := txscript.WitnessScript(tx, hashCache, idx, inputValue, witnessProgram, txscript.SigHashAll, privKey, true) if err != nil { return err } txIn.Witness = witnessScript return nil }
// addrPairsToOutputs converts a map describing a set of outputs to be created, // the outputs themselves. The passed map pairs up an address, to a desired // output value amount. Each address is converted to its corresponding pkScript // to be used within the constructed output(s). func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) { outputs := make([]*wire.TxOut, 0, len(addrPairs)) for addr, amt := range addrPairs { addr, err := btcutil.DecodeAddress(addr, activeNetParams.Params) if err != nil { return nil, err } pkscript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } outputs = append(outputs, wire.NewTxOut(amt, pkscript)) } return outputs, nil }
func TstNewOutputRequest(t *testing.T, transaction uint32, address string, amount btcutil.Amount, net *chaincfg.Params) OutputRequest { addr, err := btcutil.DecodeAddress(address, net) if err != nil { t.Fatalf("Unable to decode address %s", address) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("Unable to generate pkScript for %v", addr) } return OutputRequest{ PkScript: pkScript, Address: addr, Amount: amount, Server: "server", Transaction: transaction, } }
// SignThis isn't used anymore... func (t *TxStore) SignThis(tx *wire.MsgTx) error { fmt.Printf("-= SignThis =-\n") // sort tx before signing. txsort.InPlaceSort(tx) sigs := make([][]byte, len(tx.TxIn)) // first iterate over each input for j, in := range tx.TxIn { for k := uint32(0); k < uint32(len(t.Adrs)); k++ { child, err := t.rootPrivKey.Child(k + hdkeychain.HardenedKeyStart) if err != nil { return err } myadr, err := child.Address(t.Param) if err != nil { return err } adrScript, err := txscript.PayToAddrScript(myadr) if err != nil { return err } if bytes.Equal(adrScript, in.SignatureScript) { fmt.Printf("Hit; key %d matches input %d. Signing.\n", k, j) priv, err := child.ECPrivKey() if err != nil { return err } sigs[j], err = txscript.SignatureScript( tx, j, in.SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } break } } } for i, s := range sigs { if s != nil { tx.TxIn[i].SignatureScript = s } } return nil }
func TestSignMultiSigUTXORedeemScriptNotFound(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() mgr := pool.Manager() tx := createWithdrawalTx(t, pool, []int64{4e6}, []int64{}) // This is a P2SH address for which the addr manager doesn't have the redeem // script. addr, _ := btcutil.DecodeAddress("3Hb4xcebcKg4DiETJfwjh8sF4uDw9rqtVC", mgr.ChainParams()) if _, err := mgr.Address(addr); err == nil { t.Fatalf("Address %s found in manager when it shouldn't", addr) } msgtx := tx.toMsgTx() pkScript, _ := txscript.PayToAddrScript(addr.(*btcutil.AddressScriptHash)) err := signMultiSigUTXO(mgr, msgtx, 0, pkScript, []RawSig{RawSig{}}) TstCheckError(t, "", err, ErrTxSigning) }
func testMemWalletLockedOutputs(r *Harness, t *testing.T) { // Obtain the initial balance of the wallet at this point. startingBalance := r.ConfirmedBalance() // First, create a signed transaction spending some outputs. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate new address: %v", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create script: %v", err) } outputAmt := btcutil.Amount(50 * btcutil.SatoshiPerBitcoin) output := wire.NewTxOut(int64(outputAmt), pkScript) tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("unable to create transaction: %v", err) } // The current wallet balance should now be at least 50 BTC less // (accounting for fees) than the period balance currentBalance := r.ConfirmedBalance() if !(currentBalance <= startingBalance-outputAmt) { t.Fatalf("spent outputs not locked: previous balance %v, "+ "current balance %v", startingBalance, currentBalance) } // Now unlocked all the spent inputs within the unbroadcast signed // transaction. The current balance should now be exactly that of the // starting balance. r.UnlockOutputs(tx.TxIn) currentBalance = r.ConfirmedBalance() if currentBalance != startingBalance { t.Fatalf("current and starting balance should now match: "+ "expected %v, got %v", startingBalance, currentBalance) } }
// createCoinbaseTx returns a coinbase transaction paying an appropriate // subsidy based on the passed block height to the provided address. func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address, net *chaincfg.Params) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address. pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
func testListTransactionDetails(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running list transaction details test") // Create 5 new outputs spendable by the wallet. const numTxns = 5 const outputAmt = btcutil.SatoshiPerBitcoin txids := make(map[wire.ShaHash]struct{}) for i := 0; i < numTxns; i++ { addr, err := wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { t.Fatalf("unable to create new address: %v", err) } script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) } output := &wire.TxOut{outputAmt, script} txid, err := miner.CoinbaseSpend([]*wire.TxOut{output}) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } txids[*txid] = struct{}{} } // Generate 10 blocks to mine all the transactions created above. const numBlocksMined = 10 blocks, err := miner.Node.Generate(numBlocksMined) if err != nil { t.Fatalf("unable to mine blocks: %v", err) } // Next, fetch all the current transaction details. // TODO(roasbeef): use ntfn client here instead? time.Sleep(time.Second * 2) txDetails, err := wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } // Each of the transactions created above should be found with the // proper details populated. for _, txDetail := range txDetails { if _, ok := txids[txDetail.Hash]; !ok { continue } if txDetail.NumConfirmations != numBlocksMined { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, numBlocksMined) } if txDetail.Value != outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, blocks[0]) } delete(txids, txDetail.Hash) } if len(txids) != 0 { t.Fatalf("all transactions not found in details!") } // Next create a transaction paying to an output which isn't under the // wallet's control. b := txscript.NewScriptBuilder() b.AddOp(txscript.OP_0) outputScript, err := b.Script() if err != nil { t.Fatalf("unable to make output script: %v", err) } burnOutput := wire.NewTxOut(outputAmt, outputScript) burnTXID, err := wallet.SendOutputs([]*wire.TxOut{burnOutput}) if err != nil { t.Fatalf("unable to create burn tx: %v", err) } burnBlock, err := miner.Node.Generate(1) if err != nil { t.Fatalf("unable to mine block: %v", err) } // Fetch the transaction details again, the new transaction should be // shown as debiting from the wallet's balance. time.Sleep(time.Second * 2) txDetails, err = wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } var burnTxFound bool for _, txDetail := range txDetails { if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) { continue } burnTxFound = true if txDetail.NumConfirmations != 1 { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, 1) } if txDetail.Value >= -outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, -outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, burnBlock[0]) } } if !burnTxFound { t.Fatalf("tx burning btc not found") } }
// newBobNode generates a test "ln node" to interact with Alice (us). For the // funding transaction, bob has a single output totaling 7BTC. For our basic // test, he'll fund the channel with 5BTC, leaving 2BTC to the change output. // TODO(roasbeef): proper handling of change etc. func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) { // First, parse Bob's priv key in order to obtain a key he'll use for the // multi-sig funding transaction. privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) // Next, generate an output redeemable by bob. pkHash := btcutil.Hash160(pubKey.SerializeCompressed()) bobAddr, err := btcutil.NewAddressWitnessPubKeyHash( pkHash, miner.ActiveNet) if err != nil { return nil, err } bobAddrScript, err := txscript.PayToAddrScript(bobAddr) if err != nil { return nil, err } // Give bobNode one 7 BTC output for use in creating channels. output := &wire.TxOut{7e8, bobAddrScript} mainTxid, err := miner.CoinbaseSpend([]*wire.TxOut{output}) if err != nil { return nil, err } // Mine a block in order to include the above output in a block. During // the reservation workflow, we currently test to ensure that the funding // output we're given actually exists. if _, err := miner.Node.Generate(1); err != nil { return nil, err } // Grab the transaction in order to locate the output index to Bob. tx, err := miner.Node.GetRawTransaction(mainTxid) if err != nil { return nil, err } found, index := lnwallet.FindScriptOutputIndex(tx.MsgTx(), bobAddrScript) if !found { return nil, fmt.Errorf("output to bob never created") } prevOut := wire.NewOutPoint(mainTxid, index) bobTxIn := wire.NewTxIn(prevOut, nil, nil) // Using bobs priv key above, create a change output he can spend. bobChangeOutput := wire.NewTxOut(2*1e8, bobAddrScript) // Bob's initial revocation hash is just his private key with the first // byte changed... var revocation [32]byte copy(revocation[:], bobsPrivKey) revocation[0] = 0xff // His ID is just as creative... var id [wire.HashSize]byte id[0] = 0xff return &bobNode{ id: pubKey, privKey: privKey, channelKey: pubKey, deliveryAddress: bobAddr, revocation: revocation, fundingAmt: amt, delay: 5, availableOutputs: []*wire.TxIn{bobTxIn}, changeOutputs: []*wire.TxOut{bobChangeOutput}, }, nil }
func testTransactionSubscriptions(miner *rpctest.Harness, w *lnwallet.LightningWallet, t *testing.T) { t.Log("Running transaction subscriptions test") // First, check to see if this wallet meets the TransactionNotifier // interface, if not then we'll skip this test for this particular // implementation of the WalletController. txClient, err := w.SubscribeTransactions() if err != nil { t.Fatalf("unable to generate tx subscription: %v") } defer txClient.Cancel() const ( outputAmt = btcutil.SatoshiPerBitcoin numTxns = 3 ) unconfirmedNtfns := make(chan struct{}) go func() { for i := 0; i < numTxns; i++ { txDetail := <-txClient.UnconfirmedTransactions() if txDetail.NumConfirmations != 0 { t.Fatalf("incorrect number of confs, expected %v got %v", 0, txDetail.NumConfirmations) } if txDetail.Value != outputAmt { t.Fatalf("incorrect output amt, expected %v got %v", outputAmt, txDetail.Value) } if txDetail.BlockHash != nil { t.Fatalf("block hash should be nil, is instead %v", txDetail.BlockHash) } } close(unconfirmedNtfns) }() // Next, fetch a fresh address from the wallet, create 3 new outputs // with the pkScript. for i := 0; i < numTxns; i++ { addr, err := w.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { t.Fatalf("unable to create new address: %v", err) } script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) } output := &wire.TxOut{outputAmt, script} if _, err := miner.CoinbaseSpend([]*wire.TxOut{output}); err != nil { t.Fatalf("unable to send coinbase: %v", err) } } // We should receive a notification for all three transactions // generated above. select { case <-time.After(time.Second * 5): t.Fatalf("transactions not received after 3 seconds") case <-unconfirmedNtfns: // Fall through on successs } confirmedNtfns := make(chan struct{}) go func() { for i := 0; i < numTxns; i++ { txDetail := <-txClient.ConfirmedTransactions() if txDetail.NumConfirmations != 1 { t.Fatalf("incorrect number of confs, expected %v got %v", 0, txDetail.NumConfirmations) } if txDetail.Value != outputAmt { t.Fatalf("incorrect output amt, expected %v got %v", outputAmt, txDetail.Value) } } close(confirmedNtfns) }() // Next mine a single block, all the transactions generated above // should be included. if _, err := miner.Node.Generate(1); err != nil { t.Fatalf("unable to generate block: %v", err) } // We should receive a notification for all three transactions // since they should be mined in the next block. select { case <-time.After(time.Second * 5): t.Fatalf("transactions not received after 3 seconds") case <-confirmedNtfns: // Fall through on successs } }
// This example demonstrates how to use the Pool.StartWithdrawal method. func Example_startWithdrawal() { // Create the address manager and votingpool DB namespace. See the example // for the Create() function for more info on how this is done. mgr, vpNamespace, tearDownFunc, err := exampleCreateMgrAndDBNamespace() if err != nil { fmt.Println(err) return } defer tearDownFunc() // Create a pool and a series. See the DepositAddress example for more info // on how this is done. pool, seriesID, err := exampleCreatePoolAndSeries(mgr, vpNamespace) if err != nil { fmt.Println(err) return } // Unlock the manager if err := mgr.Unlock(privPassphrase); err != nil { fmt.Println(err) return } defer mgr.Lock() addr, _ := btcutil.DecodeAddress("1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", mgr.ChainParams()) pkScript, _ := txscript.PayToAddrScript(addr) requests := []votingpool.OutputRequest{ votingpool.OutputRequest{ PkScript: pkScript, Address: addr, Amount: 1e6, Server: "server-id", Transaction: 123}, } changeStart, err := pool.ChangeAddress(seriesID, votingpool.Index(0)) if err != nil { fmt.Println(err) return } // This is only needed because we have not used any deposit addresses from // the series, and we cannot create a WithdrawalAddress for an unused // branch/idx pair. if err = pool.EnsureUsedAddr(seriesID, votingpool.Branch(1), votingpool.Index(0)); err != nil { fmt.Println(err) return } startAddr, err := pool.WithdrawalAddress(seriesID, votingpool.Branch(1), votingpool.Index(0)) if err != nil { fmt.Println(err) return } lastSeriesID := seriesID dustThreshold := btcutil.Amount(1e4) currentBlock := int32(19432) roundID := uint32(0) txstore, tearDownFunc, err := exampleCreateTxStore() if err != nil { fmt.Println(err) return } _, err = pool.StartWithdrawal( roundID, requests, *startAddr, lastSeriesID, *changeStart, txstore, currentBlock, dustThreshold) if err != nil { fmt.Println(err) } // Output: // }
// handleSingleContribution is called as the second step to a single funder // workflow to which we are the responder. It simply saves the remote peer's // contribution to the channel, as solely the remote peer will contribute any // funds to the channel. func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Simply record the counterparty's contribution into the pending // reservation data as they'll be solely funding the channel entirely. pendingReservation.theirContribution = req.contribution theirContribution := pendingReservation.theirContribution // Additionally, we can now also record the redeem script of the // funding transaction. // TODO(roasbeef): switch to proper pubkey derivation ourKey := pendingReservation.partialState.OurMultiSigKey theirKey := theirContribution.MultiSigKey channelCapacity := int64(pendingReservation.partialState.Capacity) witnessScript, _, err := GenFundingPkScript(ourKey.SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } pendingReservation.partialState.FundingWitnessScript = witnessScript masterElkremRoot, err := l.deriveMasterElkremRoot() if err != nil { req.err <- err return } // Now that we know their commitment key, we can create the revocation // key for our version of the initial commitment transaction. elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey) elkremSender := elkrem.NewElkremSender(elkremRoot) firstPreimage, err := elkremSender.AtIndex(0) if err != nil { req.err <- err return } pendingReservation.partialState.LocalElkrem = elkremSender theirCommitKey := theirContribution.CommitKey ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:]) // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). remoteElkrem := &elkrem.ElkremReceiver{} pendingReservation.partialState.RemoteElkrem = remoteElkrem // Record the counterpaty's remaining contributions to the channel, // converting their delivery address into a public key script. deliveryScript, err := txscript.PayToAddrScript(theirContribution.DeliveryAddress) if err != nil { req.err <- err return } pendingReservation.partialState.RemoteCsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryScript = deliveryScript pendingReservation.partialState.TheirCommitKey = theirContribution.CommitKey pendingReservation.partialState.TheirMultiSigKey = theirContribution.MultiSigKey pendingReservation.ourContribution.RevocationKey = ourRevokeKey req.err <- nil return }
// handleContributionMsg processes the second workflow step for the lifetime of // a channel reservation. Upon completion, the reservation will carry a // completed funding transaction (minus the counterparty's input signatures), // both versions of the commitment transaction, and our signature for their // version of the commitment transaction. func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Create a blank, fresh transaction. Soon to be a complete funding // transaction which will allow opening a lightning channel. pendingReservation.fundingTx = wire.NewMsgTx() fundingTx := pendingReservation.fundingTx // Some temporary variables to cut down on the resolution verbosity. pendingReservation.theirContribution = req.contribution theirContribution := req.contribution ourContribution := pendingReservation.ourContribution // Add all multi-party inputs and outputs to the transaction. for _, ourInput := range ourContribution.Inputs { fundingTx.AddTxIn(ourInput) } for _, theirInput := range theirContribution.Inputs { fundingTx.AddTxIn(theirInput) } for _, ourChangeOutput := range ourContribution.ChangeOutputs { fundingTx.AddTxOut(ourChangeOutput) } for _, theirChangeOutput := range theirContribution.ChangeOutputs { fundingTx.AddTxOut(theirChangeOutput) } ourKey := pendingReservation.partialState.OurMultiSigKey theirKey := theirContribution.MultiSigKey // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. channelCapacity := int64(pendingReservation.partialState.Capacity) witnessScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } pendingReservation.partialState.FundingWitnessScript = witnessScript // Sort the transaction. Since both side agree to a canonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. fundingTx.AddTxOut(multiSigOut) txsort.InPlaceSort(pendingReservation.fundingTx) // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.Inputs)) signDesc := SignDescriptor{ HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(fundingTx), } for i, txIn := range fundingTx.TxIn { info, err := l.FetchInputInfo(&txIn.PreviousOutPoint) if err == ErrNotMine { continue } else if err != nil { req.err <- err return } signDesc.Output = info signDesc.InputIndex = i inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc) if err != nil { req.err <- err return } txIn.SignatureScript = inputScript.ScriptSig txIn.Witness = inputScript.Witness pendingReservation.ourFundingInputScripts = append( pendingReservation.ourFundingInputScripts, inputScript, ) } // Locate the index of the multi-sig outpoint in order to record it // since the outputs are canonically sorted. If this is a single funder // workflow, then we'll also need to send this to the remote node. fundingTxID := fundingTx.TxSha() _, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript) fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex) pendingReservation.partialState.FundingOutpoint = fundingOutpoint // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). e := &elkrem.ElkremReceiver{} pendingReservation.partialState.RemoteElkrem = e pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey masterElkremRoot, err := l.deriveMasterElkremRoot() if err != nil { req.err <- err return } // Now that we have their commitment key, we can create the revocation // key for the first version of our commitment transaction. To do so, // we'll first create our elkrem root, then grab the first pre-iamge // from it. elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey) elkremSender := elkrem.NewElkremSender(elkremRoot) pendingReservation.partialState.LocalElkrem = elkremSender firstPreimage, err := elkremSender.AtIndex(0) if err != nil { req.err <- err return } theirCommitKey := theirContribution.CommitKey ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:]) // Create the txIn to our commitment transaction; required to construct // the commitment transactions. fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingTxID, multiSigIndex), nil, nil) // With the funding tx complete, create both commitment transactions. // TODO(roasbeef): much cleanup + de-duplication pendingReservation.fundingLockTime = theirContribution.CsvDelay ourBalance := ourContribution.FundingAmount theirBalance := theirContribution.FundingAmount ourCommitKey := ourContribution.CommitKey ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourRevokeKey, ourContribution.CsvDelay, ourBalance, theirBalance) if err != nil { req.err <- err return } theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, theirContribution.RevocationKey, theirContribution.CsvDelay, theirBalance, ourBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) deliveryScript, err := txscript.PayToAddrScript(theirContribution.DeliveryAddress) if err != nil { req.err <- err return } // Record newly available information witin the open channel state. pendingReservation.partialState.RemoteCsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryScript = deliveryScript pendingReservation.partialState.ChanID = fundingOutpoint pendingReservation.partialState.TheirCommitKey = theirCommitKey pendingReservation.partialState.TheirMultiSigKey = theirContribution.MultiSigKey pendingReservation.partialState.OurCommitTx = ourCommitTx pendingReservation.ourContribution.RevocationKey = ourRevokeKey // Generate a signature for their version of the initial commitment // transaction. signDesc = SignDescriptor{ WitnessScript: witnessScript, PubKey: ourKey, Output: multiSigOut, HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
// handleFundingReserveRequest processes a message intending to create, and // validate a funding reservation request. func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) { // It isn't possible to create a channel with zero funds committed. if req.fundingAmount+req.capacity == 0 { req.err <- fmt.Errorf("cannot have channel with zero " + "satoshis funded") req.resp <- nil return } id := atomic.AddUint64(&l.nextFundingID, 1) totalCapacity := req.capacity + commitFee reservation := NewChannelReservation(totalCapacity, req.fundingAmount, req.minFeeRate, l, id, req.numConfs) // Grab the mutex on the ChannelReservation to ensure thread-safety reservation.Lock() defer reservation.Unlock() reservation.partialState.IdentityPub = req.nodeID reservation.nodeAddr = req.nodeAddr ourContribution := reservation.ourContribution ourContribution.CsvDelay = req.csvDelay reservation.partialState.LocalCsvDelay = req.csvDelay // If we're on the receiving end of a single funder channel then we // don't need to perform any coin selection. Otherwise, attempt to // obtain enough coins to meet the required funding amount. if req.fundingAmount != 0 { // TODO(roasbeef): consult model for proper fee rate on funding // tx feeRate := uint64(10) amt := req.fundingAmount + commitFee err := l.selectCoinsAndChange(feeRate, amt, ourContribution) if err != nil { req.err <- err req.resp <- nil return } } // Grab two fresh keys from our HD chain, one will be used for the // multi-sig funding transaction, and the other for the commitment // transaction. multiSigKey, err := l.NewRawKey() if err != nil { req.err <- err req.resp <- nil return } commitKey, err := l.NewRawKey() if err != nil { req.err <- err req.resp <- nil return } reservation.partialState.OurMultiSigKey = multiSigKey ourContribution.MultiSigKey = multiSigKey reservation.partialState.OurCommitKey = commitKey ourContribution.CommitKey = commitKey // Generate a fresh address to be used in the case of a cooperative // channel close. deliveryAddress, err := l.NewAddress(WitnessPubKey, false) if err != nil { req.err <- err req.resp <- nil return } deliveryScript, err := txscript.PayToAddrScript(deliveryAddress) if err != nil { req.err <- err req.resp <- nil return } reservation.partialState.OurDeliveryScript = deliveryScript ourContribution.DeliveryAddress = deliveryAddress // Create a limbo and record entry for this newly pending funding // request. l.limboMtx.Lock() l.fundingLimbo[id] = reservation l.limboMtx.Unlock() // Funding reservation request successfully handled. The funding inputs // will be marked as unavailable until the reservation is either // completed, or canceled. req.resp <- reservation req.err <- nil }
// Ingest puts a tx into the DB atomically. This can result in a // gain, a loss, or no result. Gain or loss in satoshis is returned. func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { var hits uint32 var err error var nUtxoBytes [][]byte // tx has been OK'd by SPV; check tx sanity utilTx := btcutil.NewTx(tx) // convert for validation // checks basic stuff like there are inputs and ouputs err = blockchain.CheckTransactionSanity(utilTx) if err != nil { return hits, err } // note that you can't check signatures; this is SPV. // 0 conf SPV means pretty much nothing. Anyone can say anything. spentOPs := make([][]byte, len(tx.TxIn)) // before entering into db, serialize all inputs of the ingested tx for i, txin := range tx.TxIn { spentOPs[i], err = outPointToBytes(&txin.PreviousOutPoint) if err != nil { return hits, err } } // go through txouts, and then go through addresses to match // generate PKscripts for all addresses wPKscripts := make([][]byte, len(ts.Adrs)) aPKscripts := make([][]byte, len(ts.Adrs)) for i, _ := range ts.Adrs { // iterate through all our addresses // convert regular address to witness address. (split adrs later) wa, err := btcutil.NewAddressWitnessPubKeyHash( ts.Adrs[i].PkhAdr.ScriptAddress(), ts.Param) if err != nil { return hits, err } wPKscripts[i], err = txscript.PayToAddrScript(wa) if err != nil { return hits, err } aPKscripts[i], err = txscript.PayToAddrScript(ts.Adrs[i].PkhAdr) if err != nil { return hits, err } } cachedSha := tx.TxSha() // iterate through all outputs of this tx, see if we gain for i, out := range tx.TxOut { for j, ascr := range aPKscripts { // detect p2wpkh witBool := false if bytes.Equal(out.PkScript, wPKscripts[j]) { witBool = true } if bytes.Equal(out.PkScript, ascr) || witBool { // new utxo found var newu Utxo // create new utxo and copy into it newu.AtHeight = height newu.KeyIdx = ts.Adrs[j].KeyIdx newu.Value = out.Value newu.IsWit = witBool // copy witness version from pkscript var newop wire.OutPoint newop.Hash = cachedSha newop.Index = uint32(i) newu.Op = newop b, err := newu.ToBytes() if err != nil { return hits, err } nUtxoBytes = append(nUtxoBytes, b) hits++ break // txos can match only 1 script } } } err = ts.StateDB.Update(func(btx *bolt.Tx) error { // get all 4 buckets duf := btx.Bucket(BKTUtxos) // sta := btx.Bucket(BKTState) old := btx.Bucket(BKTStxos) txns := btx.Bucket(BKTTxns) if duf == nil || old == nil || txns == nil { return fmt.Errorf("error: db not initialized") } // iterate through duffel bag and look for matches // this makes us lose money, which is regrettable, but we need to know. for _, nOP := range spentOPs { v := duf.Get(nOP) if v != nil { hits++ // do all this just to figure out value we lost x := make([]byte, len(nOP)+len(v)) copy(x, nOP) copy(x[len(nOP):], v) lostTxo, err := UtxoFromBytes(x) if err != nil { return err } // after marking for deletion, save stxo to old bucket var st Stxo // generate spent txo st.Utxo = lostTxo // assign outpoint st.SpendHeight = height // spent at height st.SpendTxid = cachedSha // spent by txid stxb, err := st.ToBytes() // serialize if err != nil { return err } err = old.Put(nOP, stxb) // write nOP:v outpoint:stxo bytes if err != nil { return err } err = duf.Delete(nOP) if err != nil { return err } } } // done losing utxos, next gain utxos // next add all new utxos to db, this is quick as the work is above for _, ub := range nUtxoBytes { err = duf.Put(ub[:36], ub[36:]) if err != nil { return err } } // if hits is nonzero it's a relevant tx and we should store it var buf bytes.Buffer tx.Serialize(&buf) err = txns.Put(cachedSha.Bytes(), buf.Bytes()) if err != nil { return err } return nil }) return hits, err }
// txToOutputs creates a signed transaction which includes each output from // outputs. Previous outputs to reedeem are chosen from the passed account's // UTXO set and minconf policy. An additional output may be added to return // change to the wallet. An appropriate fee is included based on the wallet's // current relay fee. The wallet must be unlocked to create the transaction. func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (*txauthor.AuthoredTx, error) { // Address manager must be unlocked to compose transaction. Grab // the unlock if possible (to prevent future unlocks), or return the // error if already locked. heldUnlock, err := w.HoldUnlock() if err != nil { return nil, err } defer heldUnlock.Release() chainClient, err := w.requireChainClient() if err != nil { return nil, err } // Get current block's height and hash. bs, err := chainClient.BlockStamp() if err != nil { return nil, err } eligible, err := w.findEligibleOutputs(account, minconf, bs) if err != nil { return nil, err } inputSource := makeInputSource(eligible) changeSource := func() ([]byte, error) { // Derive the change output script. As a hack to allow spending from // the imported account, change addresses are created from account 0. var changeAddr btcutil.Address if account == waddrmgr.ImportedAddrAccount { changeAddr, err = w.NewChangeAddress(0, waddrmgr.WitnessPubKey) } else { changeAddr, err = w.NewChangeAddress(account, waddrmgr.WitnessPubKey) } if err != nil { return nil, err } return txscript.PayToAddrScript(changeAddr) } tx, err := txauthor.NewUnsignedTransaction(outputs, w.RelayFee(), inputSource, changeSource) if err != nil { return nil, err } // Randomize change position, if change exists, before signing. This // doesn't affect the serialize size, so the change amount will still be // valid. if tx.ChangeIndex >= 0 { tx.RandomizeChangePosition() } err = tx.AddAllInputScripts(secretSource{w.Manager}) if err != nil { return nil, err } err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues) if err != nil { return nil, err } if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount { changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value) log.Warnf("Spend from imported account produced change: moving"+ " %v from imported account into default account.", changeAmount) } return tx, nil }
// createSweepTx creates a final sweeping transaction with all witnesses // inplace for all inputs. The created transaction has a single output sending // all the funds back to the source wallet. func (u *utxoNursery) createSweepTx(matureOutputs []*immatureOutput) (*wire.MsgTx, error) { sweepAddr, err := u.wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return nil, err } pkScript, err := txscript.PayToAddrScript(sweepAddr) if err != nil { return nil, err } var totalSum btcutil.Amount for _, o := range matureOutputs { totalSum += o.amt } sweepTx := wire.NewMsgTx() sweepTx.Version = 2 sweepTx.AddTxOut(&wire.TxOut{ PkScript: pkScript, Value: int64(totalSum - 1000), }) for _, utxo := range matureOutputs { sweepTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: utxo.outPoint, // TODO(roasbeef): assumes pure block delays Sequence: utxo.blocksToMaturity, }) } // TODO(roasbeef): insert fee calculation // * remove hardcoded fee above // With all the inputs in place, use each output's unique witness // function to generate the final witness required for spending. hashCache := txscript.NewTxSigHashes(sweepTx) for i, txIn := range sweepTx.TxIn { witness, err := matureOutputs[i].witnessFunc(sweepTx, hashCache, i) if err != nil { return nil, err } txIn.Witness = witness } return sweepTx, nil }