Skip to content

Commit

Permalink
Merge pull request #2198 from Roasbeef/sendall-rpc
Browse files Browse the repository at this point in the history
multi: add ability to sweep all coins in the the wallet to an addr to sendcoins
  • Loading branch information
Roasbeef committed Jan 15, 2019
2 parents 490ce4c + e140cbb commit 509bed6
Show file tree
Hide file tree
Showing 22 changed files with 1,762 additions and 713 deletions.
13 changes: 7 additions & 6 deletions breacharbiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,13 +807,13 @@ func (bo *breachedOutput) SignDesc() *lnwallet.SignDescriptor {
return &bo.signDesc
}

// BuildWitness computes a valid witness that allows us to spend from the
// CraftInputScript computes a valid witness that allows us to spend from the
// breached output. It does so by first generating and memoizing the witness
// generation function, which parameterized primarily by the witness type and
// sign descriptor. The method then returns the witness computed by invoking
// this function on the first and subsequent calls.
func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
func (bo *breachedOutput) CraftInputScript(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*lnwallet.InputScript, error) {

// First, we ensure that the witness generation function has been
// initialized for this breached output.
Expand Down Expand Up @@ -1082,15 +1082,16 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// First, we construct a valid witness for this outpoint and
// transaction using the SpendableOutput's witness generation
// function.
witness, err := so.BuildWitness(b.cfg.Signer, txn, hashCache,
idx)
inputScript, err := so.CraftInputScript(
b.cfg.Signer, txn, hashCache, idx,
)
if err != nil {
return err
}

// Then, we add the witness to the transaction at the
// appropriate txin index.
txn.TxIn[idx].Witness = witness
txn.TxIn[idx].Witness = inputScript.Witness

return nil
}
Expand Down
17 changes: 14 additions & 3 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,13 @@ var sendCoinsCommand = cli.Command{
Name: "addr",
Usage: "the BASE58 encoded bitcoin address to send coins to on-chain",
},
// TODO(roasbeef): switch to BTC on command line? int may not be sufficient
cli.BoolFlag{
Name: "sweepall",
Usage: "if set, then the amount field will be ignored, " +
"and all the wallet will attempt to sweep all " +
"outputs within the wallet to the target " +
"address",
},
cli.Int64Flag{
Name: "amt",
Usage: "the number of bitcoin denominated in satoshis to send",
Expand Down Expand Up @@ -215,14 +221,18 @@ func sendCoins(ctx *cli.Context) error {
amt = ctx.Int64("amt")
case args.Present():
amt, err = strconv.ParseInt(args.First(), 10, 64)
default:
case !ctx.Bool("sweepall"):
return fmt.Errorf("Amount argument missing")
}

if err != nil {
return fmt.Errorf("unable to decode amount: %v", err)
}

if amt != 0 && ctx.Bool("sweepall") {
return fmt.Errorf("amount cannot be set if attempting to " +
"sweep all coins out of the wallet")
}

ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()
Expand All @@ -232,6 +242,7 @@ func sendCoins(ctx *cli.Context) error {
Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
SendAll: ctx.Bool("sweepall"),
}
txid, err := client.SendCoins(ctxb, req)
if err != nil {
Expand Down
10 changes: 8 additions & 2 deletions contractcourt/contract_resolvers.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,10 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// TODO: Use time-based sweeper and result chan.
var err error
h.sweepTx, err = h.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, sweepConfTarget, 0,
[]sweep.Input{&input},
sweep.FeePreference{
ConfTarget: sweepConfTarget,
}, 0,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1270,7 +1273,10 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
//
// TODO: Use time-based sweeper and result chan.
c.sweepTx, err = c.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, sweepConfTarget, 0,
[]sweep.Input{&input},
sweep.FeePreference{
ConfTarget: sweepConfTarget,
}, 0,
)
if err != nil {
return nil, err
Expand Down
83 changes: 83 additions & 0 deletions lnd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12617,12 +12617,95 @@ func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) {
cleanupForceClose(t, net, net.Bob, chanPoint)
}

// testSweepAllCoins tests that we're able to properly sweep all coins from the
// wallet into a single target address at the specified fee rate.
func testSweepAllCoins(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()

// First, we'll make a new node, ainz who'll we'll use to test wallet
// sweeping.
ainz, err := net.NewNode("Ainz", nil)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, ainz)

// Next, we'll give Ainz exactly 2 utxos of 1 BTC each, with one of
// them being p2wkh and the other being a n2wpkh address.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, ainz)
if err != nil {
t.Fatalf("unable to send coins to eve: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.SendCoinsNP2WKH(ctxt, btcutil.SatoshiPerBitcoin, ainz)
if err != nil {
t.Fatalf("unable to send coins to eve: %v", err)
}

// With the two coins above mined, we'll now instruct ainz to sweep all
// the coins to an external address not under its control.
minerAddr, err := net.Miner.NewAddress()
if err != nil {
t.Fatalf("unable to create new miner addr: %v", err)
}

sweepReq := &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = ainz.SendCoins(ctxt, sweepReq)
if err != nil {
t.Fatalf("unable to sweep coins: %v", err)
}

// We'll mine a block wish should include the sweep transaction we
// generated above.
block := mineBlocks(t, net, 1, 1)[0]

// The sweep transaction should have exactly two inputs as we only had
// two UTXOs in the wallet.
sweepTx := block.Transactions[1]
if len(sweepTx.TxIn) != 2 {
t.Fatalf("expected 2 inputs instead have %v", len(sweepTx.TxIn))
}

// Finally, Ainz should now have no coins at all within his wallet.
balReq := &lnrpc.WalletBalanceRequest{}
resp, err := ainz.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get ainz's balance: %v", err)
}
switch {
case resp.ConfirmedBalance != 0:
t.Fatalf("expected no confirmed balance, instead have %v",
resp.ConfirmedBalance)

case resp.UnconfirmedBalance != 0:
t.Fatalf("expected no unconfirmed balance, instead have %v",
resp.UnconfirmedBalance)
}

// If we try again, but this time specifying an amount, then the call
// should fail.
sweepReq.Amount = 10000
_, err = ainz.SendCoins(ctxt, sweepReq)
if err == nil {
t.Fatalf("sweep attempt should fail")
}
}

type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest)
}

var testsCases = []*testCase{
{
name: "sweep coins",
test: testSweepAllCoins,
},
{
name: "onchain fund recovery",
test: testOnchainFundRecovery,
Expand Down
Loading

0 comments on commit 509bed6

Please sign in to comment.