Skip to content

Conversation

@tankyleo
Copy link
Contributor

@tankyleo tankyleo commented Nov 25, 2025

As part of #101

@tankyleo tankyleo force-pushed the ephemeral-dust branch 4 times, most recently from 395a212 to 58b01e2 Compare November 26, 2025 00:38
@0xB10C
Copy link
Owner

0xB10C commented Nov 26, 2025

Cool! Code wise this looks good. Did you do any analysis yet into how often it detected a ephemeral dust spent?

@tankyleo
Copy link
Contributor Author

tankyleo commented Nov 26, 2025

Here are some quick numbers:

SELECT
       block_stats.height,
       tx_spending_ephemeral_dust, pool_id
FROM tx_stats
JOIN block_stats ON tx_stats.height = block_stats.height
WHERE tx_spending_ephemeral_dust > 0
ORDER BY block_stats.height;
height tx_spending_ephemeral_dust pool_id
894455 1 MARA Pool
900559 1 MARA Pool
900676 1 AntPool
900831 1 AntPool
901136 1 AntPool
901143 1 AntPool
901146 1 F2Pool
901149 1 F2Pool
901151 1 AntPool
901152 1 F2Pool
901165 1 F2Pool
905467 1 F2Pool
905482 1 F2Pool
905530 1 F2Pool
909251 1 MARA Pool
913551 1 F2Pool
913602 1 SpiderPool
913612 2 F2Pool
913616 2 F2Pool
913617 2 SpiderPool
913631 1 F2Pool
913638 1 AntPool
913646 1 F2Pool
913676 1 F2Pool
913689 1 MARA Pool
913756 1 AntPool
913758 1 F2Pool
913766 1 F2Pool
913784 2 MARA Pool
913842 1 SpiderPool
913846 1 SpiderPool
913914 1 F2Pool
914269 1 F2Pool
914375 1 AntPool
914389 2 AntPool
915406 1 MARA Pool
915750 1 F2Pool
916047 1 AntPool
916330 1 SpiderPool
916352 1 SpiderPool
917309 1 F2Pool
917397 1 F2Pool
917804 2 F2Pool
917931 1 MARA Pool
917969 1 SpiderPool
918045 1 SpiderPool
918244 1 AntPool
918498 1 SpiderPool
920465 1 SpiderPool
920468 1 AntPool
920533 1 SpiderPool
920557 1 SpiderPool
921301 1 SpiderPool
922607 1 AntPool
923039 1 SpiderPool
923230 1 F2Pool
923686 1 AntPool
923830 1 MARA Pool
924505 1 AntPool
925202 1 SpiderPool
925262 6 MARA Pool
925273 1 SpiderPool

Note that I started collect_statistics at height 875000, still well before the v29 release.

@tankyleo
Copy link
Contributor Author

Then looking at when pools mined their first ephemeral dust:

SELECT
    t.pool_id,
    MIN(CASE WHEN t.tx_spending_ephemeral_dust > 0 THEN t.height END) AS first_ephemeral_dust_height,
    MIN(CASE WHEN t.tx_spending_ephemeral_dust > 0 THEN t.date END) AS first_ephemeral_dust_date
FROM (
    SELECT
        bs.date,
        bs.height,
        ts.tx_spending_ephemeral_dust,
        bs.pool_id
    FROM tx_stats ts
    JOIN block_stats bs ON ts.height = bs.height
    WHERE ts.tx_spending_ephemeral_dust > 0
) t
GROUP BY t.pool_id
ORDER BY first_ephemeral_dust_date;
pool_id first_ephemeral_dust_height first_ephemeral_dust_date
MARA Pool 894455 2025-04-29
AntPool 900676 2025-06-10
F2Pool 901146 2025-06-14
SpiderPool 913602 2025-09-07

@0xB10C
Copy link
Owner

0xB10C commented Nov 27, 2025

Nice, I've started a full sync with this branch too and will have a look as well. I want to build a chart of ephemeral dust spends, then this is good to go.

@0xB10C
Copy link
Owner

0xB10C commented Nov 27, 2025

pool_id first_ephemeral_dust_height first_ephemeral_dust_date
MARA Pool 894455 2025-04-29
AntPool 900676 2025-06-10
F2Pool 901146 2025-06-14
SpiderPool 913602 2025-09-07

Feel free to post these stats to https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57 if you want. It seems to me like MaraPool was fairly quick to update. Did AntPool upgrade directly to v29, likely skipping v28? SpiderPool updated to v29 sometime around ~2025-09-07 after having upgraded to v28 on ~2025-07-28. F2Pool upgraded to v28 ~2025-05-23 and then v29 around ~2025-06-14?

@tankyleo
Copy link
Contributor Author

Feel free to post these stats to https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57 if you want. It seems to me like MaraPool was fairly quick to update. Did AntPool upgrade directly to v29, likely skipping v28? SpiderPool updated to v29 sometime around ~2025-09-07 after having upgraded to v28 on ~2025-07-28. F2Pool upgraded to v28 ~2025-05-23 and then v29 around ~2025-06-14?

Yes, although Antpool still mines some blocks dropping ephemeral dust spends today. I'm thinking they are running v29 on only some of their nodes, serving different templates to subsets of their miners. Would that correspond to your past observations of Antpool ?

@tankyleo
Copy link
Contributor Author

That is an overall caveat for this PR: a mining pool mining an ephemeral dust spend on one block does not guarantee that they will mine ephemeral dust spend on all their blocks.

@tankyleo
Copy link
Contributor Author

Feel free to post these stats to https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57 if you want

Will do once you are able to reproduce the results here on your machine :)

@tankyleo
Copy link
Contributor Author

tankyleo commented Nov 27, 2025

That is an overall caveat for this PR: a mining pool mining an ephemeral dust spend on one block does not guarantee that they will mine ephemeral dust spend on all their blocks.

In your observation tooling do you have anything to audit blocks produced my miners, as compared to the template served by your local nodes ? If not would that be an interesting addition ? mempool.space has an audit feature, but I am not very confident in it. For example it shows this transaction as "expected in block" when their own data shows that this transaction should have been picked up by the previous block according to v29 policy, since it was confirmed "after 28 minutes".

https://mempool.space/tx/9a48a07affedb6b4bae1888c0876d813a5d132f8020ca5d265bb27738673d01f#vout=0

@tankyleo
Copy link
Contributor Author

mempool.space has an audit feature, but I am not very confident in it. For example it shows this transaction as "expected in block" when their own data shows that this transaction should have been picked up by the previous block according to v29 policy, since it was confirmed "after 28 minutes".

https://mempool.space/tx/9a48a07affedb6b4bae1888c0876d813a5d132f8020ca5d265bb27738673d01f#vout=0

Nonetheless, when hovering over this transaction in the audit of the previous block, it is clearly marked as "Removed".

@0xB10C
Copy link
Owner

0xB10C commented Nov 27, 2025

Yes, although Antpool still mines some blocks dropping ephemeral dust spends today. I'm thinking they are running v29 on only some of their nodes, serving different templates to subsets of their miners. Would that correspond to your past observations of Antpool ?

Yes. My assumption is that they run multiple nodes in different locations. And maybe even there have diversity of nodes they query for templates. This makes sense from a high availability standpoint. E.g.:

  • if a v29.0 nodes all have a bug/problem, get all templates from v28.1 nodes until v29.0 is patched
  • if a v28.1 node should be upgraded to v29.0, shut it down and get templates from others
  • if a data center in city X has a problem, get templates from a node in city Y
  • ...

Just recently they mined two blocks at the same height with different block contents but the same block header time. This is a clear indication that they serve different work to different miners: https://bnoc.xyz/t/antpool-mines-two-blocks-at-height-925051/60

@tankyleo
Copy link
Contributor Author

Yes, although Antpool still mines some blocks dropping ephemeral dust spends today. I'm thinking they are running v29 on only some of their nodes, serving different templates to subsets of their miners. Would that correspond to your past observations of Antpool ?

This also applies to F2Pool; in this block:
https://mempool.space/block/00000000000000000000860b5ecf836a57a17bf1417098e8a1a01d44b9f96348
they dropped this package:
https://mempool.space/tx/b6c66a8b7ea46c10a684ff5a19db12a19d3a9a4e146d7395aa4d17114a686b0b
https://mempool.space/tx/141ccf9634b1c79c7e866e6cc7ca3e96f6f10f563f98cb774f9fae2f9648c9ca

@0xB10C
Copy link
Owner

0xB10C commented Nov 27, 2025

That is an overall caveat for this PR: a mining pool mining an ephemeral dust spend on one block does not guarantee that they will mine ephemeral dust spend on all their blocks.

In your observation tooling do you have anything to audit blocks produced my miners, as compared to the template served by your local nodes ? If not would that be an interesting addition ? mempool.space has an audit feature, but I am not very confident in it. For example it shows this transaction as "expected in block" when their own data shows that this transaction should have been picked up by the previous block according to v29 policy, since it was confirmed "after 28 minutes".

https://mempool.space/tx/9a48a07affedb6b4bae1888c0876d813a5d132f8020ca5d265bb27738673d01f#vout=0

I do and I think this actually inspired the block audit feature on mempool-space! See https://b10c.me/projects/016-miningpool-observer/ and https://miningpool.observer - but note that I only recently upgrade the node behind it and forgot to turn the backend back on, so the current data isn't reliable for ephemeral dust.

@tankyleo
Copy link
Contributor Author

I do and I think this actually inspired the block audit feature on mempool-space! See https://b10c.me/projects/016-miningpool-observer/ and https://miningpool.observer - but note that I only recently upgrade the node behind it and forgot to turn the backend back on, so the current data isn't reliable for ephemeral dust.

Awesome thank you no rush. I wonder if we could use data from miningpool.observer in addition to the data collected by this PR to answer the question: "out of all blocks where mining pool X was expected to mine ephemeral dust, what fraction of their blocks did include ephemeral dust".

@0xB10C
Copy link
Owner

0xB10C commented Nov 29, 2025

Then looking at when pools mined their first ephemeral dust:

SELECT
    t.pool_id,
    MIN(CASE WHEN t.tx_spending_ephemeral_dust > 0 THEN t.height END) AS first_ephemeral_dust_height,
    MIN(CASE WHEN t.tx_spending_ephemeral_dust > 0 THEN t.date END) AS first_ephemeral_dust_date
FROM (
    SELECT
        bs.date,
        bs.height,
        ts.tx_spending_ephemeral_dust,
        bs.pool_id
    FROM tx_stats ts
    JOIN block_stats bs ON ts.height = bs.height
    WHERE ts.tx_spending_ephemeral_dust > 0
) t
GROUP BY t.pool_id
ORDER BY first_ephemeral_dust_date;

Running this on a database with the full chain indexed results in the following for me:

pool_id first_ephemeral_dust_height first_ephemeral_dust_date
Eligius 215049 2013-01-04
EclipseMC 227154 2013-03-21
F2Pool 367843 2015-07-31
MaraPool 894455 2025-04-29
AntPool 900676 2025-06-10
SpiderPool 913602 2025-09-07
Ocean 925458 2025-11-27

This seems to indicate a few false positives to me? Not sure if it's possible to filter these out. Also, Ocean seems to have mined its first ephemeral dust spent in 925458?

@tankyleo
Copy link
Contributor Author

tankyleo commented Nov 30, 2025

With the above fixups (will squash before merge), I now get the following results on a full chain scan:

pool_id first_ephemeral_dust_height first_ephemeral_dust_date
MARA Pool 894455 2025-04-29
AntPool 900676 2025-06-10
F2Pool 901146 2025-06-14
SpiderPool 913602 2025-09-07
Ocean 925458 2025-11-27

This seems to indicate a few false positives to me? Not sure if it's possible to filter these out.

I added all of the false positives to the tests so we can make tweaks to the code and quickly figure out whether the changes would catch these false positives.

You should see that asserting only that the parent transaction is V3 filters those false positives.

I now also assert that the child transaction has a non-zero fee, and is V3 with the goal of filtering any future false positives.

Finally I now filter out any coinbase transactions as we are only interested in transactions submitted via the p2p network.

Also, Ocean seems to have mined its first ephemeral dust spent in 925458?

Yes they mined the ephemeral dust I've been sprinkling on the blockchain recently
https://mempool.space/tx/f350678485ffef057ea864de70447699cb003c2573366fef78e30a0274236c74#vout=0

@tankyleo
Copy link
Contributor Author

Let me know if you'd like to tighten the conditions further; for example a chain of V3 transactions alternating between zero fee and non-zero fee would still be counted as ephemeral dust even though they violate the single-child TRUC policy.

@0xB10C
Copy link
Owner

0xB10C commented Dec 1, 2025

Let me know if you'd like to tighten the conditions further; for example a chain of V3 transactions alternating between zero fee and non-zero fee would still be counted as ephemeral dust even though they violate the single-child TRUC policy.

Do you think there's a reason someone is going to make transactions with this patter? Or possibly: is this policy going to be loosed anytime soon? I haven't been following these developments closely.

// We do not include any cases of ephemeral dust on coinbase transactions (these transactions have their
// `tx.fee` set to `None`); we are only interested in cases of ephemeral dust that could have been submitted
// via the p2p network.
if let (Some(Amount::ZERO), 3) = (tx.fee, tx.version) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is this equivalent to the following?

Suggested change
if let (Some(Amount::ZERO), 3) = (tx.fee, tx.version) {
if tx.fee == Some(Amount::ZERO) && tx.version == 3 {

If so, then I think that might be a little easier to read.

@tankyleo
Copy link
Contributor Author

tankyleo commented Dec 1, 2025

Do you think there's a reason someone is going to make transactions with this patter? Or possibly: is this policy going to be loosed anytime soon? I haven't been following these developments closely.

I am not aware of anything changing in the near future. It seems like the current restrictions are ok for the vast majority of use cases.

@tankyleo
Copy link
Contributor Author

tankyleo commented Dec 1, 2025

Addressed feedback, also removed all the newlines from the testdata json files, let me know if you agree

@instagibbs
Copy link

It seems to me like MaraPool was fairly quick to update

AFAIK they don't do any dust checks at all, so they by default would support eph dust

@tankyleo tankyleo requested a review from 0xB10C December 2, 2025 17:01
@0xB10C
Copy link
Owner

0xB10C commented Dec 2, 2025

Looks good to me, feel free to squash. I'll do a final review pass tomorrow.

Good idea with the newline removal from test files. Maybe extract the new line removal from existing test files into it's own commit.

Eclair and LDK have recently reached cross-compatibility on zero-fee
commitment channels, and LDK has just released v0.2 which allows people
to open such channels.

Nonetheless, the commitment transactions of zero-fee commitment channels
will propagate to miners only if their nodes accept ephemeral dust.
Timely confirmation of commitment transactions is crucial to the
security model of Lightning, hence the need to observe which mining
pools mine ephemeral dust.

Here, we make a best-effort attempt at only counting the transactions in
a block spending ephemeral dust that could have been submitted via the
P2P network. We are not interested in coinbase transactions, or
transactions submitted out-of-band. Therefore, we assume that any
transaction spending ephemeral dust is version 3, non-zero-fee, smaller
than 1000vB, and spends from a version 3, zero-fee transaction itself
smaller than 10_000vB. See TRUC policy rules defined in BIP-431 for
further details.

For now, we do not assert that the parent transaction does not have any
ancestors in the same block, or that the child transaction does not have
any descendants in the same block (this would violate current policy on
TRUC packages). This check could be added in the future should
false-positives appear in the data.
@tankyleo
Copy link
Contributor Author

tankyleo commented Dec 3, 2025

Squashed the commits with the following diff

diff --git a/backend/src/stats.rs b/backend/src/stats.rs
index 8fb0bae..488857b 100644
--- a/backend/src/stats.rs
+++ b/backend/src/stats.rs
@@ -385,9 +385,9 @@ impl TxStats {
                     .take(&(txid, *vout))
                     .is_some()
                     && tx.version == 3
-                    // unwrap safety: this is a child transaction of a parent in the same block, hence it must not be a
-                    // coinbase transaction
-                    && tx.fee.unwrap() != Amount::ZERO;
+                    // unwrap safety: we filter for non-coinbase inputs above, hence this transaction is non-coinbase
+                    && tx.fee.unwrap() != Amount::ZERO
+                    && tx.vsize <= 1_000;
 
                 if (tx_spending_ephemeral_dust || ephemeral_dust_outpoints_in_this_block.is_empty())
                     && tx_spending_newly_created_utxos
@@ -405,7 +405,7 @@ impl TxStats {
             // We do not include any cases of ephemeral dust on coinbase transactions (these transactions have their
             // `tx.fee` set to `None`); we are only interested in cases of ephemeral dust that could have been submitted
             // via the p2p network.
-            if tx.fee == Some(Amount::ZERO) && tx.version == 3 {
+            if tx.version == 3 && tx.fee == Some(Amount::ZERO) && tx.vsize <= 10_000 {
                 let staged_ephemeral_dust_outpoints: Vec<_> = tx
                     .output
                     .iter()

Note I now also assert that the parent is <= 10_000vB and the child is <= 1000vB, see BIP431.

@0xB10C
Copy link
Owner

0xB10C commented Dec 3, 2025

LGTM! Thank you! I'll set up a chart for this.

@0xB10C 0xB10C merged commit 0d15a96 into 0xB10C:master Dec 3, 2025
2 of 3 checks passed
@tankyleo
Copy link
Contributor Author

tankyleo commented Dec 3, 2025

LGTM! Thank you! I'll set up a chart for this.

Thanks for the review and thanks for the chart !

@0xB10C
Copy link
Owner

0xB10C commented Jan 14, 2026

LGTM! Thank you! I'll set up a chart for this.

Adding chart in #119

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

Successfully merging this pull request may close these issues.

3 participants