From c0d13d3fa9fded78d539beab23c4bdc3c235c15c Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 11 Nov 2020 14:09:36 -0800 Subject: [PATCH] account: check dust outputs on account spending transactions --- account/manager.go | 11 +++++++++-- account/manager_test.go | 24 ++++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/account/manager.go b/account/manager.go index 070b49bf0..59afa85fe 100644 --- a/account/manager.go +++ b/account/manager.go @@ -972,7 +972,7 @@ func (m *Manager) DepositAccount(ctx context.Context, return nil, nil, err } if account.State != StateOpen { - return nil, nil, fmt.Errorf("account must be in %v to be"+ + return nil, nil, fmt.Errorf("account must be in %v to be "+ "modified", StateOpen) } @@ -1044,7 +1044,7 @@ func (m *Manager) WithdrawAccount(ctx context.Context, return nil, nil, err } if account.State != StateOpen { - return nil, nil, fmt.Errorf("account must be in %v to be"+ + return nil, nil, fmt.Errorf("account must be in %v to be "+ "modified", StateOpen) } @@ -1700,6 +1700,13 @@ func sanityCheckAccountSpendTx(tx *wire.MsgTx, account *Account, return err } + // None of the outputs should be dust. + for _, output := range tx.TxOut { + if txrules.IsDustOutput(output, txrules.DefaultRelayFeePerKb) { + return fmt.Errorf("dust output %x", output.PkScript) + } + } + // CheckTransactionSanity doesn't have enough context to attempt fee // calculation, but we do. // diff --git a/account/manager_test.go b/account/manager_test.go index e14cfec83..8cf062a02 100644 --- a/account/manager_test.go +++ b/account/manager_test.go @@ -857,14 +857,27 @@ func TestAccountWithdrawal(t *testing.T) { defer h.stop() const bestHeight = 100 + const feeRate = chainfee.FeePerKwFloor + account := h.openAccount( maxAccountValue, bestHeight+maxAccountExpiry, bestHeight, ) - // With our account created, we'll start our withdrawal by creating the - // outputs we'll withdraw our funds to. We'll create three outputs, one - // of each supported output type. Each output will have 1/4 of the - // account's value. + // With our account created, we'll start with an invalid withdrawal to a + // dust output, which should fail. + dustOutput := &wire.TxOut{Value: 0, PkScript: p2wsh} + _, _, err := h.manager.WithdrawAccount( + context.Background(), account.TraderKey.PubKey, + []*wire.TxOut{dustOutput}, feeRate, bestHeight, + ) + if err == nil || !strings.Contains(err.Error(), "dust output") { + t.Fatalf("expected dust output error, got: %v", err) + } + + // We'll now attempt a withdrawal that should succeed. We'll start by + // creating the outputs we'll withdraw our funds to. We'll create three + // outputs, one of each supported output type. Each output will have 1/4 + // of the account's value. valuePerOutput := account.Value / 4 outputs := []*wire.TxOut{ { @@ -884,14 +897,13 @@ func TestAccountWithdrawal(t *testing.T) { // We'll use the lowest fee rate possible, which should yield a // transaction fee of 260 satoshis when taking into account the outputs // we'll be withdrawing to. - const feeRate = chainfee.FeePerKwFloor const expectedFee btcutil.Amount = 260 // Attempt the withdrawal. // // If successful, we'll follow with a series of assertions to ensure it // was performed correctly. - _, _, err := h.manager.WithdrawAccount( + _, _, err = h.manager.WithdrawAccount( context.Background(), account.TraderKey.PubKey, outputs, feeRate, bestHeight, )