Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dust Outputs #107

Closed
Polybius93 opened this issue Aug 7, 2024 · 5 comments
Closed

Dust Outputs #107

Polybius93 opened this issue Aug 7, 2024 · 5 comments

Comments

@Polybius93
Copy link

Polybius93 commented Aug 7, 2024

The selectUTXO function produces occasionally dust outputs.

This is how I use the selectUTXO function:

const selected = selectUTXO(userUTXOs, psbtOutputs, 'default', {
    changeAddress: depositAddress,
    feePerByte: feeRate,
    bip69: false,
    createTx: true,
    network: bitcoinNetwork,
  });
 
  const psbtOutputs = [
    { address: address1, amount: 99008600n },
    {
      address: address2,
      amount: 990086n,
    },
  ];

Here is the UTXO being used:

[
{
  type: 'wpkh',
  script: Uint8Array.from([
    0, 20, 111, 177, 23, 166, 58, 9, 93, 222, 94, 102, 35, 25, 4, 126, 27, 187, 50, 70, 252, 61,
  ]),
  address: 'bcrt1qd7c30f36p9wauhnxyvvsglsmhveydlpaqrz6md',
  txid: 'abb220f97c47289175af1ca1d199c19c9c37190bc6ea651e69e04cbd09f19501',
  index: 0,
  value: 100000000,
  witnessUtxo: {
    script: Uint8Array.from([
      0, 20, 111, 177, 23, 166, 58, 9, 93, 222, 94, 102, 35, 25, 4, 126, 27, 187, 50, 70, 252, 61,
    ]),
    amount: 100000000n,
  },
  redeemScript: undefined,
},
];

And after selectUTXO, this transaction is created:

Transaction {
        global: { txVersion: 2 },
        inputs: [
          {
            txid: [Uint8Array],
            index: 0,
            witnessUtxo: [Object],
            sequence: 4294967280,
            partialSig: [Array]
          }
        ],
        outputs: [
          { amount: 99008600n, script: [Uint8Array] },
          { amount: 990086n, script: [Uint8Array] },
          { amount: 210n, script: [Uint8Array] }
        ],
        opts: {
          createTx: true,
          bip69: false,
          changeAddress: 'bcrt1qd7c30f36p9wauhnxyvvsglsmhveydlpaqrz6md',
          feePerByte: 6n,
          network: {
            messagePrefix: '\x18Bitcoin Signed Message:\n',
            bech32: 'bcrt',
            bip32: [Object],
            pubKeyHash: 111,
            scriptHash: 196,
            wif: 239
          },
          version: 2,
          lockTime: 0,
          PSBTVersion: 0
        }
      }

Is there a way to avoid this, or I have to check if any of the transaction's output is under the dust limit?

Thank you very much!
@paulmillr

@conduition
Copy link

@Polybius93 it looks like this library uses a dust limit of 148, and so that TXO of value 210 isn't considered dust by scure.

private dust = 148n; // compat with coinselect

When adding outputs to the transaction, scure will add a change output if the value of the change exceeds 148.

if (change > this.dust) needChange = true;

@paulmillr I believe this is a bug. The line in core referenced by the code comment (link) describes a byte size of 148, not a dust limit in sats.

A typical spendable non-segwit txout is 34 bytes big, and will need a CTxIn of at least 148 bytes to spend

The actual dust limit that a node uses for its mempool relaying policy is defined by the minimum fee needed to spend an output at a particular (configurable) fee rate called the dustRelayFee.

    // "Dust" is defined in terms of dustRelayFee,
    // which has units satoshis-per-kilobyte.
    // If you'd pay more in fees than the value of the output
    // to spend something, then we consider it dust.
    // A typical spendable non-segwit txout is 34 bytes big, and will
    // need a CTxIn of at least 148 bytes to spend:
    // so dust is a spendable txout less than
    // 182*dustRelayFee/1000 (in satoshis).
    // 546 satoshis at the default rate of 3000 sat/kvB.
    // A typical spendable segwit P2WPKH txout is 31 bytes big, and will
    // need a CTxIn of at least 67 bytes to spend:
    // so dust is a spendable txout less than
    // 98*dustRelayFee/1000 (in satoshis).
    // 294 satoshis at the default rate of 3000 sat/kvB.

Source: https://github.com/bitcoin/bitcoin/blob/27a770b34b8f1dbb84760f442edb3e23a0c2420b/src/policy/policy.cpp#L28-L41

If you're looking to hardcode a single number you can use to cover most use cases, i suppose 546 should suffice (see comment above for the math).


@Polybius93 Regarding your specific case, you should be able to fix your problem by passing the dust option to selectUTXO.

if (opts.dust) {
if (typeof opts.dust !== 'bigint')
throw new Error(
`Estimator: wrong dust=${
opts.dust
}, should be of type bigint but got ${typeof opts.dust}.`
);
this.dust = opts.dust;
}

This allows you to override the dust threshold of 148 hardcoded into scure. Since you're spending a segwit input, you should be able to use the segwit dust threshold of 294. So add { dust: 294n } to your options object, and hopefully that should prevent selectUTXO from adding a dust output.

@paulmillr
Copy link
Owner

Let me know of main branch works @Polybius93 or if you need help with building of the main branch.

@conduition I think it's fixed now

@Polybius93
Copy link
Author

Let me know of main branch works @Polybius93 or if you need help with building of the main branch.

@conduition I think it's fixed now

Hey @paulmillr , thank you for the quick fix, it did work, when can we expect it to be released?

Thank you!

@conduition
Copy link

@paulmillr Thanks for the quick response, but I think you may need to reconsider the fix: d1bc93d#r145217554

@conduition
Copy link

I opened a PR with some improved dust fixes here: #109

paulmillr added a commit that referenced this issue Aug 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants