<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>b10c's blog: Bitcoin Developer on b10c's blog</title><link>https://b10c.me/</link><description>Recent content in b10c's blog: Bitcoin Developer on b10c's blog</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><lastBuildDate>Sat, 28 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://b10c.me/feed.xml" rel="self" type="application/rss+xml"/><item><title>localprobe: detect Bitcoin nodes running on the same machine as your (Firefox) browser</title><link>https://b10c.me/projects/025-localprobe/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/025-localprobe/</guid><description>&lt;p&gt;You&amp;rsquo;re running a Bitcoin node on the same machine as your (Firefox) web browser? Yeah, I and everybody else can tell&amp;hellip;&lt;/p&gt;
&lt;p&gt;localprobe is a small JavaScript snippet I built at the &lt;a href="https://btcplusplus.dev/conf/floripa26"&gt;btc++ Floripa 2026 exploits hackathon&lt;/a&gt;, where it won 2nd place. It detects whether you are running a Bitcoin node on the same machine as your Firefox browser and alerts you if so.&lt;/p&gt;
&lt;p&gt;The project has a dedicated page at &lt;a href="https://0xb10c.github.io/localprobe/"&gt;0xb10c.github.io/localprobe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Firefox, unlike Chromium-based browsers (Chrome, Brave, Edge), allows web pages to make cross-origin requests to &lt;code&gt;localhost&lt;/code&gt;. This means any website you visit in Firefox can probe ports on your machine. Chromium-based browsers block this via the &lt;a href="https://wicg.github.io/private-network-access/"&gt;Private Network Access&lt;/a&gt; spec. Firefox is &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1481298"&gt;actively implementing Local Network Access&lt;/a&gt;, with a gradual rollout starting in Firefox 147 for users with Enhanced Tracking Protection set to Strict. Tor Browser is also not affected, though not due to LNA — it blocks access to localhost by default for privacy reasons.&lt;/p&gt;
&lt;p&gt;localprobe probes the default Bitcoin Core RPC and P2P ports for mainnet, testnet3, testnet4, signet, and regtest, as well as Tor control and proxy ports. If any of these ports respond, it shows a privacy warning alerting you that any website you visit in Firefox can detect that you&amp;rsquo;re running a node.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/025-localprobe/header.png'
alt='A privacy alert from localprobe.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A privacy alert from localprobe.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can test it by running &lt;code&gt;bitcoind -regtest&lt;/code&gt; and visiting &lt;a href="https://0xb10c.github.io/localprobe/"&gt;0xb10c.github.io/localprobe&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Recap: A network wide DoS bug in Bitcoin Core</title><link>https://b10c.me/talks/028-btc++exploits/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/028-btc++exploits/</guid><description>&lt;p&gt;I did a recap on the a network wide DoS bug and highlighted a metrics that we are monitoring now to catch similar bugs in the future.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/presentation/d/1-wgJscnbqEaA29lCXUhc2-d0V4oOkfyI4y3ZYRXTjgA/edit?usp=sharing"&gt;Slides (Google Slides)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Monitoring Workshop at Vinteum BDL 2026</title><link>https://b10c.me/talks/027-vinteum-bdl-2026/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/027-vinteum-bdl-2026/</guid><description>&lt;p&gt;I held a four hour long monitoring workshop for the current &lt;a href="https://vinteum.org"&gt;Vinteum&lt;/a&gt; Bitcoin Developer Launchpad cohort. I talked about my experience working on open-source Bitcoin projects over the last few years and I went over my &lt;a href="https://github.com/bitcoin-noc/map-of-bitcoin-monitoring"&gt;Map of Bitcoin Monitoring&lt;/a&gt; I created as an overview for this workshop.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/presentation/d/1g-q5F-TodLgRGNT88oFJrGVbyEaoVCoXyD-rVGBg2bg/edit?usp=sharing"&gt;Slides (Google Slides)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin-noc/map-of-bitcoin-monitoring"&gt;Map of Bitcoin Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>OpenSats Report 8</title><link>https://b10c.me/transparency/2025-opensats-report-8/</link><pubDate>Sat, 31 Jan 2026 00:00:00 +0000</pubDate><guid>https://b10c.me/transparency/2025-opensats-report-8/</guid><description>&lt;p&gt;This is a slighly edited copy of the 8th report I sent to &lt;a href="https://opensats.org"&gt;OpenSats&lt;/a&gt; for my LTS
grant. Note that I may have redacted some information that is not or not yet
meant to be published.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="what-did-you-work-on"&gt;&lt;em&gt;What did you work on?&lt;/em&gt;&lt;/h1&gt;
&lt;p&gt;In November and December 2025, and January of 2026, I primarily focused on building out peer-observer tooling and infrastructure, but also worked on mainnet-observer, posted to the Bitcoin Network Operations Collective, contributed to Bitcoin Core, helped in the rust-bitcoin corepc and bip324 libraries, and more.&lt;/p&gt;
&lt;h2 id="publications--podcasts--talks"&gt;Publications / Podcasts / Talks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Attended the Chaincode Labs Bitcoin Research Week 2025 and led an participated in sessions there.&lt;/li&gt;
&lt;li&gt;Presented on the Chaincode Labs &lt;a href="https://brd.chaincode.com/"&gt;Bitcoin Research Day&lt;/a&gt; 2025 on &lt;a href="https://b10c.me/talks/#monitoring-bitcoins-p2p-network-insights-from-the-peer-observer-project"&gt;&amp;ldquo;Monitoring Bitcoin’s P2P network: Insights from the peer-observer Project&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Spoke with Stephan Livera about &amp;ldquo;Monitoring the Bitcoin Network&amp;rdquo; in SLP707: &lt;a href="https://stephanlivera.com/episode/707/"&gt;https://stephanlivera.com/episode/707/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bnoc"&gt;&lt;a href="https://bnoc.xyz"&gt;BNOC&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the Bitcoin Network Operations Collective discourse forum I posted about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/waves-of-bad-addresses-being-gossiped/61"&gt;Waves of (bad) addresses being gossiped&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58"&gt;Increased b-msghand thread utilization due to many 352 byte runestone inscriptions on 2025-11-17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/forward-compatible-coinbase-locktimes-for-bip-54/75"&gt;Forward-compatible coinbase locktimes for BIP-54&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57"&gt;Bitcoin Core versions run by mining pools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/viabtc-mining-pool-mines-multiple-blocks-at-same-height/48"&gt;ViaBTC mining pool mines multiple blocks at same height&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/antpool-mines-two-blocks-at-height-925051/60"&gt;AntPool mines two blocks at height 925051&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;and commented on multiple other posts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="peer-observer"&gt;peer-observer&lt;/h2&gt;
&lt;h3 id="tooling-peer-observerpeer-observer"&gt;tooling (peer-observer/peer-observer)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Added metrics for connections to and from bitprojects.io: &lt;a href="https://github.com/peer-observer/peer-observer/pull/300"&gt;peer-observer/peer-observer#300&lt;/a&gt;. These showed that there is indeed a problem as Bitcoin Core can make multiple outbound connections to the bitproject.io IPs operated by the same entity. See discussion in &lt;a href="https://bnoc.xyz/t/many-connections-to-bitproject-io-nodes/40/6"&gt;https://bnoc.xyz/t/many-connections-to-bitproject-io-nodes/40/6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add support for &lt;code&gt;inv_to_send&lt;/code&gt; and &lt;code&gt;cpu_load&lt;/code&gt; fields from &lt;code&gt;getpeerinfo&lt;/code&gt; to the peer-observer rpc-extractor in &lt;a href="https://github.com/peer-observer/peer-observer/pull/299"&gt;peer-observer/peer-observer#299&lt;/a&gt;. &lt;code&gt;inv_to_send&lt;/code&gt; metrics allow tracking if a bug from the past is really fixed (see e.g. &lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6)"&gt;https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6)&lt;/a&gt;. &lt;code&gt;cpu_load&lt;/code&gt; is proposed in &lt;a href="https://github.com/bitcoin/bitcoin/pull/31672"&gt;bitcoin/bitcoin#31672&lt;/a&gt; and collecting data on it allows providing feedback on the usefulness of that metric.&lt;/li&gt;
&lt;li&gt;Added support for &lt;code&gt;inv&lt;/code&gt; and &lt;code&gt;feefilter&lt;/code&gt; messages for the p2p-extractor &lt;a href="https://github.com/peer-observer/peer-observer/pull/297"&gt;peer-observer/peer-observer#297&lt;/a&gt;. These allow us to check when and how often &lt;code&gt;feefilter&lt;/code&gt; and &lt;code&gt;inv&lt;/code&gt; messages are send from our node and what contents are included. See e.g. &lt;code&gt;WTx inv rate&lt;/code&gt; chart (and below) in &lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6"&gt;https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed addr service bit metrics to be 64 bit instead of 32 bit in &lt;a href="https://github.com/peer-observer/peer-observer/pull/294"&gt;peer-observer/peer-observer#294&lt;/a&gt; and &lt;a href="https://github.com/peer-observer/peer-observer/pull/286"&gt;peer-observer/peer-observer#286&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed peer-observer extractors and tools not exiting if a &lt;code&gt;RuntimeError&lt;/code&gt; occurred. They were still waiting for a Ctrl+C: &lt;a href="https://github.com/peer-observer/peer-observer/pull/292"&gt;peer-observer/peer-observer#292&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added an event for received &lt;code&gt;addrv2&lt;/code&gt; messages to the p2p-extractor &lt;a href="https://github.com/peer-observer/peer-observer/pull/296"&gt;peer-observer/peer-observer#296&lt;/a&gt;. These are useful for showing metrics for, e.g., the size of the last address announcement the node sent, total number of addresses announced per announcement, rate of the announcements. Metrics were added in &lt;a href="https://github.com/peer-observer/peer-observer/pull/306"&gt;peer-observer/peer-observer#306&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Started a brainstorming issue for a better NATS subject for events from extractors to tools. This should allow using one NATS server for multiple nodes: &lt;a href="https://github.com/peer-observer/peer-observer/issues/313"&gt;peer-observer/peer-observer#313&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Improved peer-observer CI by adding a lint job (&lt;a href="https://github.com/peer-observer/peer-observer/pull/318"&gt;peer-observer/peer-observer#318&lt;/a&gt;, &lt;a href="https://github.com/peer-observer/peer-observer/pull/326"&gt;peer-observer/peer-observer#326&lt;/a&gt;) and splitting up the jobs to run in parallel &lt;a href="https://github.com/peer-observer/peer-observer/pull/325"&gt;peer-observer/peer-observer#325&lt;/a&gt;. Also updated the NixOS version in &lt;a href="https://github.com/peer-observer/peer-observer/pull/322"&gt;peer-observer/peer-observer#322&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Switched to using &lt;code&gt;addconnection&lt;/code&gt; in the peer-observer integration tests, as this 1) made the tests faster (&lt;code&gt;-addnode&lt;/code&gt; is only re-executed every minute) and 2) makes the test more stable on slower test runners (such as GHA). &lt;a href="https://github.com/peer-observer/peer-observer/pull/324"&gt;peer-observer/peer-observer#324&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added Grafana dashboards for some recently added metrics: &lt;a href="https://github.com/peer-observer/peer-observer/pull/317"&gt;peer-observer/peer-observer#317&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added documentation for the required &lt;code&gt;websocket.json&lt;/code&gt; file describing the available websocket endpoints of the websocket tool. &lt;a href="https://github.com/peer-observer/peer-observer/pull/311"&gt;peer-observer/peer-observer#311&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a overview HTML page for the websocket tools &lt;a href="https://github.com/peer-observer/peer-observer/pull/310"&gt;peer-observer/peer-observer#310&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cleaned up the protobuf definitions in &lt;a href="https://github.com/peer-observer/peer-observer/pull/305"&gt;peer-observer/peer-observer#305&lt;/a&gt; which is required for e.g. the archiver tool which requires a stable protobuf API.&lt;/li&gt;
&lt;li&gt;Started a discussion on how to best parse Bitcoin Core debug.logs for a the log-extractor in &lt;a href="https://github.com/peer-observer/peer-observer/issues/336"&gt;peer-observer/peer-observer#336&lt;/a&gt;. Discussing ML methods and literature, but also code based ways of extracting log statements and their arguments.&lt;/li&gt;
&lt;li&gt;I added support for authenticated NATS to peer-observer in &lt;a href="https://github.com/peer-observer/peer-observer/pull/337"&gt;peer-observer/peer-observer#337&lt;/a&gt; and to my NixOS module in &lt;a href="https://github.com/0xB10C/nix/pull/282"&gt;0xB10C/nix#282&lt;/a&gt;. This allows me to open a NATS server to the outside world without authentication for subscribers, but authentication required for publishers. This enables sharing a data stream of NATS events to external contributors, e.g. as part of the Vinteum residency, BOSS programm, B4OS, or other developer education programs.&lt;/li&gt;
&lt;li&gt;Added support for &lt;code&gt;getorphantxs&lt;/code&gt; for the &lt;code&gt;rpc-extractor&lt;/code&gt; in &lt;a href="https://github.com/peer-observer/peer-observer/pull/340"&gt;peer-observer/peer-observer#340&lt;/a&gt; to allow having metrics for the transaction orphanage.&lt;/li&gt;
&lt;li&gt;Added support the &lt;code&gt;getrawaddrman&lt;/code&gt; to the &lt;code&gt;rpc-extractor&lt;/code&gt; in &lt;a href="https://github.com/peer-observer/peer-observer/pull/345"&gt;peer-observer/peer-observer#345&lt;/a&gt;. This allows tracking e.g. addrman changes in metrics.&lt;/li&gt;
&lt;li&gt;Updated the LinkingLion IPs to the new 2026 IPs they seem to use now (&lt;a href="https://bnoc.xyz/t/linkinglion-an-entity-linking-bitcoin-transactions-to-ips/12/11"&gt;https://bnoc.xyz/t/linkinglion-an-entity-linking-bitcoin-transactions-to-ips/12/11&lt;/a&gt;) in &lt;a href="https://github.com/peer-observer/peer-observer/pull/335"&gt;peer-observer/peer-observer#335&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="infrastructure-library-peer-observerinfra-library"&gt;infrastructure library (peer-observer/infra-library)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Upgraded to NixOS 25.11: &lt;a href="https://github.com/peer-observer/infra-library/pull/36"&gt;peer-observer/infra-library#36&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Upgraded the default Bitcoin Core master commit used: &lt;a href="https://github.com/peer-observer/infra-library/pull/32"&gt;peer-observer/infra-library#32&lt;/a&gt; and &lt;a href="https://github.com/peer-observer/infra-library/pull/67"&gt;peer-observer/infra-library#67&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Allow custom Bitcoin Core P2P ports: &lt;a href="https://github.com/peer-observer/infra-library/pull/29"&gt;peer-observer/infra-library#29&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed intermittent integration test failures: &lt;a href="https://github.com/peer-observer/infra-library/pull/26"&gt;peer-observer/infra-library#26&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a CI job that opens an auto-update PR for my nix flake (containing the peer-observer package and module) each day, if there&amp;rsquo;s an update available. &lt;a href="https://github.com/peer-observer/infra-library/pull/23"&gt;peer-observer/infra-library#23&lt;/a&gt; and TODO&lt;/li&gt;
&lt;li&gt;Added support for the new log-extractor in &lt;a href="https://github.com/peer-observer/infra-library/pull/40"&gt;peer-observer/infra-library#40&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/nix/pull/247"&gt;0xB10C/nix#247&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bitcoin-core"&gt;Bitcoin Core&lt;/h2&gt;
&lt;h3 id="bitcoinbitcoin"&gt;bitcoin/bitcoin&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reviewed the tracepoint changes in &lt;a href="https://github.com/bitcoin/bitcoin/pull/33680#issuecomment-3553550433"&gt;bitcoin/bitcoin#33680&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Posted a bit of data for the &lt;code&gt;cpu_load&lt;/code&gt; PR: &lt;a href="https://github.com/bitcoin/bitcoin/pull/31672#issuecomment-3800200054"&gt;bitcoin/bitcoin#31672&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Opened an issue for (too) frequent PCP warnings: &lt;a href="https://github.com/bitcoin/bitcoin/issues/34114"&gt;bitcoin/bitcoin#34114&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed a minor log rate-limiting bug of a log message in &lt;a href="https://github.com/bitcoin/bitcoin/pull/34008"&gt;bitcoin/bitcoin#34008&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a functional test for the IP address self-announcement feature of Bitcoin Core, which previously didn&amp;rsquo;t have any test coverage, while playing an essential part in nodes on the network being able find others: &lt;a href="https://github.com/bitcoin/bitcoin/pull/34039"&gt;bitcoin/bitcoin#34039&lt;/a&gt;. The test had a bug which caused it to intermittently fail, which only appeared on the CI runners. I fixed this in &lt;a href="https://github.com/bitcoin/bitcoin/pull/34204"&gt;bitcoin/bitcoin#34204&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Made sure that IP address self-announcements are sent in a separate message first: &lt;a href="https://github.com/bitcoin/bitcoin/pull/34146"&gt;bitcoin/bitcoin#34146&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-coreguixsigs"&gt;bitcoin-core/guix.sigs&lt;/h3&gt;
&lt;p&gt;Build and uploaded signed GUIX signatures for the following releases and release candidates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;v29.3rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2164"&gt;bitcoin-core/guix.sigs#2164&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v29.3rc2 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2180"&gt;bitcoin-core/guix.sigs#2180&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.1rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2073"&gt;bitcoin-core/guix.sigs#2073&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.1 release &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2104"&gt;bitcoin-core/guix.sigs#2104&lt;/a&gt;, &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2087"&gt;bitcoin-core/guix.sigs#2087&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.2rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2115"&gt;bitcoin-core/guix.sigs#2115&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.2 release &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2149"&gt;bitcoin-core/guix.sigs#2149&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="0xb10cmainnet-observer"&gt;0xb10c/mainnet-observer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;added a chart for signature counts of Schnorr and ECDSA: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/97"&gt;0xB10C/mainnet-observer#97&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;fixed a the extension of the downloaded png files to be &lt;code&gt;.png&lt;/code&gt; and not &lt;code&gt;.png.png&lt;/code&gt;: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/99"&gt;0xB10C/mainnet-observer#99&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;reviewed PR adding support for tracking ephemeral dust: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/103"&gt;0xB10C/mainnet-observer#103&lt;/a&gt; and added charts for it in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/119"&gt;0xB10C/mainnet-observer#119&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added a chart for BIP-54 Consensus Cleanup locktimes set by mining pools &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/112"&gt;0xB10C/mainnet-observer#112&lt;/a&gt;. Since all pools need to be forward-compatible with BIP-54, tracking the share of hashrate being forward-compatible is interesting. Sadly, no pools are forward compatible yet (as of writing). See &lt;a href="https://mainnet.observer/charts/transactions-coinbase-locktime-bip54/"&gt;https://mainnet.observer/charts/transactions-coinbase-locktime-bip54/&lt;/a&gt;. At the same time, it&amp;rsquo;s interesting to track what percentage of hashrate sets a non-BIP-54 locktime on in their coinbase transaction: &lt;a href="https://mainnet.observer/charts/transactions-coinbase-locktime/"&gt;https://mainnet.observer/charts/transactions-coinbase-locktime/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a Cumulative OP_RETURN bytes chart in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/111"&gt;0xB10C/mainnet-observer#111&lt;/a&gt;. See &lt;a href="https://mainnet.observer/charts/outputs-cumulative-opreturn-data-bytes/"&gt;https://mainnet.observer/charts/outputs-cumulative-opreturn-data-bytes/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;stats_version&lt;/code&gt; to improve 1) re-scanning of blocks when we add or change a stat and 2) make sure we have all blocks in the database, by improving the way we determine which blocks to insert: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/113"&gt;0xB10C/mainnet-observer#113&lt;/a&gt;. This uncovered a bug in the database insertion, which I fixed in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/120"&gt;0xB10C/mainnet-observer#120&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Beefed up the CI a bit in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/116"&gt;0xB10C/mainnet-observer#116&lt;/a&gt; and added linting support. As part of this, addressed all linter warnings first &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/115"&gt;0xB10C/mainnet-observer#115&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added a chart and a table for ephemeral dust usage in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/119"&gt;0xB10C/mainnet-observer#119&lt;/a&gt; and for P2A in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/123"&gt;0xB10C/mainnet-observer#123&lt;/a&gt;. See &lt;a href="https://mainnet.observer/charts/mining-pools-mining-p2a/"&gt;https://mainnet.observer/charts/mining-pools-mining-p2a/&lt;/a&gt; and &lt;a href="https://mainnet.observer/charts/mining-pools-mining-ephemeral-dust/"&gt;https://mainnet.observer/charts/mining-pools-mining-ephemeral-dust/&lt;/a&gt;. These were requested by people working on LN implementations using P2A and ephemeral anchors. This was also useful for &lt;a href="https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57/5"&gt;https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57/5&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Reviewed PRs by contributors (thank you!):
&lt;ul&gt;
&lt;li&gt;Coinbase output type and count stats: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/118"&gt;0xB10C/mainnet-observer#118&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fix: add bzip2.dev and clippy to shell.nix: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/108"&gt;0xB10C/mainnet-observer#108&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Total number of inputs and outputs by script type over time: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/107"&gt;0xB10C/mainnet-observer#107&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add OP_RETURN bytes chart: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/106"&gt;0xB10C/mainnet-observer#106&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Update type name order of segwit inputs: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/105"&gt;0xB10C/mainnet-observer#105&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;add: track ephemeral dust &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/103"&gt;0xB10C/mainnet-observer#103&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;misc&lt;/h2&gt;
&lt;h3 id="rust-bitcoinbip324"&gt;rust-bitcoin/bip324&lt;/h3&gt;
&lt;p&gt;I plan to use this library in an upcoming project. Playing around with it to test it, fixing some low-hanging bugs, and getting more familiar with it made sense.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I noticed a bug where reading on a closed bip324 connection (e.g. the other side went offline), didn&amp;rsquo;t produce an error. This was a bug in the implementation and I fixed it in: &lt;a href="https://github.com/rust-bitcoin/bip324/pull/160"&gt;rust-bitcoin/bip324#160&lt;/a&gt;. I also added a integration test for coverage of this bug in the async code.&lt;/li&gt;
&lt;li&gt;I ported that test to the sync code (from async) and added it in &lt;a href="https://github.com/rust-bitcoin/bip324/pull/163"&gt;rust-bitcoin/bip324#163&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I went ahead and picked up an issue to update the bip324 test vectors from the BIP in the bip324 library in &lt;a href="https://github.com/rust-bitcoin/bip324/pull/162"&gt;rust-bitcoin/bip324#162&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nixosnixpkgs"&gt;nixos/nixpkgs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I updated Bitcoin Core from v30.1 to v30.2 shortly after release &lt;a href="https://github.com/NixOS/nixpkgs/pull/478769"&gt;NixOS/nixpkgs#478769&lt;/a&gt;. Bitcoin Core v30.0 and v30.1 had a wallet bug, so getting the update in quickly was a priority.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="0xb10cnix"&gt;0xb10c/nix&lt;/h3&gt;
&lt;p&gt;Next to keeping my packages up-to-date and maintaining existing packages and modules, I also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;added a new package and module for @jamesob&amp;rsquo;s discourse-archive in &lt;a href="https://github.com/0xB10C/nix/pull/283"&gt;0xB10C/nix#283&lt;/a&gt; to be able to automatically backup delvingbitcoin.org and bnoc.xyz discourse instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rust-bitcoincorepc"&gt;rust-bitcoin/corepc&lt;/h3&gt;
&lt;p&gt;I use &lt;code&gt;rust-bitcoin/corepc&lt;/code&gt; in some of my monitoring tools, so it makes sense to spent some of my time adding features &amp;amp; tests, reporting bugs, and implementing fixes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added support for the hidden &lt;code&gt;getorphantxs&lt;/code&gt; RPC to corepc along with integration test &lt;a href="https://github.com/rust-bitcoin/corepc/pull/435"&gt;rust-bitcoin/corepc#435&lt;/a&gt;. This RPC is needed for peer-observer: &lt;a href="https://github.com/peer-observer/peer-observer/pull/340"&gt;peer-observer/peer-observer#340&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added support for Bitcoin Core v30.2 and use it in integration tests. v30.x wasn&amp;rsquo;t covered by integration tests before &lt;a href="https://github.com/rust-bitcoin/corepc/pull/469"&gt;rust-bitcoin/corepc#469&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Reported a bug that the &lt;code&gt;GetMempoolInfo&lt;/code&gt; model FeeRate conversion is broken with a clear reproducer: &lt;a href="https://github.com/rust-bitcoin/corepc/issues/482"&gt;rust-bitcoin/corepc#482&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="0xb10cgithub-metadata-backup"&gt;0xb10c/github-metadata-backup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="%5B0xB10C/github-metadata-backup#11%5D(https://github.com/0xB10C/github-metadata-backup/pull/11)"&gt;#11&lt;/a&gt;: Reviewed and tested a contribution that added support for saving incremental progress on failure.&lt;/li&gt;
&lt;li&gt;&lt;a href="%5B0xB10C/github-metadata-backup#13%5D(https://github.com/0xB10C/github-metadata-backup/pull/13)"&gt;#13&lt;/a&gt;: Reviewed and tested a contribution that mitigated some backup failures when an issue or PR has not-yet-handled events.&lt;/li&gt;
&lt;li&gt;&lt;a href="%5B0xB10C/github-metadata-backup#14%5D(https://github.com/0xB10C/github-metadata-backup/pull/14)"&gt;#14&lt;/a&gt;: Upgraded the octocrab dependency to support not-yet-handled events.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-datablock-arrival-times"&gt;bitcoin-data/block-arrival-times&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Added block arrival timestamps for about 100k blocks from 6 nodes to the block-arrival-times dataset after a request from a researcher: &lt;a href="https://github.com/bitcoin-data/block-arrival-times/pull/25"&gt;bitcoin-data/block-arrival-times#25&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-datastale-blocks"&gt;bitcoin-data/stale-blocks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;changed the &lt;code&gt;get-data.py&lt;/code&gt; script to always fetch the full block, if possible: &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/54"&gt;bitcoin-data/stale-blocks#54&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added stale blocks at height:
&lt;ul&gt;
&lt;li&gt;922047 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/53"&gt;bitcoin-data/stale-blocks#53&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;923067 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/56"&gt;bitcoin-data/stale-blocks#56&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;925051 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/57"&gt;bitcoin-data/stale-blocks#57&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;926056 &amp;amp; 925605 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/58"&gt;bitcoin-data/stale-blocks#58&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;926981 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/59"&gt;bitcoin-data/stale-blocks#59&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;928484 &amp;amp; 928222 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/60"&gt;bitcoin-data/stale-blocks#60&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;929868 &amp;amp; 929872 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/61"&gt;bitcoin-data/stale-blocks#61&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>OpenSats Work-Log 8</title><link>https://b10c.me/funding/2026-opensats-report-8/</link><pubDate>Sat, 31 Jan 2026 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2026-opensats-report-8/</guid><description>&lt;p&gt;This is a copy of the 8th work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="what-did-you-work-on"&gt;&lt;em&gt;What did you work on?&lt;/em&gt;&lt;/h1&gt;
&lt;p&gt;In November and December 2025, and January of 2026, I primarily focused on building out peer-observer tooling and infrastructure, but also worked on mainnet-observer, posted to the Bitcoin Network Operations Collective, contributed to Bitcoin Core, helped in the rust-bitcoin corepc and bip324 libraries, and more.&lt;/p&gt;
&lt;h2 id="publications--podcasts--talks"&gt;Publications / Podcasts / Talks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Attended the Chaincode Labs Bitcoin Research Week 2025 and led an participated in sessions there.&lt;/li&gt;
&lt;li&gt;Presented on the Chaincode Labs &lt;a href="https://brd.chaincode.com/"&gt;Bitcoin Research Day&lt;/a&gt; 2025 on &lt;a href="https://b10c.me/talks/#monitoring-bitcoins-p2p-network-insights-from-the-peer-observer-project"&gt;&amp;ldquo;Monitoring Bitcoin’s P2P network: Insights from the peer-observer Project&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Spoke with Stephan Livera about &amp;ldquo;Monitoring the Bitcoin Network&amp;rdquo; in SLP707: &lt;a href="https://stephanlivera.com/episode/707/"&gt;https://stephanlivera.com/episode/707/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bitcoin-network-operations-collective"&gt;Bitcoin Network Operations Collective&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://bnoc.xyz"&gt;Bitcoin Network Operations Collective&lt;/a&gt; discourse forum I posted about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/waves-of-bad-addresses-being-gossiped/61"&gt;Waves of (bad) addresses being gossiped&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58"&gt;Increased b-msghand thread utilization due to many 352 byte runestone inscriptions on 2025-11-17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/forward-compatible-coinbase-locktimes-for-bip-54/75"&gt;Forward-compatible coinbase locktimes for BIP-54&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57"&gt;Bitcoin Core versions run by mining pools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/viabtc-mining-pool-mines-multiple-blocks-at-same-height/48"&gt;ViaBTC mining pool mines multiple blocks at same height&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bnoc.xyz/t/antpool-mines-two-blocks-at-height-925051/60"&gt;AntPool mines two blocks at height 925051&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;and commented on multiple other posts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="peer-observer"&gt;peer-observer&lt;/h2&gt;
&lt;h3 id="tooling-peer-observerpeer-observer"&gt;tooling (peer-observer/peer-observer)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Added metrics for connections to and from bitprojects.io: &lt;a href="https://github.com/peer-observer/peer-observer/pull/300"&gt;peer-observer/peer-observer#300&lt;/a&gt;. These showed that there is indeed a problem as Bitcoin Core can make multiple outbound connections to the bitproject.io IPs operated by the same entity. See discussion in &lt;a href="https://bnoc.xyz/t/many-connections-to-bitproject-io-nodes/40/6"&gt;https://bnoc.xyz/t/many-connections-to-bitproject-io-nodes/40/6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add support for &lt;code&gt;inv_to_send&lt;/code&gt; and &lt;code&gt;cpu_load&lt;/code&gt; fields from &lt;code&gt;getpeerinfo&lt;/code&gt; to the peer-observer rpc-extractor in &lt;a href="https://github.com/peer-observer/peer-observer/pull/299"&gt;peer-observer/peer-observer#299&lt;/a&gt;. &lt;code&gt;inv_to_send&lt;/code&gt; metrics allow tracking if a bug from the past is really fixed (see e.g. &lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6)"&gt;https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6)&lt;/a&gt;. &lt;code&gt;cpu_load&lt;/code&gt; is proposed in &lt;a href="https://github.com/bitcoin/bitcoin/pull/31672"&gt;bitcoin/bitcoin#31672&lt;/a&gt; and collecting data on it allows providing feedback on the usefulness of that metric.&lt;/li&gt;
&lt;li&gt;Added support for &lt;code&gt;inv&lt;/code&gt; and &lt;code&gt;feefilter&lt;/code&gt; messages for the p2p-extractor &lt;a href="https://github.com/peer-observer/peer-observer/pull/297"&gt;peer-observer/peer-observer#297&lt;/a&gt;. These allow us to check when and how often &lt;code&gt;feefilter&lt;/code&gt; and &lt;code&gt;inv&lt;/code&gt; messages are send from our node and what contents are included. See e.g. &lt;code&gt;WTx inv rate&lt;/code&gt; chart (and below) in &lt;a href="https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6"&gt;https://bnoc.xyz/t/increased-b-msghand-thread-utilization-due-to-many-352-byte-runestone-inscriptions-on-2025-11-17/58/6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed addr service bit metrics to be 64 bit instead of 32 bit in &lt;a href="https://github.com/peer-observer/peer-observer/pull/294"&gt;peer-observer/peer-observer#294&lt;/a&gt; and &lt;a href="https://github.com/peer-observer/peer-observer/pull/286"&gt;peer-observer/peer-observer#286&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed peer-observer extractors and tools not exiting if a &lt;code&gt;RuntimeError&lt;/code&gt; occurred. They were still waiting for a Ctrl+C: &lt;a href="https://github.com/peer-observer/peer-observer/pull/292"&gt;peer-observer/peer-observer#292&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added an event for received &lt;code&gt;addrv2&lt;/code&gt; messages to the p2p-extractor &lt;a href="https://github.com/peer-observer/peer-observer/pull/296"&gt;peer-observer/peer-observer#296&lt;/a&gt;. These are useful for showing metrics for, e.g., the size of the last address announcement the node sent, total number of addresses announced per announcement, rate of the announcements. Metrics were added in &lt;a href="https://github.com/peer-observer/peer-observer/pull/306"&gt;peer-observer/peer-observer#306&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Started a brainstorming issue for a better NATS subject for events from extractors to tools. This should allow using one NATS server for multiple nodes: &lt;a href="https://github.com/peer-observer/peer-observer/issues/313"&gt;peer-observer/peer-observer#313&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Improved peer-observer CI by adding a lint job (&lt;a href="https://github.com/peer-observer/peer-observer/pull/318"&gt;peer-observer/peer-observer#318&lt;/a&gt;, &lt;a href="https://github.com/peer-observer/peer-observer/pull/326"&gt;peer-observer/peer-observer#326&lt;/a&gt;) and splitting up the jobs to run in parallel &lt;a href="https://github.com/peer-observer/peer-observer/pull/325"&gt;peer-observer/peer-observer#325&lt;/a&gt;. Also updated the NixOS version in &lt;a href="https://github.com/peer-observer/peer-observer/pull/322"&gt;peer-observer/peer-observer#322&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Switched to using &lt;code&gt;addconnection&lt;/code&gt; in the peer-observer integration tests, as this 1) made the tests faster (&lt;code&gt;-addnode&lt;/code&gt; is only re-executed every minute) and 2) makes the test more stable on slower test runners (such as GHA). &lt;a href="https://github.com/peer-observer/peer-observer/pull/324"&gt;peer-observer/peer-observer#324&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added Grafana dashboards for some recently added metrics: &lt;a href="https://github.com/peer-observer/peer-observer/pull/317"&gt;peer-observer/peer-observer#317&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added documentation for the required &lt;code&gt;websocket.json&lt;/code&gt; file describing the available websocket endpoints of the websocket tool. &lt;a href="https://github.com/peer-observer/peer-observer/pull/311"&gt;peer-observer/peer-observer#311&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a overview HTML page for the websocket tools &lt;a href="https://github.com/peer-observer/peer-observer/pull/310"&gt;peer-observer/peer-observer#310&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cleaned up the protobuf definitions in &lt;a href="https://github.com/peer-observer/peer-observer/pull/305"&gt;peer-observer/peer-observer#305&lt;/a&gt; which is required for e.g. the archiver tool which requires a stable protobuf API.&lt;/li&gt;
&lt;li&gt;Started a discussion on how to best parse Bitcoin Core debug.logs for a the log-extractor in &lt;a href="https://github.com/peer-observer/peer-observer/issues/336"&gt;peer-observer/peer-observer#336&lt;/a&gt;. Discussing ML methods and literature, but also code based ways of extracting log statements and their arguments.&lt;/li&gt;
&lt;li&gt;I added support for authenticated NATS to peer-observer in &lt;a href="https://github.com/peer-observer/peer-observer/pull/337"&gt;peer-observer/peer-observer#337&lt;/a&gt; and to my NixOS module in &lt;a href="https://github.com/0xB10C/nix/pull/282"&gt;0xB10C/nix#282&lt;/a&gt;. This allows me to open a NATS server to the outside world without authentication for subscribers, but authentication required for publishers. This enables sharing a data stream of NATS events to external contributors, e.g. as part of the Vinteum residency, BOSS programm, B4OS, or other developer education programs.&lt;/li&gt;
&lt;li&gt;Added support for &lt;code&gt;getorphantxs&lt;/code&gt; for the &lt;code&gt;rpc-extractor&lt;/code&gt; in &lt;a href="https://github.com/peer-observer/peer-observer/pull/340"&gt;peer-observer/peer-observer#340&lt;/a&gt; to allow having metrics for the transaction orphanage.&lt;/li&gt;
&lt;li&gt;Added support the &lt;code&gt;getrawaddrman&lt;/code&gt; to the &lt;code&gt;rpc-extractor&lt;/code&gt; in &lt;a href="https://github.com/peer-observer/peer-observer/pull/345"&gt;peer-observer/peer-observer#345&lt;/a&gt;. This allows tracking e.g. addrman changes in metrics.&lt;/li&gt;
&lt;li&gt;Updated the LinkingLion IPs to the new 2026 IPs they seem to use now (&lt;a href="https://bnoc.xyz/t/linkinglion-an-entity-linking-bitcoin-transactions-to-ips/12/11"&gt;https://bnoc.xyz/t/linkinglion-an-entity-linking-bitcoin-transactions-to-ips/12/11&lt;/a&gt;) in &lt;a href="https://github.com/peer-observer/peer-observer/pull/335"&gt;peer-observer/peer-observer#335&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="infrastructure-library-peer-observerinfra-library"&gt;infrastructure library (peer-observer/infra-library)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Upgraded to NixOS 25.11: &lt;a href="https://github.com/peer-observer/infra-library/pull/36"&gt;peer-observer/infra-library#36&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Upgraded the default Bitcoin Core master commit used: &lt;a href="https://github.com/peer-observer/infra-library/pull/32"&gt;peer-observer/infra-library#32&lt;/a&gt; and &lt;a href="https://github.com/peer-observer/infra-library/pull/67"&gt;peer-observer/infra-library#67&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Allow custom Bitcoin Core P2P ports: &lt;a href="https://github.com/peer-observer/infra-library/pull/29"&gt;peer-observer/infra-library#29&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed intermittent integration test failures: &lt;a href="https://github.com/peer-observer/infra-library/pull/26"&gt;peer-observer/infra-library#26&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a CI job that opens an auto-update PR for my nix flake (containing the peer-observer package and module) each day, if there&amp;rsquo;s an update available. &lt;a href="https://github.com/peer-observer/infra-library/pull/23"&gt;peer-observer/infra-library#23&lt;/a&gt; and TODO&lt;/li&gt;
&lt;li&gt;Added support for the new log-extractor in &lt;a href="https://github.com/peer-observer/infra-library/pull/40"&gt;peer-observer/infra-library#40&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/nix/pull/247"&gt;0xB10C/nix#247&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bitcoin-core"&gt;Bitcoin Core&lt;/h2&gt;
&lt;h3 id="bitcoinbitcoin"&gt;bitcoin/bitcoin&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reviewed the tracepoint changes in &lt;a href="https://github.com/bitcoin/bitcoin/pull/33680#issuecomment-3553550433"&gt;bitcoin/bitcoin#33680&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Posted a bit of data for the &lt;code&gt;cpu_load&lt;/code&gt; PR: &lt;a href="https://github.com/bitcoin/bitcoin/pull/31672#issuecomment-3800200054"&gt;bitcoin/bitcoin#31672&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Opened an issue for (too) frequent PCP warnings: &lt;a href="https://github.com/bitcoin/bitcoin/issues/34114"&gt;bitcoin/bitcoin#34114&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed a minor log rate-limiting bug of a log message in &lt;a href="https://github.com/bitcoin/bitcoin/pull/34008"&gt;bitcoin/bitcoin#34008&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a functional test for the IP address self-announcement feature of Bitcoin Core, which previously didn&amp;rsquo;t have any test coverage, while playing an essential part in nodes on the network being able find others: &lt;a href="https://github.com/bitcoin/bitcoin/pull/34039"&gt;bitcoin/bitcoin#34039&lt;/a&gt;. The test had a bug which caused it to intermittently fail, which only appeared on the CI runners. I fixed this in &lt;a href="https://github.com/bitcoin/bitcoin/pull/34204"&gt;bitcoin/bitcoin#34204&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Made sure that IP address self-announcements are sent in a separate message first: &lt;a href="https://github.com/bitcoin/bitcoin/pull/34146"&gt;bitcoin/bitcoin#34146&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-coreguixsigs"&gt;bitcoin-core/guix.sigs&lt;/h3&gt;
&lt;p&gt;Build and uploaded signed GUIX signatures for the following releases and release candidates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;v29.3rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2164"&gt;bitcoin-core/guix.sigs#2164&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v29.3rc2 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2180"&gt;bitcoin-core/guix.sigs#2180&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.1rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2073"&gt;bitcoin-core/guix.sigs#2073&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.1 release &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2104"&gt;bitcoin-core/guix.sigs#2104&lt;/a&gt;, &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2087"&gt;bitcoin-core/guix.sigs#2087&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.2rc1 &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2115"&gt;bitcoin-core/guix.sigs#2115&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v30.2 release &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/2149"&gt;bitcoin-core/guix.sigs#2149&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="0xb10cmainnet-observer"&gt;0xb10c/mainnet-observer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;added a chart for signature counts of Schnorr and ECDSA: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/97"&gt;0xB10C/mainnet-observer#97&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;fixed a the extension of the downloaded png files to be &lt;code&gt;.png&lt;/code&gt; and not &lt;code&gt;.png.png&lt;/code&gt;: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/99"&gt;0xB10C/mainnet-observer#99&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;reviewed PR adding support for tracking ephemeral dust: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/103"&gt;0xB10C/mainnet-observer#103&lt;/a&gt; and added charts for it in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/119"&gt;0xB10C/mainnet-observer#119&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added a chart for BIP-54 Consensus Cleanup locktimes set by mining pools &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/112"&gt;0xB10C/mainnet-observer#112&lt;/a&gt;. Since all pools need to be forward-compatible with BIP-54, tracking the share of hashrate being forward-compatible is interesting. Sadly, no pools are forward compatible yet (as of writing). See &lt;a href="https://mainnet.observer/charts/transactions-coinbase-locktime-bip54/"&gt;https://mainnet.observer/charts/transactions-coinbase-locktime-bip54/&lt;/a&gt;. At the same time, it&amp;rsquo;s interesting to track what percentage of hashrate sets a non-BIP-54 locktime on in their coinbase transaction: &lt;a href="https://mainnet.observer/charts/transactions-coinbase-locktime/"&gt;https://mainnet.observer/charts/transactions-coinbase-locktime/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a Cumulative OP_RETURN bytes chart in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/111"&gt;0xB10C/mainnet-observer#111&lt;/a&gt;. See &lt;a href="https://mainnet.observer/charts/outputs-cumulative-opreturn-data-bytes/"&gt;https://mainnet.observer/charts/outputs-cumulative-opreturn-data-bytes/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;stats_version&lt;/code&gt; to improve 1) re-scanning of blocks when we add or change a stat and 2) make sure we have all blocks in the database, by improving the way we determine which blocks to insert: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/113"&gt;0xB10C/mainnet-observer#113&lt;/a&gt;. This uncovered a bug in the database insertion, which I fixed in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/120"&gt;0xB10C/mainnet-observer#120&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Beefed up the CI a bit in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/116"&gt;0xB10C/mainnet-observer#116&lt;/a&gt; and added linting support. As part of this, addressed all linter warnings first &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/115"&gt;0xB10C/mainnet-observer#115&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added a chart and a table for ephemeral dust usage in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/119"&gt;0xB10C/mainnet-observer#119&lt;/a&gt; and for P2A in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/123"&gt;0xB10C/mainnet-observer#123&lt;/a&gt;. See &lt;a href="https://mainnet.observer/charts/mining-pools-mining-p2a/"&gt;https://mainnet.observer/charts/mining-pools-mining-p2a/&lt;/a&gt; and &lt;a href="https://mainnet.observer/charts/mining-pools-mining-ephemeral-dust/"&gt;https://mainnet.observer/charts/mining-pools-mining-ephemeral-dust/&lt;/a&gt;. These were requested by people working on LN implementations using P2A and ephemeral anchors. This was also useful for &lt;a href="https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57/5"&gt;https://bnoc.xyz/t/bitcoin-core-versions-run-by-mining-pools/57/5&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Reviewed PRs by contributors (thank you!):
&lt;ul&gt;
&lt;li&gt;Coinbase output type and count stats: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/118"&gt;0xB10C/mainnet-observer#118&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fix: add bzip2.dev and clippy to shell.nix: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/108"&gt;0xB10C/mainnet-observer#108&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Total number of inputs and outputs by script type over time: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/107"&gt;0xB10C/mainnet-observer#107&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add OP_RETURN bytes chart: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/106"&gt;0xB10C/mainnet-observer#106&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Update type name order of segwit inputs: &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/105"&gt;0xB10C/mainnet-observer#105&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;add: track ephemeral dust &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/103"&gt;0xB10C/mainnet-observer#103&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;misc&lt;/h2&gt;
&lt;h3 id="rust-bitcoinbip324"&gt;rust-bitcoin/bip324&lt;/h3&gt;
&lt;p&gt;I plan to use this library in an upcoming project. Playing around with it to test it, fixing some low-hanging bugs, and getting more familiar with it made sense.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I noticed a bug where reading on a closed bip324 connection (e.g. the other side went offline), didn&amp;rsquo;t produce an error. This was a bug in the implementation and I fixed it in: &lt;a href="https://github.com/rust-bitcoin/bip324/pull/160"&gt;rust-bitcoin/bip324#160&lt;/a&gt;. I also added a integration test for coverage of this bug in the async code.&lt;/li&gt;
&lt;li&gt;I ported that test to the sync code (from async) and added it in &lt;a href="https://github.com/rust-bitcoin/bip324/pull/163"&gt;rust-bitcoin/bip324#163&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I went ahead and picked up an issue to update the bip324 test vectors from the BIP in the bip324 library in &lt;a href="https://github.com/rust-bitcoin/bip324/pull/162"&gt;rust-bitcoin/bip324#162&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nixosnixpkgs"&gt;nixos/nixpkgs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I updated Bitcoin Core from v30.1 to v30.2 shortly after release &lt;a href="https://github.com/NixOS/nixpkgs/pull/478769"&gt;NixOS/nixpkgs#478769&lt;/a&gt;. Bitcoin Core v30.0 and v30.1 had a wallet bug, so getting the update in quickly was a priority.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="0xb10cnix"&gt;0xb10c/nix&lt;/h3&gt;
&lt;p&gt;Next to keeping my packages up-to-date and maintaining existing packages and modules, I also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;added a new package and module for @jamesob&amp;rsquo;s discourse-archive in &lt;a href="https://github.com/0xB10C/nix/pull/283"&gt;0xB10C/nix#283&lt;/a&gt; to be able to automatically backup delvingbitcoin.org and bnoc.xyz discourse instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rust-bitcoincorepc"&gt;rust-bitcoin/corepc&lt;/h3&gt;
&lt;p&gt;I use &lt;code&gt;rust-bitcoin/corepc&lt;/code&gt; in some of my monitoring tools, so it makes sense to spent some of my time adding features &amp;amp; tests, reporting bugs, and implementing fixes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added support for the hidden &lt;code&gt;getorphantxs&lt;/code&gt; RPC to corepc along with integration test &lt;a href="https://github.com/rust-bitcoin/corepc/pull/435"&gt;rust-bitcoin/corepc#435&lt;/a&gt;. This RPC is needed for peer-observer: &lt;a href="https://github.com/peer-observer/peer-observer/pull/340"&gt;peer-observer/peer-observer#340&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added support for Bitcoin Core v30.2 and use it in integration tests. v30.x wasn&amp;rsquo;t covered by integration tests before &lt;a href="https://github.com/rust-bitcoin/corepc/pull/469"&gt;rust-bitcoin/corepc#469&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Reported a bug that the &lt;code&gt;GetMempoolInfo&lt;/code&gt; model FeeRate conversion is broken with a clear reproducer: &lt;a href="https://github.com/rust-bitcoin/corepc/issues/482"&gt;rust-bitcoin/corepc#482&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="0xb10cgithub-metadata-backup"&gt;0xb10c/github-metadata-backup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/github-metadata-backup/pull/11"&gt;0xB10C/github-metadata-backup#11&lt;/a&gt;: Reviewed and tested a contribution that added support for saving incremental progress on failure.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/github-metadata-backup/pull/13"&gt;0xB10C/github-metadata-backup#13&lt;/a&gt;: Reviewed and tested a contribution that mitigated some backup failures when an issue or PR has not-yet-handled events.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/github-metadata-backup/pull/14"&gt;0xB10C/github-metadata-backup#14&lt;/a&gt;: Upgraded the octocrab dependency to support not-yet-handled events.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-datablock-arrival-times"&gt;bitcoin-data/block-arrival-times&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Added block arrival timestamps for about 100k blocks from 6 nodes to the block-arrival-times dataset after a request from a researcher: &lt;a href="https://github.com/bitcoin-data/block-arrival-times/pull/25"&gt;bitcoin-data/block-arrival-times#25&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-datastale-blocks"&gt;bitcoin-data/stale-blocks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;changed the &lt;code&gt;get-data.py&lt;/code&gt; script to always fetch the full block, if possible: &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/54"&gt;bitcoin-data/stale-blocks#54&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added stale blocks at height:
&lt;ul&gt;
&lt;li&gt;922047 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/53"&gt;bitcoin-data/stale-blocks#53&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;923067 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/56"&gt;bitcoin-data/stale-blocks#56&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;925051 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/57"&gt;bitcoin-data/stale-blocks#57&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;926056 &amp;amp; 925605 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/58"&gt;bitcoin-data/stale-blocks#58&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;926981 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/59"&gt;bitcoin-data/stale-blocks#59&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;928484 &amp;amp; 928222 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/60"&gt;bitcoin-data/stale-blocks#60&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;929868 &amp;amp; 929872 &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/61"&gt;bitcoin-data/stale-blocks#61&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Bitcoin Network Monitoring with b10c: SLP707</title><link>https://b10c.me/talks/026-stephanlivera-slp-707/</link><pubDate>Fri, 19 Dec 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/026-stephanlivera-slp-707/</guid><description>&lt;p&gt;In this episode, Stephan Livera and I discusses my work in the Bitcoin ecosystem,
focusing on the importance of censorship resistance, the role of mining pools,
and the implications of OFAC sanctions on Bitcoin transactions. I introduce my
&lt;a href="public.peer.observer"&gt;peer-observer&lt;/a&gt; project aimed at monitoring the Bitcoin P2P
network for anomalies and attacks, and highlight the need for a collaborative
approach to Bitcoin network operations through the Bitcoin Network Operations
Collective.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stephanlivera.com/episode/707/"&gt;stephanlivera.com/episode/707&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Monitoring Bitcoin’s P2P network: Insights from the peer-observer Project</title><link>https://b10c.me/talks/025-monitoring-p2p-chaincode-brd25/</link><pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/025-monitoring-p2p-chaincode-brd25/</guid><description>&lt;p&gt;I presented about &amp;ldquo;Monitoring Bitcoin’s P2P network: Insights from the peer-observer Project&amp;rdquo; at the Chaincode Labs &lt;a href="https://brd.chaincode.com/"&gt;Bitcoin Research Day 2025&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.google.com/presentation/d/1IdJMN9QoUaZYcUNrUfD8uEUbixQxFvKjLiJeZcSo91k"&gt;Slides (Google Docs)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>OpenSats Work-Log 7</title><link>https://b10c.me/funding/2025-opensats-report-7/</link><pubDate>Fri, 31 Oct 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2025-opensats-report-7/</guid><description>&lt;p&gt;This is a copy of the 7th work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications--talks"&gt;Publications &amp;amp; Talks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Continued my &amp;ldquo;Stats on compact block reconstructions&amp;rdquo;: thread by posting an update and some of the current work: &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24"&gt;https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Created a topic for peer-observer on delving for people who want to stay up-to-date with the project: &lt;a href="https://delvingbitcoin.org/t/peer-observer-a-tool-and-infrastructure-for-monitoring-the-bitcoin-p2p-network-for-attacks-and-anomalies/1988"&gt;https://delvingbitcoin.org/t/peer-observer-a-tool-and-infrastructure-for-monitoring-the-bitcoin-p2p-network-for-attacks-and-anomalies/1988&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;At CoreDev, did a Spark session on &amp;ldquo;When do Bitcoin node operators upgrade?&amp;rdquo;. Plan to publish a blog post on this at some point.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-core"&gt;Bitcoin Core&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;planned and attended CoreDev meeting: &lt;a href="https://coredev.tech/2025_frankfurt.html"&gt;https://coredev.tech/2025_frankfurt.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;opened PR &amp;ldquo;p2p: reduce false-positives in addr rate-limiting&amp;rdquo; (&lt;a href="https://github.com/bitcoin/bitcoin/pull/33699"&gt;https://github.com/bitcoin/bitcoin/pull/33699&lt;/a&gt;) based on the observation documented here &lt;a href="https://bnoc.xyz/t/bitcoin-core-addr-gossip-is-rate-limited-right-after-connection-open/47"&gt;https://bnoc.xyz/t/bitcoin-core-addr-gossip-is-rate-limited-right-after-connection-open/47&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;opened PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/33133"&gt;https://github.com/bitcoin/bitcoin/pull/33133&lt;/a&gt; to fix the RPC docs of getpeerinfo ping field&lt;/li&gt;
&lt;li&gt;opened issue &amp;ldquo;signet: disk-space-DoS due to low mining difficulty&amp;rdquo; (&lt;a href="https://github.com/bitcoin/bitcoin/issues/33266"&gt;https://github.com/bitcoin/bitcoin/issues/33266&lt;/a&gt;) after previously disclosing the signet vulnerability and it being fixed&lt;/li&gt;
&lt;li&gt;Bitcoin Core GUIX sigs for:
&lt;ul&gt;
&lt;li&gt;v30 (&lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1984"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1984&lt;/a&gt;, &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1953"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1953&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;v30rc3 (&lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1940"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1940&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;v28.3rc1 (&lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1923"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1923&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="peer-observer"&gt;peer-observer&lt;/h3&gt;
&lt;p&gt;A tool and infrastrucuture to monitor for attacks and anomalies in the Bitcoin network with passive monitoring.&lt;/p&gt;
&lt;p&gt;I worked on the tooling, the infrastructure, and a demo instance over the past few months.&lt;/p&gt;
&lt;h4 id="tool"&gt;Tool&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/0xB10C/peer-observer"&gt;https://github.com/0xB10C/peer-observer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since August, there have been 180+ commits in that repo across 57 PRs by 4 authors.
I&amp;rsquo;m only listing the most interesting ones below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/191"&gt;https://github.com/0xB10C/peer-observer/pull/191&lt;/a&gt;: added a new extractor that fetches from the getpeerinfo RPC&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/256"&gt;https://github.com/0xB10C/peer-observer/pull/256&lt;/a&gt;: added a new extractor that measurements against the nodes P2P interface (e.g. bitcoin-ping duration)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/272"&gt;https://github.com/0xB10C/peer-observer/pull/272&lt;/a&gt;: added a new extractor that parses the debug.log file and emits events (contributed by @m4ycon; I only reviewed it)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/213"&gt;https://github.com/0xB10C/peer-observer/pull/213&lt;/a&gt;: added a bunch of Grafana dashboards I stored elsewhere to the repo&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/215"&gt;https://github.com/0xB10C/peer-observer/pull/215&lt;/a&gt;: added a metric for LinkingLion traffic based on a discussion with gmax&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/241"&gt;https://github.com/0xB10C/peer-observer/pull/241&lt;/a&gt;: reduced the resource usage (CPU, bandwidth, RAM) of metrics tool by removing high-cardinallity metrics&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/249"&gt;https://github.com/0xB10C/peer-observer/pull/249&lt;/a&gt;: metric for peers relaying sub-1sat/vbyte txns to track adoption&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/269"&gt;https://github.com/0xB10C/peer-observer/pull/269&lt;/a&gt;: added new events for RPC-extractor based on getmempoolinfo events&lt;/li&gt;
&lt;li&gt;As part of &lt;a href="https://github.com/0xB10C/peer-observer/issues/26"&gt;https://github.com/0xB10C/peer-observer/issues/26&lt;/a&gt; (Integration test the tools and extractors):
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/222"&gt;https://github.com/0xB10C/peer-observer/pull/222&lt;/a&gt;: integration tests for the metrics tool&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/229"&gt;https://github.com/0xB10C/peer-observer/pull/229&lt;/a&gt;: integration tests for the websocket tool&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/268"&gt;https://github.com/0xB10C/peer-observer/pull/268&lt;/a&gt;: integration tests for the logger tool&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/231"&gt;https://github.com/0xB10C/peer-observer/pull/231&lt;/a&gt;: integration tests for the rpc-extractor&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="infrastructure"&gt;Infrastructure&lt;/h4&gt;
&lt;p&gt;Next to the tooling component of peer-observer, there is also a infrastructure component
for running nodes, connecting the extractors to the nodes, collecting system metrics, and
more. Since this is infrastructure is defined as code for NixOS systems, it&amp;rsquo;s possible to
track in git and share. Previously, the infrastructure and individual honey pot host
definitions were mixed and sharing them would have revealed too much information about the
honey pots. However, I spent a bit of time extracting an opinionated library for defining
peer-observer hosts. This has multiple benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the host definitions can be tested in CI with full integration tests&lt;/li&gt;
&lt;li&gt;the library can be published as FOSS and used by others too, if they want&lt;/li&gt;
&lt;li&gt;the &amp;ldquo;production&amp;rdquo; implementation (i.e. &amp;ldquo;honey pot host definitions&amp;rdquo;) can remain private&lt;/li&gt;
&lt;li&gt;the library can be used to set up a fully public demo instance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The library can be found in &lt;a href="https://github.com/0xB10C/peer-observer-infra-library"&gt;https://github.com/0xB10C/peer-observer-infra-library&lt;/a&gt; but as of
writing, is still missing a bunch of documentation.&lt;/p&gt;
&lt;p&gt;A fully public demo instance (sponsored by Localhost Research) is available on
&lt;a href="https://demo.peer.observer"&gt;https://demo.peer.observer&lt;/a&gt; and the infrastructure definitions (using the above library)
can be found in &lt;a href="https://github.com/0xB10C/peer-observer-infra-demo"&gt;https://github.com/0xB10C/peer-observer-infra-demo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="bitcoin-network-operations-collective-bnocxyz"&gt;Bitcoin Network Operations Collective (bnoc.xyz)&lt;/h3&gt;
&lt;p&gt;I hinted at something similar in &lt;a href="https://b10c.me/projects/024-peer-observer/#a-bitcoin-network-operations-collective"&gt;https://b10c.me/projects/024-peer-observer/#a-bitcoin-network-operations-collective&lt;/a&gt;.
The &lt;a href="https://bnoc.xyz"&gt;https://bnoc.xyz&lt;/a&gt; is a first step in bringing such a collective forward. While I haven&amp;rsquo;t
announced it publicly yet, the forum is meant for people to post their raw network observations
and allow for discussions on network events.&lt;/p&gt;
&lt;p&gt;So far, I&amp;rsquo;ve been mainly been posting old notes there and a few others have started to post their
observations too. I plan to annouce it in the next few months.&lt;/p&gt;
&lt;h3 id="misc"&gt;Misc&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;updates the NixOS bitcoind package:
&lt;ul&gt;
&lt;li&gt;v29.0 -&amp;gt; v29.1: &lt;a href="https://github.com/NixOS/nixpkgs/pull/440372"&gt;https://github.com/NixOS/nixpkgs/pull/440372&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v29.1 -&amp;gt; v30.0: &lt;a href="https://github.com/NixOS/nixpkgs/pull/451606"&gt;https://github.com/NixOS/nixpkgs/pull/451606&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jamesob/delving-bitcoin-archive/pull/1"&gt;https://github.com/jamesob/delving-bitcoin-archive/pull/1&lt;/a&gt;: looked into delving archive&lt;/li&gt;
&lt;li&gt;Kept bitcoin-data/stale-blocks up-to-date with recent stale blocks:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/52"&gt;https://github.com/bitcoin-data/stale-blocks/pull/52&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/50"&gt;https://github.com/bitcoin-data/stale-blocks/pull/50&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/49"&gt;https://github.com/bitcoin-data/stale-blocks/pull/49&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/42"&gt;https://github.com/bitcoin-data/stale-blocks/pull/42&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Organized and held Socratic Seminar #4 for BTCFFM: &lt;a href="https://github.com/btcffm/website-btcffm/pull/28"&gt;https://github.com/btcffm/website-btcffm/pull/28&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-do-you-plan-to-work-on-next-quarter"&gt;&lt;em&gt;What do you plan to work on next quarter?&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;peer-observer-infra-library:
&lt;ul&gt;
&lt;li&gt;work on set up documentation and better README&lt;/li&gt;
&lt;li&gt;plenty of other open issues&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;peer-observer tooling:
&lt;ul&gt;
&lt;li&gt;continue working on open issues. The main ones are:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/issues/263"&gt;https://github.com/0xB10C/peer-observer/issues/263&lt;/a&gt;: more P2P-extractor events&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/issues/199"&gt;https://github.com/0xB10C/peer-observer/issues/199&lt;/a&gt;: more RPC-extractor events&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/issues/185"&gt;https://github.com/0xB10C/peer-observer/issues/185&lt;/a&gt;: alert on predefined heuristics&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/issues/121"&gt;https://github.com/0xB10C/peer-observer/issues/121&lt;/a&gt;: figure out how to archive events&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/issues/13"&gt;https://github.com/0xB10C/peer-observer/issues/13&lt;/a&gt;: finally get to anomaly detection?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;bnoc.xyz
&lt;ul&gt;
&lt;li&gt;annouce it publicly and continue filling it with content&lt;/li&gt;
&lt;li&gt;set up a backup and a simple HTML mirroring for bnoc&lt;/li&gt;
&lt;li&gt;while at it, set up a backup for delvingbitcoin too and mirror posts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Based on recent discussions, we noticed that we don&amp;rsquo;t have good data on block propagation. Possibly add some monitoring there.&lt;/li&gt;
&lt;li&gt;blog posts:
&lt;ul&gt;
&lt;li&gt;I have data on Bitcoin nodes upgrading, and plan to publish a blog post or similar on this at some point.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m expecting to get data on historical block propagation soon. This allows to look into how e.g. non-standard transaction really affect block proagation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The KIT DSN has been running P2P network monitoring and block propagation for close to 10 years now, however they might shutdown some of their services next year. It would be good to set up some block propatagion monitoring for our self.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Support from LocalhostResearch for peer-observer</title><link>https://b10c.me/funding/2025-localhostresearch-support/</link><pubDate>Wed, 01 Oct 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2025-localhostresearch-support/</guid><description>&lt;p&gt;&lt;a href="https://lclhost.org/"&gt;Localhost Research&lt;/a&gt; supported my &lt;a href="https://b10c.me/projects/024-peer-observer/"&gt;peer-observer&lt;/a&gt; project by sponsoring three servers for a demo instance, which can be found on &lt;a href="https://demo.peer.observer"&gt;demo.peer.observer&lt;/a&gt;. Compared to the
&lt;a href="https://public.peer.observer"&gt;public.peer.observer&lt;/a&gt; instance, this allows everyone to explore the metrics and data extracted from two Bitcoin Core nodes, while no information about the &amp;ldquo;production&amp;rdquo; honey pot nodes is leaked.&lt;/p&gt;
&lt;p&gt;The full NixOS infrastructure is published on &lt;a href="https://github.com/peer-observer/infra-demo"&gt;https://github.com/peer-observer/infra-demo&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>OpenSats Work-Log 6</title><link>https://b10c.me/funding/2025-opensats-report-6/</link><pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2025-opensats-report-6/</guid><description>&lt;p&gt;This is a copy of the 6th work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;p&gt;In May, June, and July of 2025 I finished mainnet-observer up and launched it, gave a talk on my peer-observer project at the Prague dev/hack/day 2025 and published a blog post about it, and more. I also took some time off to touch some grass, recharge, and enjoy summer a bit.&lt;/p&gt;
&lt;h2 id="publications"&gt;Publications:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve written down my notes on the discovery of the Bitcoin Core DoS: &amp;ldquo;DoS due to inv-to-send sets growing too large&amp;rdquo; from May 2023. I think that having these documented somewhere is helpful and allows me to link to it. &lt;a href="https://b10c.me/observations/15-inv-to-send-queue/"&gt;https://b10c.me/observations/15-inv-to-send-queue/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;At the Prague dev/hack/day 2025 I gave a talk about my peer-observer project with the goal of monitoring the Bitcoin P2P network for attacks and anomalies. &lt;a href="https://b10c.me/talks/#peer-observer-a-tool-and-infrastructure-for-monitoring-the-bitcoin-p2p-network-for-attacks-and-anomalies"&gt;https://b10c.me/talks/#peer-observer-a-tool-and-infrastructure-for-monitoring-the-bitcoin-p2p-network-for-attacks-and-anomalies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;As the talk wasn&amp;rsquo;t recorded, I also published a blog post on peer-observer to reach a wider audience and to have the project idea written down somewhere. &lt;a href="https://b10c.me/projects/024-peer-observer/"&gt;https://b10c.me/projects/024-peer-observer/&lt;/a&gt;. I also have a section on a potential Bitcoin Network Operations Collective, which is something I want to further explore in the coming months.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mainnet-observer"&gt;mainnet-observer&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/0xB10C/mainnet-observer"&gt;mainnet-observer&lt;/a&gt; project is an Open-Source rewrite of my 2017 transactionfee.info project (semi-closed source). It shows blockchain statistics interesting for developers and power users and is useful to make data-based decisions for protocol development. Over the course of April and in early May, I finalized an initial version ready for publishing.&lt;/p&gt;
&lt;p&gt;In May, this included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/mainnet-observer/pull/62"&gt;a new chart for UTXO set composition over time&lt;/a&gt; which partly addressed Murch&amp;rsquo;s issue &lt;a href="https://github.com/0xB10C/mainnet-observer/issues/14"&gt;#14 New chart request: UTXO set composition over time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A pie chart for &lt;a href="https://mainnet.observer/charts/mining-pools-hashrate-distribution/"&gt;pool hashrate distribution&lt;/a&gt; including AntPool &amp;amp; friends in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/76"&gt;#76&lt;/a&gt; to have an updating version of the chart published at the bottom of &lt;a href="https://b10c.me/blog/015-bitcoin-mining-centralization/"&gt;Bitcoin Mining Centralization in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://mainnet.observer/charts/block-weight/"&gt;block-weight chart&lt;/a&gt; in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/80"&gt;#80&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Charts for some OP_RETURN meta-protocol usage in &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/79"&gt;#79&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;some minor and last-minute visual fixes like &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/71"&gt;#71&lt;/a&gt;, &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/73"&gt;#73&lt;/a&gt;, &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/75"&gt;#75&lt;/a&gt;, &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/81"&gt;#81&lt;/a&gt;, &lt;a href="https://github.com/0xB10C/mainnet-observer/pull/82"&gt;#82&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="peer-observer"&gt;peer-observer&lt;/h2&gt;
&lt;p&gt;Next to speaking about peer-observer and announcing the project in my blog post (see publications at the top), I also worked on a few things that had been on my list for a while.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/173"&gt;#173&lt;/a&gt; adds documentation and usage instructions on how to use the peer-observer tools - this helps with new contributors (coming from the talk and blog post) trying the tools&lt;/li&gt;
&lt;li&gt;Initially, peer-observer was only using the eBPF/USDT interface of Bitcoin Core. The RPC interface can provide useful information too, especially stateful information. To use this data, I implemented an RPC-extractor with initially data from &lt;code&gt;getpeerinfo&lt;/code&gt; in &lt;a href="https://github.com/0xB10C/peer-observer/pull/191"&gt;#191&lt;/a&gt;. More RPCs to implement are tracked in &lt;a href="https://github.com/0xB10C/peer-observer/issues/199"&gt;#199&lt;/a&gt;. This also caused of followup issues with other ideas to implement linked from the PR. As part of the RPC-exporter I also fixed the &lt;code&gt;getpeerinfo&lt;/code&gt; RPC implementation of rust-bitcoin/corepc &lt;a href="https://github.com/rust-bitcoin/corepc/pull/310"&gt;https://github.com/rust-bitcoin/corepc/pull/310&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Some general maintenance in &lt;a href="https://github.com/0xB10C/peer-observer/pull/171"&gt;#171&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/peer-observer/pull/172"&gt;#172&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Clube Bitcoin Universidade de Brasília has been starting to setup their own peer-observer infrastructure with my help. So I made some small contributions on their infra structure: &lt;a href="https://github.com/ClubeBitcoinUnB/peer-observer-docker/pull/2"&gt;https://github.com/ClubeBitcoinUnB/peer-observer-docker/pull/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="fork-observer"&gt;fork-observer&lt;/h2&gt;
&lt;p&gt;With &amp;ldquo;1sat/vbyte summer&amp;rdquo;, monitoring for stale blocks has become more important again as stale-blocks are an indication for poor network block propagation. By connecting to public electrum servers, we have a much better view into the network by having much more data sources. So I implemented &lt;a href="https://github.com/0xB10C/fork-observer/pull/86"&gt;Electrum backend support&lt;/a&gt; for fork-observer and set my instance up to connect to a bunch of public electrum servers on mainnet, testnet, testnet4, and signet.&lt;/p&gt;
&lt;h2 id="misc"&gt;misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I started thinking about an IPC-based tracing interface for tracing Bitcoin Core (as opposed to the current USDT/eBPF interface), and asked ryanofsky, who has been spearheading the Bitcoin Core IPC and multiprocess work, for his thoughts on this in &lt;a href="https://github.com/bitcoin-core/libmultiprocess/issues/185"&gt;https://github.com/bitcoin-core/libmultiprocess/issues/185&lt;/a&gt;. He was so kind to write a quick demo of how this would look like. I haven&amp;rsquo;t found the time to dive into it, but I see this as worthwhile project to explore.&lt;/li&gt;
&lt;li&gt;continued to maintain the website of the Frankfurt Bitcoin Meetup &lt;a href="https://github.com/btcffm/website-btcffm/pull/25"&gt;June meetup&lt;/a&gt;, &lt;a href="https://github.com/btcffm/website-btcffm/pull/26"&gt;July meetup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;maintained &lt;a href="https://github.com/0xB10C/github-metadata-mirror"&gt;github-metadata-mirror&lt;/a&gt; project with &lt;a href="https://github.com/0xB10C/github-metadata-mirror/pull/6"&gt;#6&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/github-metadata-mirror/pull/8"&gt;#8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Helping to organize an upcoming &lt;a href="https://coredev.tech/"&gt;CoreDev&lt;/a&gt; event&lt;/li&gt;
&lt;li&gt;Bitcoin Core GUIX sigs for v28.2 and its release candidates: &lt;a href="https://github.com/bitcoin-core/guix.sigs/pulls?q=is%3Apr+author%3A0xB10C+is%3Aclosed"&gt;https://github.com/bitcoin-core/guix.sigs/pulls?q=is%3Apr+author%3A0xB10C+is%3Aclosed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>peer-observer: A tool and infrastructure for monitoring the Bitcoin P2P network for attacks and anomalies</title><link>https://b10c.me/projects/024-peer-observer/</link><pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/024-peer-observer/</guid><description>&lt;p&gt;Over the past few years, I&amp;rsquo;ve been working on monitoring tools for the Bitcoin network. One of these projects is &lt;a href="https://github.com/0xB10C/peer-observer"&gt;peer-observer&lt;/a&gt;: A tool and infrastructure for monitoring the Bitcoin P2P network for attacks and anomalies. This post describes the motivation for starting yet another Bitcoin network observer. It details how the tool works, what my honeypot infrastructure looks like, and finishes with an idea for a decentralized Bitcoin Network Operations Collective and incident response team.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;At some point in late 2021, I stumbled across reports of an &lt;code&gt;addr&lt;/code&gt; message flooding having happened a few months earlier on the Bitcoin network. It was first reported by Piotr Narewski, the maintainer of the &lt;a href="https://github.com/piotrnar/gocoin"&gt;Gocoin&lt;/a&gt; Bitcoin node implementation, in a thread called &lt;a href="https://bitcointalk.org/index.php?topic=5348856.0"&gt;Loads of fake peers advertised on bitcoin network&lt;/a&gt;. Piotr details that his node was receiving &amp;ldquo;hundreds of thousands of non-working [IP] addresses&amp;rdquo; via the &lt;code&gt;addr&lt;/code&gt; P2P message in July 2021. Since his node implementation stores all addresses&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, he is experiencing high resource usage and needs more connection attempts until a working peer is found.&lt;/p&gt;
&lt;p&gt;Matthias Grundmann and Max Baumstark from the Karlsruhe Institute of Technology noticed this attack on their DSN Bitcoin Monitoring infrastructure, too. In a &lt;a href="https://arxiv.org/pdf/2108.00815v1"&gt;preprint&lt;/a&gt; for a paper, they write: &amp;ldquo;Some peers in the Bitcoin P2P network distributed a huge amount of spam IP addresses during July 2021. These spam IP addresses did not belong to actual Bitcoin peers.&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;At the same time, the Bitcoin Core PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/22387"&gt;#22387: Rate limit the processing of rumoured addresses&lt;/a&gt;, is being tested and reviewed. This PR implements rate-limiting for the number of addresses a peer can send to a node. Previously, Bitcoin Core would &lt;a href="https://github.com/bitcoin/bitcoin/pull/22387#issue-935319998"&gt;&amp;ldquo;happily accept and process an effectively unbounded rate from attackers&amp;rdquo;&lt;/a&gt;. During testing of this PR, reviewers &lt;a href="https://github.com/bitcoin/bitcoin/pull/22387#issuecomment-878411443"&gt;note&lt;/a&gt; that the rate-limiting is being triggered by peers on mainnet. This is likely related to the same &lt;code&gt;addr&lt;/code&gt;-flodding attack observed by Piotr, Matthias, and Max.&lt;/p&gt;
&lt;p&gt;Three years later, the Bitcoin Core project discloses &lt;a href="https://bitcoincore.org/en/2024/07/31/disclose-addrman-int-overflow/"&gt;CVE-2024-52919 - Remote crash due to addr message spam&lt;/a&gt; discovered by Eugene Siegel. An attacker could remotely crash Bitcoin Core nodes by spamming &lt;code&gt;addr&lt;/code&gt; messages that would then be inserted into the &lt;code&gt;addrman&lt;/code&gt;. The node would crash due to a 32-bit identifier overflowing. This was fixed by the rate limiting implemented in PR #22387 and then later changing the 32-bit identifier to a 64-bit identifier.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Learning about this attack a few months after it happened, I started to wonder what other attacks are happening on the Bitcoin P2P network. While the &lt;code&gt;addr&lt;/code&gt;-flodding attack was detected by a few different parties, I had the feeling that it would be good to not only learn about these attacks by coincidence. The idea for another Bitcoin network monitoring tool was born.&lt;/p&gt;
&lt;h2 id="the-peer-observer-tool"&gt;The peer-observer tool&lt;/h2&gt;
&lt;p&gt;Early on, I decided that my monitoring should be passive and as minimally invasive to the Bitcoin network as possible. This means, for example, not to use up inbound connection slots by connecting to all possible Bitcoin nodes on the network. I decided to run multiple honeypot nodes that are normal, honest nodes that behave well and participate in block and transaction relay. They just have a lot of monitoring tools attached to them.&lt;/p&gt;
&lt;p&gt;Since Bitcoin Core is currently the most widely used node software on the Bitcoin network, I choose to focus on it for now&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. This allows for testing out various Bitcoin Core node features and configuration options that could be susceptible to attacks or anomalies (i.e., bugs).&lt;/p&gt;
&lt;h3 id="interfaces"&gt;Interfaces&lt;/h3&gt;
&lt;p&gt;To extract data and events about the P2P network and the peers connected to a Bitcoin Core node, interfaces are needed. To learn about events in real-time via machine-to-machine interface, I started implementing peer-observer primarily with the Bitcoin Core tracepoint interface &lt;a href="https://b10c.me/blog/008-bitcoin-core-usdt-support/"&gt;I&amp;rsquo;ve been working on&lt;/a&gt; over the past few years. The tracepoint interface provides all data required for an MVP. As alternatives, I also considered parsing the Bitcoin Core &lt;code&gt;debug.log&lt;/code&gt; similar to James O&amp;rsquo;Beirne&amp;rsquo;s &lt;a href="https://github.com/chaincodelabs/bmon"&gt;bmon&lt;/a&gt; tool and fetching data from the RPC interface. The debug log is primarily an interface for humans and not machines. Parsing log messages that might change without warning over time didn&amp;rsquo;t seem optimal to me. Polling on the RPC interface doesn&amp;rsquo;t give me the required resolution. For example, between two calls to &lt;code&gt;getpeerinfo&lt;/code&gt;, multiple peers might have connected and already disconnected.&lt;/p&gt;
&lt;p&gt;Nonetheless, I&amp;rsquo;ve started exploring adding support for extracting data from &lt;a href="https://github.com/0xB10C/peer-observer/issues/141"&gt;more interfaces&lt;/a&gt;. The RPC interface, and particularly, &lt;code&gt;getpeerinfo&lt;/code&gt;, can be useful to get more stateful information about peers. Since the tracepoint interface has a few pain points, I&amp;rsquo;ve also started thinking about an &lt;a href="https://github.com/bitcoin-core/libmultiprocess/issues/185"&gt;IPC-based alternative&lt;/a&gt;. In the future, it might become worthwhile to supplement the data with parsed debug.log output, while keeping in mind that the log statements might change over time.&lt;/p&gt;
&lt;p&gt;For peer-observer, I primarily use the &lt;a href="https://github.com/bitcoin/bitcoin/blob/29.x/doc/tracing.md"&gt;&lt;code&gt;net&lt;/code&gt;&lt;/a&gt; tracepoints to learn about in- and outbound P2P messages and opened, closed, evicted, or misbehaving connections. The &lt;a href="https://github.com/bitcoin/bitcoin/blob/29.x/doc/tracing.md#tracepoint-validationblock_connected"&gt;&lt;code&gt;validation:block_connected&lt;/code&gt;&lt;/a&gt; tracepoint, along with &lt;a href="https://github.com/bitcoin/bitcoin/blob/29.x/doc/tracing.md#context-mempool"&gt;&lt;code&gt;mempool&lt;/code&gt;&lt;/a&gt; tracepoints, are also interesting to get insights into block processing and the node&amp;rsquo;s mempool. For a while, I&amp;rsquo;ve also maintained custom tracepoints in a patch, for example, &lt;code&gt;addrman&lt;/code&gt; tracepoints, to see how much effect peers can have on our addrman.&lt;/p&gt;
&lt;h3 id="extractors-and-tools"&gt;Extractors and Tools&lt;/h3&gt;
&lt;p&gt;To be able to process data from multiple Bitcoin Core interfaces in multiple tools, I choose a message queue that allows for multiple publishers and consumers. &lt;em&gt;Extractors&lt;/em&gt;, like the &lt;code&gt;ebpf-extractor&lt;/code&gt;, hooking into the tracepoint interface, publish events into the message queue. On the other end, subscribers or &lt;em&gt;Tools&lt;/em&gt; consume these events and further process them. The most basic tool, called &lt;code&gt;logger&lt;/code&gt;, just logs all received events. As a message queue peer-observer is using a &lt;a href="NATS.io"&gt;NATS.io&lt;/a&gt; server. Messages are serialized using protobuf.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;
┌──────────────────────┐
NATS.io │ Tools │
PUB-SUB │ │
┌──────┼──► logger │
Tracepoints │ │ │
┌───────────┐ via libbpf ├──────┼──► metrics │
│ Bitcoin │ ┌───────────────┐ │ │ │
│ Core Node ├───────► ebpf-extractor├────┼──────┼──► websocket │
└───────────┘ └───────────────┘ │ │ │
├──────┼──► addr-connectivty │
│ │ │
└──────┼──►... │
protobuf │ │
messages └──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="logger-tool"&gt;&lt;code&gt;logger&lt;/code&gt; tool&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://github.com/0xB10C/peer-observer/tree/master/tools/logger"&gt;&lt;code&gt;logger&lt;/code&gt; tool&lt;/a&gt; logs events to &lt;code&gt;stdout&lt;/code&gt; and supports basic topic filtering (thanks to &lt;a href="https://github.com/nassersaazi"&gt;Nasser&lt;/a&gt; for &lt;a href="https://github.com/0xB10C/peer-observer/pull/138"&gt;PR #138&lt;/a&gt;). I mainly use it to show how much communication is happening between a Bitcoin Core node and its peers.&lt;/p&gt;
&lt;p&gt;The output of the tool looks similar to the following snippet. Here, &lt;code&gt;&amp;lt;-&lt;/code&gt; and &lt;code&gt;-&amp;gt;&lt;/code&gt; indicate an in- and outbound P2P message to or from our node. Connection events are marked with &lt;code&gt;# CONN&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;--&amp;gt; to id=11937171 (conn_type=1): wtxidrelay
--&amp;gt; to id=11937171 (conn_type=1): sendaddrv2
--&amp;gt; to id=11937171 (conn_type=1): verack
&amp;lt;-- from id=10237648 (conn_type=1): Inv([WTx(205bfe2dfbeb46c7d91963a13097ef49511ad2d71c3018fdbdebbff83d8caa2f), WTx(0cd27eb1f63d95c0ec82adf0090756aef0eb1b1e840634ec7a4f440919ab991c), WTx(98bb7eb29ab06dcfdd30aa4875ebafcedd14da2738d63d0cc8d6dcc0f3a12e8b), WTx(a361125873bffb5d70636e50bac18bd71963821d05ba07d9d70c91e660779632)])
&amp;lt;-- from id=10752006 (conn_type=1): AddrV2([Address(timestamp=1750941674, address=IPv4(XXX), port=8333, services=3077), Address(timestamp=1750941953, address=IPv4(XXX), port=8333, services=3081), Address(timestamp=1750941813, address=IPv6(XXX), port=8333, services=1032)])
&amp;lt;-- from id=10162242 (conn_type=1): Inv([WTx(5a7a949a920cf57eacd8ad38906a56ba6882188dda4ff9ea5660aad35adf1ef4), WTx(1acc1f2f3ec70c4ffd2181783bb2407e204be39b1017b5ae13d45b9b54a19e43), WTx(5a68c9197c31b5629c146be6d789a44bbb03e2c43633216ec0ca8cd73bd737f2), WTx(78ad4525de5b0e03db2b552b0091275caf781d57b04affc468d960ba645c1370)])
# CONN EvictedInboundConnection(conn=Connection(id=11937171, addr=&amp;lt;linkinglion&amp;gt;, conn_type=1, network=2), time_established=1750942328)
# CONN InboundConnection(conn=Connection(id=11937172, addr=&amp;lt;linkinglion&amp;gt;, conn_type=1, network=1), existing_connections=115)
# CONN ClosedConnection(conn=Connection(id=11937171, addr=&amp;lt;linkinglion&amp;gt;, conn_type=1, network=2), time_established=1750942328)
&amp;lt;-- from id=11554724 (conn_type=1): Inv([WTx(12eff03d987ef34ec759abe864bd88c2ecb4c994bd23ac18680fed251440020a), WTx(1e34d5e8d3e34ee7257363a34c877ea6031f0657574c78d6d1379485e1a8b533), WTx(68ae6c3279f1797f08f90948d7599ec60a476f896013f164a49b38eae10c6cf9), WTx(d1d6a9a50d1059db07a8367e52a6569d989f2dcdde24e56369aae2aaab4cf0aa), WTx(8c3c1033296b4c593560f40fd22929cbcc6f63c3ec20287829e9b52dea9a4ea2), WTx(77c7e46e402f94c4a03445479e727b67008e105f2c97d3120e8c2d2008b6c6c3)])
&amp;lt;-- from id=11875990 (conn_type=1): Pong(16368378148765531861)
&amp;lt;-- from id=8950578 (conn_type=1): Inv([WTx(12eff03d987ef34ec759abe864bd88c2ecb4c994bd23ac18680fed251440020a), WTx(1e34d5e8d3e34ee7257363a34c877ea6031f0657574c78d6d1379485e1a8b533), WTx(68ae6c3279f1797f08f90948d7599ec60a476f896013f164a49b38eae10c6cf9), WTx(8c3c1033296b4c593560f40fd22929cbcc6f63c3ec20287829e9b52dea9a4ea2), WTx(77c7e46e402f94c4a03445479e727b67008e105f2c97d3120e8c2d2008b6c6c3)])
&amp;lt;-- from id=11833825 (conn_type=1): Inv([WTx(5a7a949a920cf57eacd8ad38906a56ba6882188dda4ff9ea5660aad35adf1ef4), WTx(1acc1f2f3ec70c4ffd2181783bb2407e204be39b1017b5ae13d45b9b54a19e43), WTx(205bfe2dfbeb46c7d91963a13097ef49511ad2d71c3018fdbdebbff83d8caa2f), WTx(0cd27eb1f63d95c0ec82adf0090756aef0eb1b1e840634ec7a4f440919ab991c), WTx(a361125873bffb5d70636e50bac18bd71963821d05ba07d9d70c91e660779632), WTx(5de0156daa756bdcad84a93699972a6ecb451841f2404ad181bd540c87006756), WTx(f2c8df33b2ef2e15c6d239e05f651927b4108758d99bafb478e76b9f7827e19d)])
--&amp;gt; to id=8444252 (conn_type=2): Inv([WTx(1e34d5e8d3e34ee7257363a34c877ea6031f0657574c78d6d1379485e1a8b533), WTx(68ae6c3279f1797f08f90948d7599ec60a476f896013f164a49b38eae10c6cf9), WTx(8c3c1033296b4c593560f40fd22929cbcc6f63c3ec20287829e9b52dea9a4ea2), WTx(77c7e46e402f94c4a03445479e727b67008e105f2c97d3120e8c2d2008b6c6c3)])
&amp;lt;-- from id=11937172 (conn_type=1): Version(version=70016, services=3081, timestamp=1750942328, receiver=Address(timestamp=0, address=IPv4(XXX), port=8333, services=0), sender=Address(timestamp=0, address=IPv4(XXX), port=8333, services=0), nonce=0, user_agent=/bitcoinj:0.14.5/Bitcoin Wallet:5.40/, start_height=902650, relay=true)
--&amp;gt; to id=11937172 (conn_type=1): Version(version=70016, services=3080, timestamp=1750942328, receiver=Address(timestamp=0, address=IPv4(XXX), port=62311, services=0), sender=Address(timestamp=0, address=IPv4(0.0.0.0), port=0, services=3080), nonce=redacted, user_agent=/Satoshi:28.00.0/, start_height=902811, relay=true)
--&amp;gt; to id=11937172 (conn_type=1): wtxidrelay
--&amp;gt; to id=11937172 (conn_type=1): sendaddrv2
--&amp;gt; to id=11937172 (conn_type=1): verack
&amp;lt;-- from id=11937172 (conn_type=1): verack
--&amp;gt; to id=11937172 (conn_type=1): SendCompact(send_compact=false, version=2)
--&amp;gt; to id=11937172 (conn_type=1): Ping(2927426282439637971)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="metrics-tool"&gt;&lt;code&gt;metrics&lt;/code&gt; tool&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://github.com/0xB10C/peer-observer/tree/master/tools/metrics"&gt;&lt;code&gt;metrics&lt;/code&gt; tool&lt;/a&gt; transforms individual events into aggregated statistics and serves them as Prometheus metrics. These metrics can then be displayed in Grafana dashboards. This allows for visual exploration and dashboard playlists that can help to detect attacks and anomalies visually. While there are some Grafana alerts for restarted nodes and inbound connections dropping, more work can be done on automatic anomaly detection. For this, the Prometheus recording rules mentioned in &lt;a href="https://github.com/0xB10C/peer-observer/issues/13#issuecomment-2404570158"&gt;#13 (comment)&lt;/a&gt; could be useful to explore.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/024-peer-observer/grafana-block-connection-duration.png'
alt='A Grafana dashboard showing the time it takes to connect blocks per node. Some nodes are faster than others due to hardware and configuration differences. For example, node frank is usually slower as it doesn&amp;#39;t have a mempool and needs to validate all transactions. The other nodes have already validated the transactions. '&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A Grafana dashboard showing the time it takes to connect blocks per node. Some nodes are faster than others due to hardware and configuration differences. For example, node frank is usually slower as it doesn&amp;rsquo;t have a mempool and needs to validate all transactions. The other nodes already have validated the transactions. An interactive version of this dashboard can be found on &lt;a href="https://snapshots.raintank.io/dashboard/snapshot/lH8uj0MNKO9BIZEi39iivDmiEzROLFQP"&gt;snapshots.raintank.io&lt;/a&gt;.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="websocket-tool"&gt;&lt;code&gt;websocket&lt;/code&gt; tool&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;websocket&lt;/code&gt; tool publishes the events from NATS into a websocket as JSON objects. This allows us to work with the events in a browser and enables building web tools and visualizations. An example is the &lt;code&gt;p2p-circle.html&lt;/code&gt; page, which displays the node connected to peer-observer in the middle and arranges the node&amp;rsquo;s peers in a circle around it. Exchanged messages and opened and closed connections are shown.&lt;/p&gt;
&lt;div class="embed-responsive embed-responsive-1by1"&gt;
&lt;video autoplay muted loop class="embed-responsive-item"&gt;
  &lt;source src="https://b10c.me/data/projects/024-peer-observer/websocket-p2p-circle.mp4" type="video/mp4"&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;p&gt;The video shows the &lt;code&gt;p2p-circle.html&lt;/code&gt; page with the peer-observer node in the middle and its peers arranged in a circle around it. The peers are labeled with their peer-id and colored by connection type: blue peers are inbound connections, red ones are full-relay-outbound, and yellow peers are block-only-outbound peers. Additionally, the P2P message exchange between the node and its peers, and new inbound connections being accepted and others being closed, can be seen.&lt;/p&gt;
&lt;h2 id="the-peer-observer-infrastructure"&gt;The peer-observer infrastructure&lt;/h2&gt;
&lt;p&gt;As of writing, I host 12 honeypot nodes with different configurations across the globe as part of the peer-observer infrastructure. Running nodes with different configurations means having a bigger attack surface, and at the same time, being able to detect anomalies for more features. Some nodes run with privacy networks like Tor and I2P enabled. An attacker might perfer to attack via these to avoid leaving an IP address trail. Some nodes have bloom filters (known to be DoS-able, and observed in &lt;a href="https://github.com/bitcoinj/bitcoinj/issues/3404"&gt;bitcoinj/bitcoinj #3404&lt;/a&gt;) and compact block filters enabled. Others run with a ban list to block, for example, connections from &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion&lt;/a&gt;, and some are testing an &lt;a href="https://asmap.org/"&gt;ASMap&lt;/a&gt; file. Some nodes are pruned, others are not. Some accept an increased number of inbound connections, some don&amp;rsquo;t have a mempool and only process blocks, while others run behind a NAT and can only accept inbound connections from Tor, I2P, and CJDNS. A few nodes run binaries compiled with LLVM sanitizers (particularly ASan and UBSan) enabled. Most nodes run on &lt;code&gt;x86_64&lt;/code&gt;, but I also have a few on &lt;code&gt;aarch64&lt;/code&gt;. Generally, the nodes can run with different PRs, master versions, release candidates, or releases of Bitcoin Core. All nodes are configured with detailed &lt;code&gt;debug.log&lt;/code&gt; logging enabled. As of writing, I have collected more than 35 TB&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; of debug logs, which can be used in future research projects.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://dci.mit.edu"&gt;MIT DCI&lt;/a&gt; generously sponsored six nodes between June 2023 and April 2025. I&amp;rsquo;m grateful for their trust and support in building out this project. Also, thanks to Sam, who has been very pleasant to work with. In April 2025, &lt;a href="brink.dev"&gt;Brink&lt;/a&gt; took over sponsorship of these six nodes. The other six nodes are currently paid by me, as is the archival storage, web, and database servers. A public list of nodes can be found on &lt;a href="https://public.peer.observer/"&gt;public.peer.observer&lt;/a&gt;. Since the nodes are supposed to be honey pot nodes, I can&amp;rsquo;t share node IP addresses, hosting details, and data publicly. An attacker would know which nodes to ignore when attacking. I&amp;rsquo;ve been thinking about setting up a demo node with a public frontend and public IP address for people to explore and experiment with. However, I have had more urgent items in my backlog for now.&lt;/p&gt;
&lt;p&gt;To manage and deploy nodes to different cloud providers and hardware with different node configurations and versions, &lt;a href="nixos.org"&gt;NixOS&lt;/a&gt; has proven to be a useful tool. It allows me to write the infrastructure as code, track it in git, and have reproducible deployments across hosts. Without it, I don&amp;rsquo;t think maintaining the peer-observer infrastructure would have been possible as a one-man job. The Nix package and NixOS module for peer-observer are published in &lt;a href="https://github.com/0xb10c/nix"&gt;0xb10c/nix&lt;/a&gt;, and I think I can publish a NixOS Flake for the node configuration at some point while maintaining my infrastructure in a separate, private Flake.&lt;/p&gt;
&lt;p&gt;Next to peer-observer and Bitcoin Core nodes, the infrastructure also includes a &lt;a href="https://github.com/0xB10C/fork-observer"&gt;fork-observer&lt;/a&gt; instance connected to the nodes (this is &lt;a href="https://public.peer.observer/forks/"&gt;publicly accessible&lt;/a&gt;) and an installation of &lt;a href="https://github.com/0xB10C/addrman-observer"&gt;addrman-observer&lt;/a&gt;, which allows viewing the addrman of the nodes. Next to the &lt;code&gt;metrics&lt;/code&gt; tool, each host runs a &lt;a href="https://github.com/prometheus/node_exporter"&gt;node_exporter&lt;/a&gt; that allows Prometheus to fetch metrics on CPU, RAM, disk, network, and more. Additionally, a &lt;a href="https://github.com/ncabatoff/process-exporter"&gt;process_exporter&lt;/a&gt; exports metrics on CPU time spent in each Bitcoin Core thread. Each host also runs a service that uploads the Bitcoin Core logs to a remote data share.&lt;/p&gt;
&lt;h2 id="some-peer-observer-findings"&gt;Some peer-observer findings&lt;/h2&gt;
&lt;p&gt;While building out the tooling and infrastructure, I already made a few observations of attacks and anomalies, and had the chance to use the node data for research into compact block relay efficiency. I&amp;rsquo;m linking to some write-ups below.&lt;/p&gt;
&lt;p&gt;Early on, I discovered an entity I call &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion&lt;/a&gt;, which opens multiple connections to Bitcoin nodes and listens to transaction announcements. The entity has been active since at least 2018 and is connecting to nodes on the Monero network, too. I assume the entity is a blockchain analysis company collecting data to improve its products. This is a privacy attack on Bitcoin users. Having access to data from multiple nodes makes it possible to detect attacks like these.&lt;/p&gt;
&lt;p&gt;In May 2023, I noticed an anomaly in the number of inbound connections on one of my peer-observer nodes. Its inbound connections dropped, and the node had 100% CPU utilization. Looking into it, it turned out that an edge case in the Bitcoin Core transaction relay implementation had been triggered, and the node could not keep up with normal operation. Since many nodes in the network were affected by this, it had an effect on the whole network to the point where block and transaction relay were impacted. I&amp;rsquo;ve written down my notes from back then in &lt;a href="https://b10c.me/observations/15-inv-to-send-queue/"&gt;this&lt;/a&gt; post. Having more monitoring and nodes back then would have helped to pinpoint and react to this anomaly faster.&lt;/p&gt;
&lt;p&gt;One of my goals has always been to extract insights from data and feed them back into Bitcoin development. With detailed, historical logs from multiple nodes available, I published &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052"&gt;Stats on compact block reconstructions&lt;/a&gt;. This led to a renewed discussion on prefilling compact blocks to improve block relay. I hope to get back to finishing the implementation of this at some point.&lt;/p&gt;
&lt;p&gt;There is a lot more data to process and monitoring to build out. I can&amp;rsquo;t monitor the Bitcoin network, analyze data, and build out tools alone. This could easily be a full-time effort for a small team of developers and data scientists. I&amp;rsquo;d be very happy to share data and help with processing, analyzing, and publishing findings. I&amp;rsquo;m also certain that finding funding for this work, given some prior Proof-of-Work, isn&amp;rsquo;t too hard at the moment.&lt;/p&gt;
&lt;h2 id="a-bitcoin-network-operations-collective"&gt;A Bitcoin Network Operations Collective&lt;/h2&gt;
&lt;p&gt;As mentioned above, I noticed that I could use a few helping hands that are interested in monitoring the Bitcoin network health and analyzing data to provide data-based feedback for development. As of writing, Bitcoin has a market cap well over $2T USD. That&amp;rsquo;s more than Meta and Google, and close to Amazon. What monitoring infrastructure, Network Operation Centers, and incident response teams do these companies have to protect against attacks and anomalies? And what does Bitcoin have?&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/024-peer-observer/slide-big-company-monitoring.png'
alt='Current state of Bitcoin monitoring compared to companies with a similar market cap.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Current state of Bitcoin monitoring compared to companies with a similar market cap.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There are a few developers I know running nodes and looking at logs, the KIT DSN is running their &lt;a href="https://www.dsn.kastel.kit.edu/bitcoin/"&gt;Bitcoin Network Monitoring&lt;/a&gt; infrastructure, a Brazilian University is building out a Bitcoin Monitoring Lab, I heard, and Lopp&amp;rsquo;s &lt;a href="https://statoshi.info/"&gt;statoshi.info&lt;/a&gt; is still running. And I have a spare Raspberry Pi on my desk that cycles through a few Grafana dashboards and might get a ping to my phone if on some node the connections drop faster than expected. What happens when I&amp;rsquo;m asleep, on vacation, or just don&amp;rsquo;t have the time to look into it?&lt;/p&gt;
&lt;p&gt;With initial tooling and infrastructure in place, I&amp;rsquo;ve been thinking about the next step to improve the situation. If Bitcoin were a company, a Network Operations Center could be formed, and people could be hired for an incident response team. In Bitcoin, this works differently, and some might even reject a Network Operations &lt;strong&gt;Center&lt;/strong&gt; as too centralizing. Similarly, people can&amp;rsquo;t be hired for a job like this. I think they need to be self-driven, curious about the behavior of network participants, and motivated to ensure the longevity of Bitcoin and its network.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;ve been thinking about might be better described as a Network Operations &lt;strong&gt;Collective&lt;/strong&gt;. A loose, decentralized group of people who share the interest of monitoring the Bitcoin Network. A collective to enable sharing of ideas, discussion, data, tools, insights, and more. Maybe with a chat channel, a forum, a shared data store, and access to, for example, monitoring tools like my peer-observer. A place where a Bitcoin network incident could be analyzed, discussed, and ideally resolved, even if some members aren&amp;rsquo;t online. A collective with good relationships to developers, companies, and the community, to be able to reach out and be reachable if required.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure if the time is right for this idea yet, and I&amp;rsquo;ll likely think about it for a bit longer. If you happen to have any input on this idea, want to support it, or want to get more into monitoring the Bitcoin network in some capacity, please reach out. If you don&amp;rsquo;t have an open communication channel with me yet, feel free to write an email to &lt;code&gt;bitcoin-noc@(domain of my blog)&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/024-peer-observer/bnoc.png'
alt='Is this how a Bitcoin Network Operations Collective could look like?'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;To avoid this problem, Bitcoin Core&amp;rsquo;s IP address manager (addrman) does not store all IP addresses it receives. It has a table with a fixed size and a DoS-resistant insertion and eviction policy.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I don&amp;rsquo;t think the attacker tried to exploit CVE-2024-52919, but it remains unclear who the &lt;code&gt;addr&lt;/code&gt;-flooding attacker and what motivation for this attack was.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;With the recent popularity of Bitcoin Knots, I&amp;rsquo;m considering adding a Bitcoin Knots node or two into my setup. However, since Knots and Core share most of the codebase, I don&amp;rsquo;t think there should be a lot of new insights to gain from observing a Knots node. Additionally, the main promise of the Knots patch set is the limited mempool policy, which ideally should make it less susceptible to e.g., mempool-based Denial-of-Service attacks. If you&amp;rsquo;re nonetheless interested in sponsoring a Knots node for my monitoring infrastructure, please feel free to reach out.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Luckily, Bitcoin Core debug.logs compress fairly well, and I&amp;rsquo;ve been starting to combine and recompress them to save a bit of disk space.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Notes on 'DoS due to inv-to-send sets growing too large' from May 2023</title><link>https://b10c.me/observations/15-inv-to-send-queue/</link><pubDate>Tue, 24 Jun 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/15-inv-to-send-queue/</guid><description>&lt;p&gt;In October 2024, the Bitcoin Core project disclosed a &lt;a href="https://bitcoincore.org/en/2024/10/08/disclose-large-inv-to-send/"&gt;Denial-of-Service due to inv-to-send sets growing too large&lt;/a&gt;, which I authored, for Bitcoin Core versions before v25.0. I have a few notes and screenshots from my investigation back then that I want to persist here. In early May 2023, my monitoring infrastructure noticed this bug affecting mainnet nodes, which allowed me to pinpoint where the problem came from. Credit for working on a &lt;a href="https://github.com/bitcoin/bitcoin/pull/27610"&gt;fix&lt;/a&gt; goes to &lt;a href="https://www.erisian.com.au/wordpress/"&gt;Anthony Towns&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="observation"&gt;Observation&lt;/h2&gt;
&lt;p&gt;On May 2nd, 2023, I noticed that on one of my monitoring nodes, the inbound connections had dropped from about 190&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; to only 35 over about two days. Normally, a node keeps its filled inbound slots until it restarts or loses network connectivity.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/15-inv-to-send-queues/inbound-connections-dropping-2023-04-30.png'
alt='A screenshot of my Grafana dashboard for monitoring the number of in and outbound connections of my Bitcoin Core node. The yellow bars show the number of inbound connections. These drop off at the end of the graph.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A screenshot of my Grafana dashboard for monitoring the number of in and outbound connections of my Bitcoin Core node. The yellow bars show the number of inbound connections. These drop off at the end of the graph.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Checking in with other contributors, I noticed the node had 100% CPU utilization. This affected the node to a point where it could not to keep communicating with its peers, which resulted in inbound connections timing out and dropping. Using &lt;code&gt;perf top&lt;/code&gt; on the node process, I could see that a lot of CPU time was being spent on &lt;code&gt;CTxMemPool::CompareDepthAndScore()&lt;/code&gt; in the &lt;code&gt;b-msghand&lt;/code&gt; thread. I recorded the following flamegraph, which shows that &lt;code&gt;make_heap()&lt;/code&gt;, which calls &lt;code&gt;CompareDepthAndScore()&lt;/code&gt;, used over 45% of the CPU time of the process.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/15-inv-to-send-queues/237420554-7e31c262-648f-4168-9a9a-5cc93b4a9da0.svg'
alt='A flame graph showing where the CPU time of the Bitcoin Core process is being spent. Open in a new tab to interact with this flame graph.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A flame graph showing where the CPU time of the Bitcoin Core process is being spent. Open in a new tab to interact with this flame graph.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At the same time, there was an open, unrelated &lt;a href="https://github.com/bitcoin/bitcoin/issues/27586"&gt;100% CPU usage issue&lt;/a&gt; with debug mode builds of Bitcoin Core. This confused some contributors and users who weren&amp;rsquo;t running debug mode builds but noticed the high CPU usage on their nodes. While the debug mode issue likely only affected some developers, the other high CPU usage issue affected the entire network. This included, for example, mining pools such as &lt;a href="https://github.com/bitcoin/bitcoin/issues/27623"&gt;AntPool and others&lt;/a&gt;, who reported problems with their mining operations because to their nodes failing to process received blocks in a timely manner.&lt;/p&gt;
&lt;h2 id="effect"&gt;Effect&lt;/h2&gt;
&lt;p&gt;Observing ping timings across the network reveals the effect of this Denial-of-Service. Since Bitcoin Core&amp;rsquo;s message processing is single-threaded, only one message can be created or processed at a time, meaning that all other peers have to wait. Longer wait times impact the response time for a ping. The &lt;a href="https://www.dsn.kastel.kit.edu/bitcoin/"&gt;KIT DSN Bitcoin monitoring&lt;/a&gt; has data on ICMP and Bitcoin protocol pings. Comparing these allows us to determine when node software has problems keeping up with message processing. The data shows the ICMP ping to the host remained unaffected, however, the median ping to the Bitcoin node software nearly doubled from about 25ms to more than 50ms between the end of April and early May. The median Bitcoin ping spiked to 200ms on May 8th, while the ICMP ping remained unaffected.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/15-inv-to-send-queues/bitcoin-ping-icmp-ping.png'
alt='Block propagation delay: average time until 50% and 90% of the network announced a block based on data from DSN KIT (https://www.dsn.kastel.kit.edu/bitcoin/)'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Median Bitcoin protocol ping and ICMP ping. Based on data from DSN KIT (&lt;a href="https://www.dsn.kastel.kit.edu/bitcoin/"&gt;https://www.dsn.kastel.kit.edu/bitcoin/&lt;/a&gt;)&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The effect can also be seen by looking at the block propagation delay data collected by the &lt;a href="https://www.dsn.kastel.kit.edu/bitcoin/"&gt;KIT DSN Bitcoin monitoring&lt;/a&gt;. Around May 8th, 2023, a spike in the block propagation delay is visible. The time it took 50% of the reachable nodes to announce the block to their monitoring nodes increased from less than a second to more than five seconds. Similarly, the 90% measurement spiked from about two seconds to more than 20 seconds.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/15-inv-to-send-queues/block-propagation-delay.png'
alt='Block propagation delay: average time until 50% and 90% of the network announced a block based on data by DSN KIT (https://www.dsn.kastel.kit.edu/bitcoin/)'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Block propagation delay: average time until 50% and 90% of the network announced a block. Based on data by DSN KIT (&lt;a href="https://www.dsn.kastel.kit.edu/bitcoin/"&gt;https://www.dsn.kastel.kit.edu/bitcoin/&lt;/a&gt;)&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Bad block propagation also causes more stale blocks as mining pools mine on their outdated blocks for longer, while a new block they haven&amp;rsquo;t seen yet already exists in the network. Based on the data from my &lt;a href="https://github.com/bitcoin-data/stale-blocks"&gt;stale-blocks dataset&lt;/a&gt;, ten stale blocks were observed during the week between May 3rd (starting with stale block 788016) and May 10th (and ending with block 789147). That&amp;rsquo;s a rate of about 8.84 stale blocks per 1000 blocks. In comparison, between blocks 800000 and 900000 (about two years), 73 stale blocks were observed. This is a rate of 0.73 stale blocks per 1000 blocks. This 10-fold increase in the stale-block rate was likely caused by block propagation being significantly affected.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;Why did the function &lt;code&gt;CTxMemPool::CompareDepthAndScore()&lt;/code&gt; slow down the node to a point where it had trouble processing P2P messages? In Bitcoin Core, the &lt;code&gt;b-msghand&lt;/code&gt; thread processes P2P messages. For example, passing newly received blocks to validation, responding to pings, announcing transactions to other peers, and a lot more.&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;CTxMemPool::CompareDepthAndScore()&lt;/code&gt; is used when deciding which transactions to announce to a peer next. In the Bitcoin P2P protocol, transactions are announced via &lt;code&gt;inv&lt;/code&gt; (inventory) messages. A Bitcoin Core transaction announcement to a peer usually contains up to 35 &lt;code&gt;wtxid&lt;/code&gt; entries. To keep track of which transactions to announce to a peer next, there is a per-peer &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; set. It contains the transactions the node thinks the peer hasn&amp;rsquo;t seen yet. When constructing an inventory message for a peer, the set is sorted by transaction dependencies and feerate to prioritize high-feerate transactions and to avoid leaking the order the node learned about the transactions. For this, the &lt;code&gt;CTxMemPool::CompareDepthAndScore()&lt;/code&gt; comparison function is used.&lt;/p&gt;
&lt;p&gt;In early May 2023, a huge amount of transactions related to &lt;a href="https://www.coindesk.com/tech/2023/05/08/bitcoins-brc-20-explosion-sends-users-scrambling-for-options-including-lightning"&gt;BRC-20 tokens were broadcast&lt;/a&gt;. This meant that the &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; sets grew faster than usual and larger than usual. As a result, sorting the sets took more time. On evening of May 7th (UTC), the mint of the VMPX BRC-20 token started, which resulted in more than 300k transactions being broadcast in 6h next to the other ongoing BRC-20 token mints. This caused the spikes in median ping and block propagation times observed on May 8th.&lt;/p&gt;
&lt;p&gt;The effect is amplified by so-called spy nodes which only listen to &lt;code&gt;inv&lt;/code&gt; messages and never announce transactions on their own. When a peer announces a transaction to a node,
the node can remove it from their &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; set as it&amp;rsquo;s known by the peer and does not need to be announced anymore. This meant that the sets for spy nodes were even larger and took even more time to sort as they were drained more slowly. Spy nodes, for example, &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion&lt;/a&gt; and others, are common and often have multiple connections open to a node in parallel. At times, I count more assumed spy nodes than non-spy node connections to my nodes.&lt;/p&gt;
&lt;p&gt;The huge amount of transactions being broadcast, combined with amplification by spy nodes, and non-optimal sorting of the large &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; sets by &lt;code&gt;CTxMemPool::CompareDepthAndScore()&lt;/code&gt; caused nodes to spend a lot of time creating new
inventory messages for transaction relay. Since message handling is single-threaded, communication with other peers was significantly slowed down. This reached a point where blocks weren&amp;rsquo;t processed in a timely manner and some connections timed out.&lt;/p&gt;
&lt;h2 id="fix"&gt;Fix&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/bitcoin/bitcoin/pull/27610"&gt;fix&lt;/a&gt; is twofold. First, all to-be-announced transactions that were already mined or for some other reason not in the mempool anymore, were removed before the &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; set was sorted. Previously, these transactions were removed only after the set was sorted. This avoids spending time on sorting transaction entries that will never be announced anyway and reduces the size of the to-be-sorted set. Secondly, when the &lt;code&gt;m_tx_inventory_to_send&lt;/code&gt; sets are large, the number of entries to drain from the set is dynamically increased based on the set size. This means that when many transactions are broadcast, a node will announce more transactions to its peers until the sets are smaller again. The fix was backported in time for the v25.0 release at the end of May 2023.&lt;/p&gt;
&lt;h2 id="reflection"&gt;Reflection&lt;/h2&gt;
&lt;p&gt;While a set of regular contributors knew that this was going on, this issue was not openly communicated to the public. The &lt;a href="https://github.com/bitcoin/bitcoin/issues/27586"&gt;100% CPU usage issue&lt;/a&gt; with debug mode being discussed at the same time caused confusion, even among regular Bitcoin Core contributors. At the time, I had the feeling that this could and maybe should be fixed quietly and doesn&amp;rsquo;t need a lot of publicity for the time being. In hindsight, maybe being more public and transparent with the issue could have worked too. The high number of BRC-20 broadcasts only lasted for about a week (but this wasn&amp;rsquo;t known beforehand) and restarting the node would have helped for a while. To mitigate the issue for, for example, mining pools that can&amp;rsquo;t upgrade to a version with the fix immediately (due to running with custom patches), a &lt;a href="https://github.com/0xB10C/banlist/issues/1"&gt;ban list of spy nodes&lt;/a&gt; was prepared, but I don&amp;rsquo;t know if it was ever used.&lt;/p&gt;
&lt;p&gt;While there was no dedicated communication channel for this event, a non-listed IRC channel with P2P contributors was used and interested contributors were invited or informed about the events via direct messages. As far as I&amp;rsquo;m aware, there was no incident response channel and I don&amp;rsquo;t know if one would be helpful given the ad hoc and decentralized nature of Bitcoin development. No contributor is responsible for incident response, but everyone can help.&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;m happy that my monitoring proved to be useful for this. While I didn&amp;rsquo;t have alerting for dropped connections set up at the time and only noticed it by looking at the dashboard, it was helpful to have it. To pinpoint the issue, having a few nodes to play around with and run, for example, &lt;code&gt;perf top&lt;/code&gt; on was helpful. Future monitoring should include ping times and alerting on dropped connections.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I had increased the connection count from the default 125 connection to 200.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>peer-observer: A tool and infrastructure for monitoring the Bitcoin P2P network for attacks and anomalies</title><link>https://b10c.me/talks/024-peer-observer-prague-dhd/</link><pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/024-peer-observer-prague-dhd/</guid><description>&lt;p&gt;I presented about my &lt;a href="https://public.peer.observer"&gt;peer-observer&lt;/a&gt; project during the BTC Prague dev/hack/day 2025.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.google.com/presentation/d/1XLC3sB9-fp1sVpz5K7V5k27PRmqzutSQEEI3b83vTF0/edit?usp=sharing"&gt;Slides (Google Docs)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>OpenSats Work-Log 5</title><link>https://b10c.me/funding/2025-opensats-report-5/</link><pubDate>Wed, 30 Apr 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2025-opensats-report-5/</guid><description>&lt;p&gt;This is a copy of the 5th work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications--talks"&gt;Publications &amp;amp; Talks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Continued my &amp;ldquo;Stats on compact block reconstructions&amp;rdquo;: thread by posting an update and some of the current work: &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24"&gt;https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve took a closer look at the invalid jobs by AntPool and friends during forks: &lt;a href="https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/"&gt;https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I wrote about mining centralization and how we currently have only 6 blocks producing &amp;gt;95% of the templates. Also introduced a mining centralization index: &lt;a href="https://b10c.me/blog/015-bitcoin-mining-centralization/"&gt;https://b10c.me/blog/015-bitcoin-mining-centralization/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Did a talk for Chaincode&amp;rsquo;s BOSS program on Bitcoin monitoring&lt;/li&gt;
&lt;li&gt;Held the third FFM Socratic Seminar with fjahr: &lt;a href="https://btcffm.org/#2025-04-16"&gt;https://btcffm.org/#2025-04-16&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mainnet-observer"&gt;mainnet-observer&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/0xB10C/mainnet-observer"&gt;https://github.com/0xB10C/mainnet-observer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An open-source refresh of my transactionfee.info (closed source) project showing protocol level bitcoin statistics.&lt;/p&gt;
&lt;p&gt;Over the last months, I&amp;rsquo;ve spent a bit of time to get this project closer to a point where it&amp;rsquo;s ready to be used.
The original transactionfee.info project was started in 2017 and I did a refresh of it in early 2020. I think it&amp;rsquo;s
important to know how the network and blockspace is used to reason about protocol changes, make data-based
development decisions, and to generally be aware of how the Bitcoin network is being used. Since the old Go
backend was hard to maintain and the handrolled D3.js frontend was brittle, I choose to rewrite the back- and
frontend to be more maintainable and easy to work with. This also allowed me to open-source the code. READCTED&lt;/p&gt;
&lt;p&gt;While I will host an instance on &lt;a href="https://mainnet.observer"&gt;https://mainnet.observer&lt;/a&gt;, the site is complelty self-hostable. As of writing, the
project features close to 100 charts and I plan to add more over time.&lt;/p&gt;
&lt;h3 id="bitcoin-core"&gt;Bitcoin Core&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;attended CoreDev meeting: Next to the usual CoreDev program, I held a session on current data and monitoring efforts and two sessions about issues with Bitcoin Core self-hosting their CI&lt;/li&gt;
&lt;li&gt;self-hosted CI: I mentioned the current self-hosted CI problems in my last progress report. At CoreDev,
I wanted to figure out if it&amp;rsquo;s worthwhile for me to spend my time on it. After presenting and brainstorming
on this at CoreDev, I came to the conclusion that I want to cut back my time spent on CI stuff for now.
Other contributors agreed that the current self-hosted CI situation is not optimal and we started looking into
alternatives. As of writing these efforts have pretty much died down for now. See this issue for some of the discussion
&lt;a href="https://github.com/bitcoin/bitcoin/issues/31965"&gt;https://github.com/bitcoin/bitcoin/issues/31965&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code wise, I only ended up PRing &lt;a href="https://github.com/bitcoin/bitcoin/pull/31848"&gt;&lt;code&gt;test, tracing: don't use problematic bpf_usdt_readarg_p()&lt;/code&gt; #31848&lt;/a&gt; during the last three months.&lt;/li&gt;
&lt;li&gt;As mentioned in &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24"&gt;https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/24&lt;/a&gt;, I&amp;rsquo;ve been working on a PR for
predicitvly prefilling compact blocks with transactions a node would likely have to request otherwise. I noticed problems
in the fuzztest that covers this part of the code and have been speaking with the author of the fuzz test on how to best
resolve them. Hope to pick up work on this again soon!&lt;/li&gt;
&lt;li&gt;As usual, contributed my GUIX sigs for the v29.0 release (and release canidates): &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1626"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1626&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;REDACTED&lt;/li&gt;
&lt;li&gt;REDACTED&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="peer-observer"&gt;peer-observer&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/0xB10C/peer-observer"&gt;https://github.com/0xB10C/peer-observer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A tool used to monitor for attacks and anomalies by hooking into the Bitcoin Core tracepoints.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Until May 2025, MIT DCI was sponsoring six peer-observer monitoring nodes. REDACTED. Subsequently I moved the nodes to different servers.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/106"&gt;Add python tool to record getblocktxn msgs&lt;/a&gt;: I&amp;rsquo;ve used this tool for my research on compact block reconstruction. I posted preliminary results of it in the devling post linked above.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve also been mentoring someone interested in this project and helping him make his first contributions there&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="misc"&gt;Misc&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I opened PR &lt;a href="https://github.com/NixOS/nixpkgs/pull/398586"&gt;bitcoind: 28.1 -&amp;gt; 29.0 #398586&lt;/a&gt; to upgrade the Nix Bitcoin Core package to v29.0. This was more work than usual as the build system changed with the v29 release.&lt;/li&gt;
&lt;li&gt;For the &lt;a href="https://github.com/bitcoin-data/stale-blocks"&gt;bitcoin-data/stale-blocks&lt;/a&gt; dataset I maintain, we decided to add the full stale blocks there too as they might be interesting for future analysis. See &lt;a href="https://github.com/bitcoin-data/stale-blocks/issues/7"&gt;#7&lt;/a&gt;, &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/8"&gt;#8&lt;/a&gt;, and &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/11"&gt;#11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://github.com/0xB10C/nix/pull/98"&gt;https://github.com/0xB10C/nix/pull/98&lt;/a&gt;, I added systemd hardening measures for the NixOS modules of my tools. This should make them somewhat more sandboxed and isolated for everyone wanting to run them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-do-you-plan-to-work-on-next-quarter"&gt;&lt;em&gt;What do you plan to work on next quarter?&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;continue the work on my predictivly prefilling compact blocks Bitcoin Core PR&lt;/li&gt;
&lt;li&gt;continue working on peer-observer: This includes a presentation and possible an announcement blog post, but also implementing more data extractors like an RPC extractor (&lt;a href="https://github.com/0xB10C/peer-observer/issues/141"&gt;https://github.com/0xB10C/peer-observer/issues/141&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;getting mainnet-observer ready and an initial version out. And add more metrics and charts as needed.&lt;/li&gt;
&lt;li&gt;maintain and continue working on all the other small side projects I have&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next to work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;take some time off: I&amp;rsquo;ve been feeling a bit over-worked for the past half year and I think it&amp;rsquo;s healthy to take a few days off to enjoy summer and touch some grass. I&amp;rsquo;ve been very happy to see people like e.g. @bboerst looking into stratum stuff, which allows me to focus more on my other projects. I really hope other people find fun in doing some network monitoring too - sometimes I&amp;rsquo;ve been feeling overwhelmed by the all the projects that could and should be done, but aren&amp;rsquo;t.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Bitcoin Mining Centralization in 2025</title><link>https://b10c.me/blog/015-bitcoin-mining-centralization/</link><pubDate>Tue, 15 Apr 2025 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/015-bitcoin-mining-centralization/</guid><description>&lt;p&gt;This post explores Bitcoin Mining Centralization in 2025 by looking
at the hashrate share of the current five biggest mining pools.
It presents a Mining Centralization Index and updates it with the
assumed proxy pooling by AntPool &amp;amp; friends. It shows that Bitcoin
mining is highly centralized today, with only six pools mining more
than 95% of the blocks.&lt;/p&gt;
&lt;p&gt;In the current Bitcoin mining landscape, nearly the complete mining hashrate is
controlled by a few large mining pools, which produce the templates for the blocks.
These pools control which transactions they include in or exclude from their
blocks. This doesn&amp;rsquo;t hurt Bitcoin&amp;rsquo;s censorship resistance as long as these
mining pools don&amp;rsquo;t collude and decide to censor transactions. However,
it raises the question of how many distinct block template producers exist.
The 51%-attack, where a single miner or pool controls more than half the
hashrate and can out-compete all other miners by building on its own chain,
is well known. However, even with only 40% of the hashrate a pool has a ~50%
chance of out-competing all other pools for six blocks &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.
Are there pools with 40% of the hashrate today?
And in general, how centralized is Bitcoin mining today? Especially with the
assumed proxy pooling where smaller pools proxy the mining jobs of larger
pools while putting their own name into the coinbase transaction.&lt;/p&gt;
&lt;p&gt;This post explores Bitcoin mining centralization in two parts. The first part
looks at the mining pool information included in the coinbase transaction. The
second part takes the assumed proxy pooling into account. Both parts
show the current biggest mining pools and a mining centralization index.&lt;/p&gt;
&lt;h2 id="measuring-centralization-by-counting-coinbase-tags"&gt;Measuring Centralization by counting Coinbase tags&lt;/h2&gt;
&lt;p&gt;Mining pools usually leave an ASCII tag in their coinbase transactions. For
example, the mining pool F2Pool includes the tag &lt;code&gt;/F2Pool/&lt;/code&gt;. Additionally, mining
pools frequently reuse their coinbase output addresses. A dataset of these
identifiers can be used to find out which pool mined a block. For this
blog post, I&amp;rsquo;ve used the &lt;a href="https://github.com/bitcoin-data/mining-pools"&gt;bitcoin-data/mining-pools&lt;/a&gt; dataset. To measure
the network share, I&amp;rsquo;ve looked at the blocks per day per mining pool
divided by the total number of blocks per day.
The data shown is averaged with a 31-day moving average.&lt;/p&gt;
&lt;div id="top5-pools" style="width: 100%;height:500px;"&gt;
&lt;img src="https://b10c.me/data/blog/015-mining-centralization/top5-pools.png" style="background: #222;"&gt;
&lt;/div&gt;
&lt;script src="https://b10c.me/js/echarts.min.js" integrity="sha512-XSmbX3mhrD2ix5fXPTRQb2FwK22sRMVQTpBP2ac8hX7Dh/605hA2QDegVWiAvZPiXIxOV0CbkmUjGionDpbCmw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
async function fetchCSV(url) {
const res = await fetch(url);
const text = await res.text();
return parseCSV(text);
}
function parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h =&gt; h.trim());
return lines.slice(1).map(line =&gt; {
const values = line.split(',').map(v =&gt; v.trim());
return Object.fromEntries(headers.map((h, i) =&gt; [h, values[i]]));
});
}
function movingAverage(data, windowSize) {
const result = [];
for (let i = 0; i &lt; data.length; i++) {
if (i &lt; windowSize - 1) {
result.push(null);
continue;
}
const slice = data.slice(i - windowSize + 1, i + 1);
const sum = slice.reduce((a, b) =&gt; a + b, 0);
result.push(sum / windowSize);
}
return result;
}
async function loadAndRender() {
var chart = echarts.init(document.getElementById("top5-pools"));
let style = window.getComputedStyle(document.body)
let bodyTextColor = style.getPropertyValue('--body-color');
const [data] = await Promise.all([
fetchCSV("/data/blog/015-mining-centralization/top5pools.csv"),
]);
let pools = [];
let dates = [];
const dataMap = new Map();
data.forEach(row =&gt; {
if (row.date) {
dates.push(row.date);
}
let total = parseInt(row.total)
Object.keys(row).forEach((k) =&gt; {
if(k != "date" &amp;&amp; k != "total" &amp;&amp; !pools.includes(k)){
pools.push(k)
dataMap[k] = []
}
})
pools.forEach((p) =&gt; {
dataMap[p].push(
parseInt(row[p]) / total
)
})
});
var option = {
title: { text: 'Network share of the current top five mining pools', textStyle: {color: bodyTextColor}},
tooltip: { trigger: 'axis' },
legend: { top: 30, data: pools, textStyle: {color: bodyTextColor}},
xAxis: { data: dates, axisLabel: { textStyle: {color: bodyTextColor} } },
yAxis: {
axisLabel: { type: 'value', formatter: (value) =&gt; (value).toFixed(0) + "%" , textStyle: {color: bodyTextColor}},
},
dataZoom: [
{ type: 'inside' }
],
series: pools.map((p) =&gt; {
return {
name: p,
type: "line",
symbol: 'none',
data: movingAverage(dataMap[p], 31).map(v =&gt; v == null ? null : (v * 100).toFixed(1)),
}
}),
}
chart.setOption(option);
}
loadAndRender();
&lt;/script&gt;
&lt;p&gt;Currently, the five largest mining pools are Foundry (30%), AntPool (19%),
ViaBTC (14.5%), F2Pool (10%), and MARA Pool (5%). Out of these, MARA Pool is
the youngest pool, having started only in May 2021. Additionally, they are the
only private pool in the top five. At the end of 2020, Foundry started mining
its first blocks. After one year of operation, Foundry surpassed the other
mining pools and became the largest pool with about 17% of the network hashrate.
A bit more than another year later, in January 2023, Foundry reached 30% of the
hashrate and has remained at this level since. AntPool, the mining pool owned by
the ASIC manufacturer Bitmain, increased its hashrate from about 10% in
2020 to more than 25% in 2024 before dropping below 20% again at the end of
2024. Last year, ViaBTC overtook F2Pool in the race for the third place.&lt;/p&gt;
&lt;p&gt;With the largest two pools currently having over 50% of the hashrate,
followed by places three and four having another 25% together, 75% of the
hashrate is controlled by just four pools. To put this into perspective,
the following Mining Centralization Index can help.&lt;/p&gt;
&lt;div id="mining-centralization-index" style="width: 100%;height:500px;"&gt;
&lt;img src="https://b10c.me/data/blog/015-mining-centralization/mining-centralization-index.png" style="background: #222;"&gt;
&lt;/div&gt;
&lt;script src="https://b10c.me/js/echarts.min.js" integrity="sha512-XSmbX3mhrD2ix5fXPTRQb2FwK22sRMVQTpBP2ac8hX7Dh/605hA2QDegVWiAvZPiXIxOV0CbkmUjGionDpbCmw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
async function fetchCSV(url) {
const res = await fetch(url);
const text = await res.text();
return parseCSV(text);
}
function parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h =&gt; h.trim());
return lines.slice(1).map(line =&gt; {
const values = line.split(',').map(v =&gt; v.trim());
return Object.fromEntries(headers.map((h, i) =&gt; [h, values[i]]));
});
}
function movingAverage(data, windowSize) {
const result = [];
for (let i = 0; i &lt; data.length; i++) {
if (i &lt; windowSize - 1) {
result.push(null);
continue;
}
const slice = data.slice(i - windowSize + 1, i + 1);
const sum = slice.reduce((a, b) =&gt; a + b, 0);
result.push(sum / windowSize);
}
return result;
}
async function loadAndRender() {
var chart = echarts.init(document.getElementById("mining-centralization-index"));
let style = window.getComputedStyle(document.body)
let bodyTextColor = style.getPropertyValue('--body-color');
const [data] = await Promise.all([
fetchCSV("/data/blog/015-mining-centralization/miningpools-centralization-index.csv"),
]);
let pools = [
"top 6 pools",
"top 5 pools",
"top 4 pools",
"top 3 pools",
"top 2 pools",
];
let dates = [];
// only consider data after 2013-01-01
let threshold = new Date("2013-01-01");
const dataMap = new Map();
dataMap["top 2 pools"] = []
dataMap["top 3 pools"] = []
dataMap["top 4 pools"] = []
dataMap["top 5 pools"] = []
dataMap["top 6 pools"] = []
data.forEach(row =&gt; {
if (new Date(row.date) &lt; threshold) {
return
}
dates.push(row.date);
let total = parseInt(row.total)
let top1 = parseInt(row.top1)
let top2 = parseInt(row.top2)
let top3 = parseInt(row.top3)
let top4 = parseInt(row.top4)
let top5 = parseInt(row.top5)
let top6 = parseInt(row.top6)
dataMap["top 2 pools"].push((top1 + top2) / total)
dataMap["top 3 pools"].push((top1 + top2 + top3) / total)
dataMap["top 4 pools"].push((top1 + top2 + top3 + top4) / total)
dataMap["top 5 pools"].push((top1 + top2 + top3 + top4 + top5) / total)
dataMap["top 6 pools"].push((top1 + top2 + top3 + top4 + top5 + top6) / total)
});
var option = {
title: { text: 'Mining Centralization Index', textStyle: {color: bodyTextColor} },
tooltip: { trigger: 'axis' },
legend: { top: 30, data: pools, textStyle: {color: bodyTextColor}},
xAxis: { data: dates, axisLabel: { textStyle: {color: bodyTextColor} } },
yAxis: {
axisLabel: { type: 'value', formatter: (value) =&gt; (value).toFixed(0) + "%" , textStyle: {color: bodyTextColor}},
},
dataZoom: [
{ type: 'inside' }
],
series: pools.map((p) =&gt; {
return {
name: p,
type: "line",
symbol: 'none',
markLine: {
symbol: 'none', // remove arrows
label: {
show: true,
position: "insideStartBottom",
},
lineStyle: { color: 'lightgray', type: "dotted" },
data: [
{xAxis: '2017-05-15', label: {formatter: "min"}},
{xAxis: '2023-12-15', label: {formatter: "recent max"}}
]
},
data: movingAverage(dataMap[p], 31).map(v =&gt; v == null ? null : (v * 100).toFixed(1)),
}
}),
}
chart.setOption(option);
}
loadAndRender();
&lt;/script&gt;
&lt;p&gt;To measure the Bitcoin mining centralization over time, the Mining Centralization
Index shows the hashrate sum of the largest 2, 3, 4, 5, and 6 pools at each point
in time. Higher values mean more centralization.&lt;/p&gt;
&lt;p&gt;For example, in May 2017, the top 2 pools together had a hashrate of less than 30% and the top 6 pools had less than
65% together. Then, Bitcoin mining was the most decentralized it has ever been in its
pooled mining history. December 2023 is a strong contrast to this when more than 55% of
the hashrate was controlled by the top 2 pools and 90% was controlled by the top 6 pools.
Compared to the period between 2019 to 2022, where the top 2 pools had around 35% of the
network hashrate and the top 6 pools had about 75%, Bitcoin mining is currently a lot more
centralized.&lt;/p&gt;
&lt;h2 id="measuring-centralization-in-times-of-proxy-pools"&gt;Measuring Centralization in times of Proxy Pools&lt;/h2&gt;
&lt;p&gt;It has been &lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;observed&lt;/a&gt; that AntPool and multiple smaller mining pools send out very
similar block templates. It&amp;rsquo;s assumed that these smaller pools are proxy pools
for AntPool meaning they relay AntPools mining jobs but change the coinbase tags
and addresses to their own. This causes hashrate estimates based on coinbase tags
and addresses to become inaccurate. AntPool&amp;rsquo;s hashrate share is underreported as
the blocks are attributed to the smaller miners. However, AntPool and these smaller
miners, collectively referred to as &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo; can be counted together as one
big pool consisting of multiple pools. It can only be assumed which pools belong
to the &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo; group&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, and it&amp;rsquo;s not clear when they joined that group.&lt;/p&gt;
&lt;div id="antpool-and-friends" style="width: 100%;height:500px;"&gt;
&lt;img src="https://b10c.me/data/blog/015-mining-centralization/antpool-and-friends.png" style="background: #222;"&gt;
&lt;/div&gt;
&lt;script src="https://b10c.me/js/echarts.min.js" integrity="sha512-XSmbX3mhrD2ix5fXPTRQb2FwK22sRMVQTpBP2ac8hX7Dh/605hA2QDegVWiAvZPiXIxOV0CbkmUjGionDpbCmw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
async function fetchCSV(url) {
const res = await fetch(url);
const text = await res.text();
return parseCSV(text);
}
function parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h =&gt; h.trim());
return lines.slice(1).map(line =&gt; {
const values = line.split(',').map(v =&gt; v.trim());
return Object.fromEntries(headers.map((h, i) =&gt; [h, values[i]]));
});
}
function movingAverage(data, windowSize) {
const result = [];
for (let i = 0; i &lt; data.length; i++) {
if (i &lt; windowSize - 1) {
result.push(null);
continue;
}
const slice = data.slice(i - windowSize + 1, i + 1);
const sum = slice.reduce((a, b) =&gt; a + b, 0);
result.push(sum / windowSize);
}
return result;
}
async function loadAndRender() {
var chart = echarts.init(document.getElementById("antpool-and-friends"));
let style = window.getComputedStyle(document.body)
let bodyTextColor = style.getPropertyValue('--body-color');
const [data] = await Promise.all([
fetchCSV("/data/blog/015-mining-centralization/miningpools-antpool-and-friends.csv"),
]);
let pools = [];
let dates = [];
// only consider data after 2023-01-01 as older data might not be accurate
let threshold = new Date("2023-01-01");
const dataMap = new Map();
data.forEach(row =&gt; {
if (row.date) {
if (new Date(row.date) &lt; threshold) {
return
}
dates.push(row.date);
}
let total = parseInt(row.total)
Object.keys(row).forEach((k) =&gt; {
if(k != "date" &amp;&amp; k != "total" &amp;&amp; !pools.includes(k)){
pools.push(k)
dataMap[k] = []
}
})
pools.forEach((p) =&gt; {
dataMap[p].push(
parseInt(row[p]) / total
)
})
});
var option = {
title: { text: 'Hashrate of AntPool &amp; friends', textStyle: {color: bodyTextColor} },
tooltip: { trigger: 'axis' },
legend: { top: 30, data: pools, textStyle: {color: bodyTextColor}},
xAxis: { data: dates, axisLabel: { textStyle: {color: bodyTextColor} } },
yAxis: {
axisLabel: { type: 'value', formatter: (value) =&gt; (value).toFixed(0) + "%" , textStyle: {color: bodyTextColor}},
},
dataZoom: [
{ type: 'inside' }
],
series: pools.map((p) =&gt; {
return {
name: p,
type: "line",
lineStyle: {
type: p == "AntPool &amp; friends" ? "dashed" : "solid",
},
symbol: 'none',
data: movingAverage(dataMap[p], 31).map(v =&gt; v == null ? null : (v * 100).toFixed(1)),
}
}),
}
chart.setOption(option);
}
loadAndRender();
&lt;/script&gt;
&lt;p&gt;This chart assumes all pools considered a part of &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo; were a
part of it starting in 2023. Since this is not proven to be the case, assume a
slight over-reporting of the hashrate share throughout 2023. However, the data
should be more accurate for 2024, where &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo; made up about 40%
of the network hashrate.
While AntPool &amp;amp; friends had a higher hashrate share than Foundry for the last
two years, Foundry seems to have gained 5% while AntPool &amp;amp; friends lost the same
share during early 2025. This trend seems to be reversing with AntPool &amp;amp; friends
growing again.&lt;/p&gt;
&lt;p&gt;Given that AntPool &amp;amp; friends have had a 10% to 15% higher hashrate share than
assumed in the previous Mining Centralization Index chart, revisiting the index
is worthwhile.&lt;/p&gt;
&lt;div id="mining-centralization-index-with-antpool" style="width: 100%;height:500px;"&gt;
&lt;img src="https://b10c.me/data/blog/015-mining-centralization/mining-centralization-index-with-antpool.png" style="background: #222;"&gt;
&lt;/div&gt;
&lt;script src="https://b10c.me/js/echarts.min.js" integrity="sha512-XSmbX3mhrD2ix5fXPTRQb2FwK22sRMVQTpBP2ac8hX7Dh/605hA2QDegVWiAvZPiXIxOV0CbkmUjGionDpbCmw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
async function fetchCSV(url) {
const res = await fetch(url);
const text = await res.text();
return parseCSV(text);
}
function parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h =&gt; h.trim());
return lines.slice(1).map(line =&gt; {
const values = line.split(',').map(v =&gt; v.trim());
return Object.fromEntries(headers.map((h, i) =&gt; [h, values[i]]));
});
}
function movingAverage(data, windowSize) {
const result = [];
for (let i = 0; i &lt; data.length; i++) {
if (i &lt; windowSize - 1) {
result.push(null);
continue;
}
const slice = data.slice(i - windowSize + 1, i + 1);
const sum = slice.reduce((a, b) =&gt; a + b, 0);
result.push(sum / windowSize);
}
return result;
}
async function loadAndRender() {
var chart = echarts.init(document.getElementById("mining-centralization-index-with-antpool"));
let style = window.getComputedStyle(document.body)
let bodyTextColor = style.getPropertyValue('--body-color');
const [data] = await Promise.all([
fetchCSV("/data/blog/015-mining-centralization/miningpools-centralization-index-with-proxy-pools.csv"),
]);
let pools = [
"top 6 pools",
"top 5 pools",
"top 4 pools",
"top 3 pools",
"top 2 pools",
];
let dates = [];
// only consider data after 2023-01-01
let threshold = new Date("2023-01-01");
const dataMap = new Map();
dataMap["top 2 pools"] = []
dataMap["top 3 pools"] = []
dataMap["top 4 pools"] = []
dataMap["top 5 pools"] = []
dataMap["top 6 pools"] = []
data.forEach(row =&gt; {
if (new Date(row.date) &lt; threshold) {
return
}
dates.push(row.date);
let total = parseInt(row.total)
let top1 = parseInt(row.top1)
let top2 = parseInt(row.top2)
let top3 = parseInt(row.top3)
let top4 = parseInt(row.top4)
let top5 = parseInt(row.top5)
let top6 = parseInt(row.top6)
dataMap["top 2 pools"].push((top1 + top2) / total)
dataMap["top 3 pools"].push((top1 + top2 + top3) / total)
dataMap["top 4 pools"].push((top1 + top2 + top3 + top4) / total)
dataMap["top 5 pools"].push((top1 + top2 + top3 + top4 + top5) / total)
dataMap["top 6 pools"].push((top1 + top2 + top3 + top4 + top5 + top6) / total)
});
var option = {
title: { text: 'Mining Centralization Index with AntPool &amp; friends', textStyle: {color: bodyTextColor} },
tooltip: { trigger: 'axis' },
legend: { top: 30, data: pools, textStyle: {color: bodyTextColor}},
xAxis: { data: dates, axisLabel: { textStyle: {color: bodyTextColor} } },
yAxis: {
axisLabel: { type: 'value', formatter: (value) =&gt; (value).toFixed(0) + "%" , textStyle: {color: bodyTextColor}},
},
dataZoom: [
{ type: 'inside' }
],
series: pools.map((p) =&gt; {
return {
name: p,
type: "line",
lineStyle: {
type: p == "AntPool &amp; friends" ? "dashed" : "solid",
},
symbol: 'none',
data: movingAverage(dataMap[p], 31).map(v =&gt; v == null ? null : (v * 100).toFixed(1)),
}
}),
}
chart.setOption(option);
}
loadAndRender();
&lt;/script&gt;
&lt;p&gt;The Mining Centralization Index including AntPool &amp;amp; friends shows that over the last two years,
AntPool &amp;amp; friends and Foundry together controlled 60% to 70% of the hashrate. However,
even worse, 96% to 99% of the blocks were mined by only six mining pools. These numbers
indicate that Bitcoin mining is heavily centralized around a few template-producing pools.&lt;/p&gt;
&lt;p&gt;Bitcoin needs smaller pools like MARA Pool with 5% hashrate. Some large US mining
companies could probably leave Foundry and start solo mining given their hashrate. Additionally,
shifting hashrate to pools like Ocean (&amp;lt;1%) or DEMAND (0%), where miners build their own template,
helps make Bitcoin mining more decentralized. More individuals home-mining with small miners
help too, however, the home-mining hashrate is currently still negligible compared to the industrial
hashrate.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To answer the questions from the introduction:&lt;/p&gt;
&lt;h5 id="how-many-distinct-block-template-producers-exist"&gt;How many distinct block template producers exist?&lt;/h5&gt;
&lt;p&gt;We can&amp;rsquo;t know, but we know that six block template producers
mine more than 95% of the blocks.&lt;/p&gt;
&lt;h5 id="are-there-pools-with-40-of-the-hashrate-today"&gt;Are there pools with 40% of the hashrate today?&lt;/h5&gt;
&lt;p&gt;No, but it&amp;rsquo;s assumed that AntPool &amp;amp; friends controlled about 40% of the
network hashrate in 2023 and throughout the first half of 2024. Foundry
had about 35% in early 2025. Currently, AntPool &amp;amp; Foundry each have more
than 30% of the hashrate.&lt;/p&gt;
&lt;h5 id="how-centralized-is-bitcoin-mining-today"&gt;How centralized is Bitcoin mining today?&lt;/h5&gt;
&lt;p&gt;According to the Mining Centralization Index, Bitcoin mining was most
decentralized for a short period in May 2017. 2019 to 2022 was also a
good period. Starting in 2023, Bitcoin mining has become more and more
centralized, especially with large pools like Foundry and proxy pooling
a la AntPool &amp;amp; friends.&lt;/p&gt;
&lt;hr&gt;
&lt;div id="antpool-and-friends-pie-chart" style="width: 100%;height:500px;"&gt;
&lt;img src="https://b10c.me/data/blog/015-mining-centralization/antpool-and-friends-pie-chart.png" style="background: #222;"&gt;
&lt;/div&gt;
&lt;script src="https://b10c.me/js/echarts.min.js" integrity="sha512-XSmbX3mhrD2ix5fXPTRQb2FwK22sRMVQTpBP2ac8hX7Dh/605hA2QDegVWiAvZPiXIxOV0CbkmUjGionDpbCmw==" crossorigin="anonymous" referrerpolicy="no-referrer"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
async function fetchCSV(url) {
const res = await fetch(url);
const text = await res.text();
return parseCSV(text);
}
function parseCSV(text) {
const lines = text.trim().split('\n');
const headers = lines[0].split(',').map(h =&gt; h.trim());
return lines.slice(1).map(line =&gt; {
const values = line.split(',').map(v =&gt; v.trim());
return Object.fromEntries(headers.map((h, i) =&gt; [h, values[i]]));
});
}
function movingAverage(data, windowSize) {
const result = [];
for (let i = 0; i &lt; data.length; i++) {
if (i &lt; windowSize - 1) {
result.push(null);
continue;
}
const slice = data.slice(i - windowSize + 1, i + 1);
const sum = slice.reduce((a, b) =&gt; a + b, 0);
result.push(sum / windowSize);
}
return result;
}
async function loadAndRender() {
var chart = echarts.init(document.getElementById("antpool-and-friends-pie-chart"));
let style = window.getComputedStyle(document.body)
let bodyTextColor = style.getPropertyValue('--body-color');
const [data] = await Promise.all([
fetchCSV("/data/blog/015-mining-centralization/miningpools-antpool-and-friends.csv"),
]);
let pools = [];
let dates = [];
// only consider data after 2023-01-01 as older data might not be accurate
let threshold = new Date("2023-01-01");
const dataMap = new Map();
data.forEach(row =&gt; {
if (row.date) {
if (new Date(row.date) &lt; threshold) {
return
}
dates.push(row.date);
}
let total = parseInt(row.total)
Object.keys(row).forEach((k) =&gt; {
if(k != "date" &amp;&amp; k != "total" &amp;&amp; !pools.includes(k)){
pools.push(k)
dataMap[k] = []
}
})
pools.forEach((p) =&gt; {
dataMap[p].push(
parseInt(row[p]) / total
)
})
});
let AntPoolAndFriends = (movingAverage(dataMap["AntPool &amp; friends"], 31).slice(-1)[0] * 100).toFixed(1)
let Foundry = (movingAverage(dataMap["Foundry USA"], 31).slice(-1)[0] * 100).toFixed(1)
let ViaBTC = (movingAverage(dataMap["ViaBTC"], 31).slice(-1)[0] * 100).toFixed(1)
let F2Pool = (movingAverage(dataMap["F2Pool"], 31).slice(-1)[0] * 100).toFixed(1)
let MARAPool = (movingAverage(dataMap["MARA Pool"], 31).slice(-1)[0] * 100).toFixed(1)
let SpiderPool = 3.9
let other = (100 - AntPoolAndFriends - Foundry - ViaBTC - F2Pool - MARAPool - SpiderPool).toFixed(1)
var option = {
title: {
top: 20,
text: "Bitcoin Hashrate Distribution in April 2025",
subtext: "with proxy pools taken into account",
left: 'center',
textStyle: {color: "white"},
},
tooltip: {
trigger: 'item'
},
backgroundColor: '#1d1f31',
series: [
{
name: 'Hashrate share',
type: 'pie',
radius: ['15%', '60%'],
itemStyle: {
borderColor: '#000',
borderWidth: 0.5
},
data: [
{ value: AntPoolAndFriends, name: `AntPool &amp; friends\n${AntPoolAndFriends}%`, itemStyle: {color: "#D81B60"}},
{ value: Foundry, name: `Foundry\n${Foundry}%`, itemStyle: {color: "#8E24AA"}},
{ value: ViaBTC, name: `ViaBTC\n${ViaBTC}%`, itemStyle: {color: "#5E35B1"}},
{ value: F2Pool, name: `F2Pool\n${F2Pool}%`, itemStyle: {color: "#3949AB"}},
{ value: MARAPool, name: `MARA Pool\n${MARAPool}%`, itemStyle: {color: "#1E88E5"}},
{ value: SpiderPool, name: `SpiderPool\n${SpiderPool}%`, itemStyle: {color: "#039BE5"}},
{ value: other, name: `other\n${other}%`, itemStyle: {color: "#6b6b6b"}}
],
}
]
};
chart.setOption(option);
}
loadAndRender();
&lt;/script&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://bitcoinops.org/en/tools/reorg-calculator/"&gt;https://bitcoinops.org/en/tools/reorg-calculator/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;As of writing, the following pools are assumed to be part of the AntPool &amp;amp; friends group:
AntPool,
Poolin (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;, &lt;a href="https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/"&gt;invalid jobs&lt;/a&gt;, &lt;a href="https://x.com/boerst/status/1899102559161520497"&gt;invalid jobs 2&lt;/a&gt;),
CloverPool (formerly BTC.com, &lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;, &lt;a href="https://x.com/boerst/status/1899102559161520497"&gt;invalid jobs 2&lt;/a&gt;),
Braiins (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;, &lt;a href="https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/"&gt;invalid jobs&lt;/a&gt;),
Ultimus Pool (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;, &lt;a href="https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/"&gt;invalid jobs&lt;/a&gt;, &lt;a href="https://x.com/boerst/status/1899102559161520497"&gt;invalid jobs 2&lt;/a&gt;),
Binance Pool (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;, &lt;a href="https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/"&gt;invalid jobs&lt;/a&gt;),
SecPool (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;),
SigmaPool (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;),
Rawpool (&lt;a href="https://x.com/boerst/status/1899102559161520497"&gt;invalid jobs 2&lt;/a&gt;),
Luxor (&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;template similarities&lt;/a&gt;),
Mining Squared (&lt;a href="https://x.com/boerst/status/1907036334784458974"&gt;mining-squared&lt;/a&gt;).
SpiderPool is currently not considered an AntPool proxy due to not having enough evidence for it yet.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Invalid mining jobs by AntPool &amp; friends during forks</title><link>https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/14-antpool-and-friends-invalid-mining-jobs/</guid><description>&lt;p&gt;Looking deeper into @boerst&amp;rsquo;s recent observation about invalid mining jobs by AntPool &amp;amp; friends to discuss his
hypothesis about &amp;ldquo;selfish mining&amp;rdquo; and &amp;ldquo;glitchy template code&amp;rdquo;. I conclude that it&amp;rsquo;s probably a bug in AntPool&amp;rsquo;s
coinbase creation code and agree with @boerst&amp;rsquo;s conclusion that this is another good data point for proxy pooling
of &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://x.com/boerst"&gt;@boerst&lt;/a&gt;, the developer behind the &lt;a href="https://stratum.work"&gt;stratum.work&lt;/a&gt; mining pool job monitoring tool recently
&lt;a href="https://x.com/boerst/status/1899102559161520497"&gt;observed&lt;/a&gt; that AntPool, CloverPool, Ultimus, Rawpool, and Poolin published mining jobs for invalid blocks. The
pools publish empty mining jobs only including the coinbase transaction but have a coinbase output value higher
than the subsidy. These jobs are invalid as they attempt to create more new coins than allowed. &lt;a href="https://x.com/boerst"&gt;@boerst&lt;/a&gt; saw these
jobs being published on March 1st, 2025 and noted that the previous block hash &lt;code&gt;..acdeb985ea&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;
in these jobs isn&amp;rsquo;t known to the network. He speculates that these bad jobs could be caused by a &amp;ldquo;botched selfish
mining attempt&amp;rdquo; or &amp;ldquo;glitchy template code&amp;rdquo;. Additionally, he notes that this makes it obvious that these pools
are related.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;I added historical data to &lt;a href="https://t.co/GCd3WfVtZW"&gt;https://t.co/GCd3WfVtZW&lt;/a&gt; over the weekend.&lt;br&gt;&lt;br&gt;Looking at some really interesting block templates sent out for height 885797 by Antpool, CloverPool, Ultimus, Rawpool, and Poolin.&lt;a href="https://t.co/0oNZepy6HA"&gt;https://t.co/0oNZepy6HA&lt;/a&gt;&lt;br&gt;&lt;br&gt;These are interesting because:&lt;br&gt;&lt;br&gt;- Empty merkle… &lt;a href="https://t.co/QOw7WqZRGW"&gt;pic.twitter.com/QOw7WqZRGW&lt;/a&gt;&lt;/p&gt;&amp;mdash; boerst (@boerst) &lt;a href="https://twitter.com/boerst/status/1899102559161520497?ref_src=twsrc%5Etfw"&gt;March 10, 2025&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h1 id="observations"&gt;Observations&lt;/h1&gt;
&lt;p&gt;Looking at historical data I collected with an &lt;a href="https://stratum.miningpool.observer"&gt;instance&lt;/a&gt; of my &lt;a href="https://github.com/0xB10C/stratum-observer"&gt;stratum-observer&lt;/a&gt; tool, I noticed three similar
cases in December 2024 next to the occurrence on March 1st, 2025. While I don&amp;rsquo;t collect jobs from CloverPool and
Rawpool, I saw jobs from Braiins and Binance Pool that have the same characteristics. I also noticed that these
only occur when AntPool and friends are involved in a block-race during a fork.&lt;/p&gt;
&lt;h2 id="while-building-block-873559"&gt;While building block 873559&lt;/h2&gt;
&lt;p&gt;On December 6th, 2024, AntPool, Braiins, Poolin, Binance Pool, and Ultimus published invalid jobs building block 873559
on the previous block hash &lt;code&gt;..2ba3bd04af&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. This previous block mined
by AntPool ended up losing in a block-race against a ViaBTC block. While a coinbase output value of 3.125 BTC would have
been allowed, Braiins and Ultimus set a coinbase output value of 3.28466550 BTC (0.1596655 BTC more than allowed) while
AntPool, Binance Pool, and Poolin had a 5606 sat higher coinbase output value of 3.28460944 BTC (+0.1596094 BTC) in
their mining jobs.&lt;/p&gt;
&lt;p&gt;I received the first jobs building block 873559 from Poolin, Binance Pool, and F2Pool at nearly the same time. While
Poolin and Binance Pool were building on AntPool&amp;rsquo;s block, F2Pool was building on ViaBTC&amp;rsquo;s block. The initial jobs by
Binance Pool and Poolin had an empty template (no Merkle branches) and a correct coinbase output value of 3.125 BTC.
Similar, valid mining jobs arrived for AntPool, Braiins, and Ultimus. However, 400ms after receiving the initial jobs by
Binance Pool and Poolin, AntPool, and the other pools sent new, invalid jobs with a too-high coinbase output value. The
incorrect coinbase output values excatly match the coinbase output values of the last jobs mining on the previous
block indicating the output value was cached from the previous block. A detailed look at the AntPool coinbase
transaction reveals that the invalid coinbase and the coinbase from the previous block job only differ in the merge-mining
commitments (OP_RETURN outputs and the Namecoin commitment in the coinbase script) and the BIP 30 block height.
After 17 seconds AntPool sent a non-empty job with a correct coinbase output value.&lt;/p&gt;
&lt;p&gt;The relevant jobs are listed in this table.&lt;/p&gt;
&lt;table class="table table-sm "&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;time&lt;/th&gt;
&lt;th&gt;pool&lt;/th&gt;
&lt;th&gt;Merkle branches&lt;/th&gt;
&lt;th&gt;value in BTC&lt;/th&gt;
&lt;th&gt;building block&lt;/th&gt;
&lt;th&gt;prev hash&lt;/th&gt;
&lt;th&gt;note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:09.49&lt;/td&gt;
&lt;td&gt;AntPool&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;3.28460944&lt;/td&gt;
&lt;td&gt;873558&lt;/td&gt;
&lt;td&gt;..8be5b7a176&lt;/td&gt;
&lt;td&gt;last job for prev block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:19.90&lt;/td&gt;
&lt;td&gt;Poolin&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3.12500000&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..2ba3bd04af&lt;/td&gt;
&lt;td&gt;first new jobs for 873559&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:19.90&lt;/td&gt;
&lt;td&gt;Binance Pool&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3.12500000&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..2ba3bd04af&lt;/td&gt;
&lt;td&gt;mining on AntPool&amp;rsquo;s block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:19.90&lt;/td&gt;
&lt;td&gt;F2Pool&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3.12500000&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..ea5ba4af6a&lt;/td&gt;
&lt;td&gt;mining on ViaBTC&amp;rsquo;s block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:20.01&lt;/td&gt;
&lt;td&gt;AntPool&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3.12500000&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..2ba3bd04af&lt;/td&gt;
&lt;td&gt;valid, empty AntPool job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:20.31&lt;/td&gt;
&lt;td&gt;AntPool&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3.28460944⚠️&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..2ba3bd04af&lt;/td&gt;
&lt;td&gt;invalid AntPool job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-06 22:34:37.43&lt;/td&gt;
&lt;td&gt;AntPool&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;3.24736182&lt;/td&gt;
&lt;td&gt;873559&lt;/td&gt;
&lt;td&gt;..2ba3bd04af&lt;/td&gt;
&lt;td&gt;valid, non-empty AntPool job after 17s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="while-building-block-874037"&gt;While building block 874037&lt;/h2&gt;
&lt;p&gt;On December 10th, 2024, only AntPool, Binance Pool, and Poolin published an invalid job for block 874037 on AntPool&amp;rsquo;s
block &lt;code&gt;..e581c9c12f&lt;/code&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; during a block-race with F2Pool&amp;rsquo;s
&lt;code&gt;..07a3cdc451&lt;/code&gt;&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. AntPool&amp;rsquo;s block ended up winning while F2Pools
block became stale. Braiins and Ultimus Pool didn&amp;rsquo;t publish invalid blocks. The invalid jobs had a coinbase output
value of 3.17199711 BTC which again exactly matches the coinbase output value of the last job of the previous block.
The coinbase transactions again only differ in the merge-mining commitments and the BIP30 block height. It took
AntPool about 16 seconds before they sent a valid job after their invalid job.&lt;/p&gt;
&lt;h2 id="while-building-block-875590"&gt;While building block 875590&lt;/h2&gt;
&lt;p&gt;On December 20th, 2024, AntPool, Braiins, Poolin, Binance Pool, and Ultimus published a job building block 875590
on the AntPool&amp;rsquo;s block &lt;code&gt;..b905ac264e&lt;/code&gt;&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; during a block-race with ViaBTC&amp;rsquo;s &lt;code&gt;..b911cbd12c&lt;/code&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;
which ended up winning. AntPool, Binance Pool, and Poolin had a coinbase transaction with a value of 3.25665133 BTC
(0.13165133 BTC too much), and the coinbase transaction of Braiins and Ultimus had a value of 3.25664121 BTC
(0.13164121 BTC too much). The coinbase output values again exactly match the values of the last job of the previous
block. After 22 seconds, AntPool sent out a valid mining job.&lt;/p&gt;
&lt;h2 id="while-building-block-885797"&gt;While building block 885797&lt;/h2&gt;
&lt;p&gt;On March 1st, 2025, AntPool, Braiins, Poolin, Binance Pool, and Ultimus published invalid jobs building block 885797
on the unknown block &lt;code&gt;..acdeb985ea&lt;/code&gt;&lt;sup id="fnref1:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; which was in a block-race
with a block mined by Foundry. The jobs by Braiins and Ultimus had an invalid coinbase output value of 3.16436527 BTC
while AntPool, Poolin, and Ultimus had a value of 3.14557552 BTC. This time, the coinbase output value of the last
AntPool job for the previous block was 3.16295187 BTC which doesn&amp;rsquo;t match with either of the invalid jobs. For this
block, AntPool didn&amp;rsquo;t send out a valid job and even refreshed the invalid job multiple times until Foundry found
block 885797 about 35 seconds after 885796 had been found. This means AntPool and the other pools tried to mine an
invalid block for about 35 seconds.&lt;/p&gt;
&lt;p&gt;The unknown block &lt;code&gt;..acdeb985ea&lt;/code&gt;&lt;sup id="fnref2:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; may be a block by AntPool, Braiins, Poolin, Binance Pool, or Ultimus
that didn&amp;rsquo;t propagate well during a block-race with the Foundry block.&lt;/p&gt;
&lt;h1 id="discussion"&gt;Discussion&lt;/h1&gt;
&lt;p&gt;Based on the observations, the behavior of AntPool &amp;amp; friends can be summarized as follows:
On a new block, AntPool and friends send a job for an empty block. If they detect that one of AntPool&amp;rsquo;s blocks is
in a block-race with another block, they send an empty mining job with a coinbase output value of or similar to
their last job for the previous block. At some point, they will issue a new, valid job with a correct coinbase output
value. Based on the four observations, this usually takes more than 15 seconds.&lt;/p&gt;
&lt;p&gt;This behavior doesn&amp;rsquo;t seem like this is a selfish-mining attempt. The three blocks in December propagated through
the network and in 874037 even arrived before F2Pool&amp;rsquo;s block. The block in March might not have propagated well due
to being found slightly after the Foundry block 885796.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://x.com/boerst"&gt;@boerst&lt;/a&gt; speculates that the invalid jobs could be related to &amp;ldquo;glitchy template code&amp;rdquo;. Based on the observations,
I assume it&amp;rsquo;s related to the coinbase building code as opposed to the template building code. In three cases, the
coinbase output value of the last job of the previous block is reused. This leads to the assumption that it&amp;rsquo;s
cached somewhere and in some way not updated or reset in the block-race scenario. On March 1st, this assumption
doesn&amp;rsquo;t hold as the AntPool coinbase value of the last job of the previous block doesn&amp;rsquo;t match the value in the
invalid job. However, an explanation could be that the coinbase value for the previous block had been updated
internally, but the corresponding mining job hadn&amp;rsquo;t been published yet.&lt;/p&gt;
&lt;p&gt;While I don&amp;rsquo;t track the the same pools as boerst, I agree with his assumption that this weird and incorrect
behavior once more confirms that these pools are operated by the same entity. It might be time to rename AntPool
and its proxy pools Braiins, Poolin, Binance Pool, and Ultimus (and probably more) to &amp;ldquo;AntPool &amp;amp; friends&amp;rdquo;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;A list of all invalid jobs I received can be found below:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;stratumobserver=&amp;gt; select pool, timestamp, coinbase_value, coinbase_height, cardinality(merkle_branches) as merkle_branches_count, header_prev_hash from job_updates where coinbase_value &amp;gt; 312500000 and cardinality(merkle_branches) = 0 order by timestamp desc;
pool | timestamp | coinbase_value | coinbase_height | merkle_branches_count | header_prev_hash
--------------+----------------------------+----------------+-----------------+-----------------------+------------------------------------------------------------------
Braiins | 2025-03-01 02:32:48.256093 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2025-03-01 02:32:47.95568 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Ultimus | 2025-03-01 02:32:47.934396 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:47.834271 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:47.76899 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Braiins | 2025-03-01 02:32:38.442376 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2025-03-01 02:32:38.342103 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Ultimus | 2025-03-01 02:32:38.220214 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:38.120078 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:38.054957 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Braiins | 2025-03-01 02:32:29.622593 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2025-03-01 02:32:29.322023 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Ultimus | 2025-03-01 02:32:29.19731 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:29.097238 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:29.036254 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2025-03-01 02:32:28.020701 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:27.69472 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:27.634525 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Braiins | 2025-03-01 02:32:26.719377 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Braiins | 2025-03-01 02:32:26.218713 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Ultimus | 2025-03-01 02:32:26.19205 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2025-03-01 02:32:25.918188 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Ultimus | 2025-03-01 02:32:25.891502 | 316436527 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:25.590893 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:25.532066 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:25.431804 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Binance Pool | 2025-03-01 02:32:25.390627 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
Poolin | 2025-03-01 02:32:25.331699 | 314557552 | 885797 | 0 | 00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea
AntPool | 2024-12-20 12:45:27.914581 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
AntPool | 2024-12-20 12:45:09.963704 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Binance Pool | 2024-12-20 12:45:09.865582 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Poolin | 2024-12-20 12:45:09.865545 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Ultimus | 2024-12-20 12:45:09.86551 | 325664121 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Braiins | 2024-12-20 12:45:09.817793 | 325664121 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Binance Pool | 2024-12-20 12:45:09.36458 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
AntPool | 2024-12-20 12:45:09.362357 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Braiins | 2024-12-20 12:45:09.317215 | 325664121 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Poolin | 2024-12-20 12:45:09.264466 | 325665133 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
Ultimus | 2024-12-20 12:45:09.16438 | 325664121 | 875590 | 0 | 0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e
AntPool | 2024-12-10 01:33:10.167938 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:33:10.008015 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:33:09.915384 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
AntPool | 2024-12-10 01:32:57.246391 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:57.088351 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:56.998516 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
AntPool | 2024-12-10 01:32:45.129636 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:44.967986 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:44.880904 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:44.667515 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:44.580475 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
AntPool | 2024-12-10 01:32:37.120691 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:36.969376 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:36.955785 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
AntPool | 2024-12-10 01:32:31.613235 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:31.447633 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:31.360356 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
AntPool | 2024-12-10 01:32:31.11264 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Binance Pool | 2024-12-10 01:32:30.946879 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Poolin | 2024-12-10 01:32:30.859647 | 317199711 | 874037 | 0 | 0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f
Braiins | 2024-12-06 22:34:21.411346 | 328466550 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Ultimus | 2024-12-06 22:34:20.910805 | 328466550 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
AntPool | 2024-12-06 22:34:20.810713 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Poolin | 2024-12-06 22:34:20.710726 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Braiins | 2024-12-06 22:34:20.710714 | 328466550 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Binance Pool | 2024-12-06 22:34:20.710682 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
AntPool | 2024-12-06 22:34:20.710616 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Binance Pool | 2024-12-06 22:34:20.610491 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Poolin | 2024-12-06 22:34:20.510439 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Ultimus | 2024-12-06 22:34:20.410317 | 328466550 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
AntPool | 2024-12-06 22:34:20.310274 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Binance Pool | 2024-12-06 22:34:20.210146 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Poolin | 2024-12-06 22:34:20.110058 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Binance Pool | 2024-12-06 22:34:20.009977 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
Poolin | 2024-12-06 22:34:20.009938 | 328460944 | 873559 | 0 | 00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af
(74 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;00000000000000000001509b28e96d4eba8508a8885e78dc83b60cacdeb985ea&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;00000000000000000000ba2ab3a02b9de883cd7368bf57da3e04cd2ba3bd04af&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;0000000000000000000026da3d0aabc84cc20868a847f629b722a1e581c9c12f&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;00000000000000000002829ecf6967a9729412446dd571de06e02007a3cdc451&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;0000000000000000000005eee6163885d621cd0b4e999630d00433b905ac264e&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;00000000000000000000d9940eb168eb3de849ee1d55da2b15f79db911cbd12c&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>OpenSats Work-Log 4</title><link>https://b10c.me/funding/2025-opensats-report-4/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2025-opensats-report-4/</guid><description>&lt;p&gt;This is a copy of the 4th work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="what-did-you-work-on"&gt;&lt;em&gt;What did you work on?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications"&gt;Publications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;My mininpool-observer project detected 15 OFAC sanctioned transactions missing from blocks. After analyzing them to rule out false-positives, I concluded that F2Pool is filtering transactions with OFAC sanctioned addresses again: &lt;a href="https://b10c.me/observations/13-missing-sanctioned-transactions-2024-12/"&gt;https://b10c.me/observations/13-missing-sanctioned-transactions-2024-12/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I was asked by a fellow developer if I happen to have any insights into Bitcoin Core orphanage overflows. To make them available to everyone, I posted them to delving: &lt;a href="https://delvingbitcoin.org/t/stats-on-orphanage-overflows/1421"&gt;Stats on orphanage overflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I updated my stats on compact block reconstructions on delving with the newest data. This sparked new discussion and interest from other developers: &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/5"&gt;https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bitcoin-core-self-hosted-ci"&gt;Bitcoin Core self-hosted CI&lt;/h3&gt;
&lt;p&gt;Inspired by discussions at the last Bitcoin CoreDev meeting in fall 2024, I started looking into a NixOS based configuration for the Bitcoin Core self-hosted CI runners. At the time, I discovered that the self-hosted runners were running under a privileged user, which could easily stop/vandalize/otherwise negatively affect the underlying machine. CI tasks weren&amp;rsquo;t isolated from the machine and from other tasks. Note that a CI is basically one of your servers where you give someone from the internet Remote-Code-Execution access. Additionally, the CI token could easily be leaked, which allowed anyone to spawn new, and potentially malicious CI runners for the Bitcoin Core project.&lt;/p&gt;
&lt;p&gt;This made is it worthwhile to spend a bit of time looking into a potential CI runner setup that is more secure, properly isolates CI tasks, and doesn&amp;rsquo;t leak the CI token. By choosing NixOS, the CI runners can be configured once as infrastructure-as-code, and then easily deterministically replicated across multiple hosts.&lt;/p&gt;
&lt;p&gt;To isolate individual CI jobs from each other, I choose to run a ephemeral QEMU VM for each job. For this, the cirrus-ci runner used by Bitcoin Core needs to stop after it completed a single job (ephemeral mode). I opened a PR for this, but sadly, there hasn&amp;rsquo;t been much review activity by the Cirrus-CI folks: &lt;a href="https://github.com/cirruslabs/cirrus-cli/pull/813"&gt;https://github.com/cirruslabs/cirrus-cli/pull/813&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The hardest part is to do caching of CI build inputs in ephemeral runners. The current Bitcoin Core CI runners aren&amp;rsquo;t ephemeral because caching dependency sources and built dependency artifacts, docker base images and task specific docker, previous releases, and ccache artifacts is important for both CI performance and resilience against e.g. network problems or rate-limiting. Managing these across CI jobs can be challenging, especially when a single CI job shouldn&amp;rsquo;t be able to clear the cache. I&amp;rsquo;ve written down some notes about this in &lt;a href="https://github.com/bitcoin/bitcoin/issues/30852#issuecomment-2558198430"&gt;https://github.com/bitcoin/bitcoin/issues/30852#issuecomment-2558198430&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The project is about 85% done, but still requires some work to get to a production ready-level. Based on discussions with other Bitcoin Core developers, the current CI situation might have been improved since last fall: CI jobs don&amp;rsquo;t run as privileged user anymore and other safeguards have been put into place. The plan is to present my CI setup at the upcoming CoreDev and evaluate if it makes sense to invest more time and energy into this project in the short-term.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve published the CI-runner setup in &lt;a href="https://github.com/0xB10C/bitcoin-core-cirrus-runner"&gt;https://github.com/0xB10C/bitcoin-core-cirrus-runner&lt;/a&gt; and my infrastructure in &lt;a href="https://github.com/0xB10C/bitcoin-core-cirrus-runner-infra"&gt;https://github.com/0xB10C/bitcoin-core-cirrus-runner-infra&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;During development, I noticed that it is time consuming to manually parse the Bitcoin Core CI logs. To help during my development and to provide stats for the Bitcoin Core project, I&amp;rsquo;ve build a tool and website that parses and gives an overview over the recent tasks. The repository can be found at &lt;a href="https://github.com/0xB10C/bitcoin-core-ci-stats"&gt;https://github.com/0xB10C/bitcoin-core-ci-stats&lt;/a&gt; and the website here &lt;a href="https://0xb10c.github.io/bitcoin-core-ci-stats/"&gt;https://0xb10c.github.io/bitcoin-core-ci-stats/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the Bitcoin Core side, I initially proposed &lt;a href="https://github.com/bitcoin/bitcoin/pull/31377"&gt;https://github.com/bitcoin/bitcoin/pull/31377&lt;/a&gt;, which was then superseded by &lt;a href="https://github.com/bitcoin/bitcoin/pull/31545"&gt;https://github.com/bitcoin/bitcoin/pull/31545&lt;/a&gt;. This makes the caching of the Bitcoin Core CI docker images possible.&lt;/p&gt;
&lt;h3 id="bitcoin-core"&gt;Bitcoin Core&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Opened a &amp;ldquo;Tracepoint Interface Tracking Issue&amp;rdquo; to track open tasks that could be done to improve the tracepoint interface &lt;a href="https://github.com/bitcoin/bitcoin/issues/31274"&gt;https://github.com/bitcoin/bitcoin/issues/31274&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Got two of my long-open Bitcoin Core tracepoint PRs merged: &lt;a href="https://github.com/bitcoin/bitcoin/pull/26593"&gt;https://github.com/bitcoin/bitcoin/pull/26593&lt;/a&gt; and &lt;a href="https://github.com/bitcoin/bitcoin/pull/25832"&gt;https://github.com/bitcoin/bitcoin/pull/25832&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Did some testing on &amp;ldquo;ci: detect outbound internet traffic generated while running tests #31349&amp;rdquo; and found that on some systems the tests still make DNS requests, which can be used to identify developers running the tests &lt;a href="https://github.com/bitcoin/bitcoin/pull/31349#issuecomment-2499335672"&gt;https://github.com/bitcoin/bitcoin/pull/31349#issuecomment-2499335672&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Helped Suhas with tracing related changes in &lt;a href="https://github.com/bitcoin/bitcoin/pull/31122#discussion_r1820876152"&gt;https://github.com/bitcoin/bitcoin/pull/31122#discussion_r1820876152&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quick fixup &lt;a href="https://github.com/bitcoin/bitcoin/pull/31419"&gt;https://github.com/bitcoin/bitcoin/pull/31419&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="peer-observer"&gt;peer-observer&lt;/h3&gt;
&lt;p&gt;A tool used to monitor for attacks and anomalies by hooking into the Bitcoin Core tracepoints.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With Bitcoin Core &lt;a href="https://github.com/bitcoin/bitcoin/pull/26593"&gt;# tracing: Only prepare tracepoint arguments when actually tracing #26593&lt;/a&gt; merged, I opened &lt;a href="https://github.com/0xB10C/peer-observer/issues/58"&gt;Support PID / PID file to attach tracepoints to bitcoind #58&lt;/a&gt; and addressed it in &lt;a href="https://github.com/0xB10C/peer-observer/pull/61"&gt;add: cli arg to specify a bitcoind PID #61&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/nix/pull/75"&gt;add: use bitcoind PID in peer-observer #75&lt;/a&gt;. This made sure peer-observer stayed compatible with the changes to Bitcoin Core.&lt;/li&gt;
&lt;li&gt;Added a &lt;a href="https://github.com/0xB10C/peer-observer/issues/62"&gt;feature&lt;/a&gt; to warn and exit if the peer-observer extractor didn&amp;rsquo;t receive any events for a while. Implemented in &lt;a href="https://github.com/0xB10C/peer-observer/pull/63"&gt;https://github.com/0xB10C/peer-observer/pull/63&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Refactored peer-observer to use a NATS message server instead of an NNG (nanomessage) PUB-SUB. This allows to have multiple publishers instead of a single one. I&amp;rsquo;ve been planning to work on a RPC publisher in combination with the current tracepoint publisher. &lt;a href="https://github.com/0xB10C/peer-observer/pull/76"&gt;https://github.com/0xB10C/peer-observer/pull/76&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A python tool to record getblocktxn msgs used for &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/21?u=0xb10c"&gt;https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/21?u=0xb10c&lt;/a&gt;: &lt;a href="https://github.com/0xB10C/peer-observer/pull/106"&gt;https://github.com/0xB10C/peer-observer/pull/106&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;websocket tool maintenance: &lt;a href="https://github.com/0xB10C/peer-observer/pull/100"&gt;https://github.com/0xB10C/peer-observer/pull/100&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;nix-shell maintenance: &lt;a href="https://github.com/0xB10C/peer-observer/pull/96"&gt;https://github.com/0xB10C/peer-observer/pull/96&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="fork-observer"&gt;fork-observer&lt;/h3&gt;
&lt;p&gt;Tool to visualize forks and reorgs on various Bitcoin networks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;testnet4 reorg mining improvements for sjors: &lt;a href="https://github.com/0xB10C/fork-observer/pull/50"&gt;show header difficulty and highlight difficulty 1 blocks #50&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;esplora backend (blockstream.info, mempool.space) support &lt;a href="https://github.com/0xB10C/fork-observer/issues/52"&gt;https://github.com/0xB10C/fork-observer/issues/52&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/fork-observer/pull/55"&gt;https://github.com/0xB10C/fork-observer/pull/55&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added a legend explaining the tip status colors: &lt;a href="https://github.com/0xB10C/fork-observer/issues/53"&gt;https://github.com/0xB10C/fork-observer/issues/53&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/fork-observer/pull/54"&gt;https://github.com/0xB10C/fork-observer/pull/54&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;to display changes quicker to reduce desync, the backend now notifies the frontend about all changes: &lt;a href="https://github.com/0xB10C/fork-observer/pull/57"&gt;https://github.com/0xB10C/fork-observer/pull/57&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;btcd would send chain tips in random order, which confused fork-observer into thinking it the btcd tips changed. Fixed by sorting the tips before comparing them: &lt;a href="https://github.com/0xB10C/fork-observer/pull/58"&gt;https://github.com/0xB10C/fork-observer/pull/58&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="miningpool-observer"&gt;miningpool-observer&lt;/h3&gt;
&lt;p&gt;Transparency for Mining Pool Transaction Selection&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;general maintenance &lt;a href="https://github.com/0xB10C/miningpool-observer/pull/86"&gt;update rawtx-rs and bitcoin-pool-identification dependecies #86&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;add missing-sanctioned page &lt;a href="https://github.com/0xB10C/miningpool-observer/pull/89"&gt;https://github.com/0xB10C/miningpool-observer/pull/89&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use unique but non-sequential block id in database for easier importing of backups: &lt;a href="https://github.com/0xB10C/miningpool-observer/issues/91"&gt;https://github.com/0xB10C/miningpool-observer/issues/91&lt;/a&gt; and &lt;a href="https://github.com/0xB10C/miningpool-observer/pull/92"&gt;https://github.com/0xB10C/miningpool-observer/pull/92&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;remove dead block explorer links &lt;a href="https://github.com/0xB10C/miningpool-observer/issues/88"&gt;https://github.com/0xB10C/miningpool-observer/issues/88&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="my-nix-packages"&gt;my nix packages&lt;/h3&gt;
&lt;p&gt;Collection Nix packages and NixOS modules of software I&amp;rsquo;ve written or software I use. Allows others to easily run my tools too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;integration test for asmap.dat files/package &lt;a href="https://github.com/0xB10C/nix/pull/71"&gt;https://github.com/0xB10C/nix/pull/71&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;added a features that checks for new versions of packages in a nightly CI run and automatically opens a PR for the new version with me as a reviewer in &lt;a href="https://github.com/0xB10C/nix/pull/63"&gt;https://github.com/0xB10C/nix/pull/63&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;other maintenance and package updating: &lt;a href="https://github.com/0xB10C/nix/commits?author=0xB10C&amp;amp;since=2024-11-01&amp;amp;until=2025-01-31"&gt;https://github.com/0xB10C/nix/commits?author=0xB10C&amp;amp;since=2024-11-01&amp;amp;until=2025-01-31&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="misc"&gt;Misc&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;maintained my &lt;code&gt;ofac-sanctioned-digital-currency-addresses&lt;/code&gt; repo with, for example, &lt;a href="https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses/pull/18"&gt;https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses/pull/18&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;further maintenance of btcffm.org (&lt;a href="https://github.com/btcffm/website-btcffm/pull/16"&gt;https://github.com/btcffm/website-btcffm/pull/16&lt;/a&gt;, &lt;a href="https://github.com/btcffm/website-btcffm/pull/17"&gt;https://github.com/btcffm/website-btcffm/pull/17&lt;/a&gt;, &lt;a href="https://github.com/btcffm/website-btcffm/pull/15"&gt;https://github.com/btcffm/website-btcffm/pull/15&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;contributed my GUIX signatures for two Bitcoin Core release candidates: &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1484"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1484&lt;/a&gt;, &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1455"&gt;https://github.com/bitcoin-core/guix.sigs/pull/1455&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-do-you-plan-to-work-on-next-quarter"&gt;&lt;em&gt;What do you plan to work on next quarter?&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Evaluate (e.g. at CoreDev) if makes sense to continue working on the self-hosted Bitcoin Core CI runners or if other projects might have a higher priority for me for now.&lt;/li&gt;
&lt;li&gt;Continue working on open issues for my current projects like fork-observer, miningpool-observer, peer-observer and others&lt;/li&gt;
&lt;li&gt;Continue working on the Bitcoin Core tracing interface. See e.g. &lt;a href="https://github.com/bitcoin/bitcoin/issues/31274"&gt;https://github.com/bitcoin/bitcoin/issues/31274&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Further analyzing data and research posts on delving (e.g. compact block reconstruction and more)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fifteen OFAC-sanctioned transactions missing from blocks</title><link>https://b10c.me/observations/13-missing-sanctioned-transactions-2024-12/</link><pubDate>Thu, 16 Jan 2025 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/13-missing-sanctioned-transactions-2024-12/</guid><description>&lt;p&gt;My &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt; project aims to detect when Bitcoin mining pools are
not mining transactions they could have been mining. Over the past few weeks, it
detected fifteen missing transactions spending from OFAC-sanctioned addresses.
This post examines whether these transactions were filtered intentionally or if
there are other possible explanations for these transactions to be missing from
blocks. I conclude that F2Pool might have started to filter OFAC-sanctioned
transactions again. However, as F2Pool is currently the only pool that&amp;rsquo;s
possibly filtering, it does not affect Bitcoin&amp;rsquo;s censorship resistance: The
sanctioned transactions confirmed in the following blocks.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;An exchange used the recent low-fee environment to consolidate coins
from deposit addresses to their hot wallet. These consolidations included more
than a hundred transactions each spending at least one UTXO belonging to an
&lt;a href="https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses/blob/lists/sanctioned_addresses_XBT.txt"&gt;OFAC-sanctioned Bitcoin address&lt;/a&gt;. These transactions were good candiates for
re-evaluating if mining pools filter OFAC-sanctioned transactions.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/ofac-sanctioned-bitcoin-transactions-per-month.png'
alt='OFAC-sanctioned transactions since 2023. The OFAC-sanctioned transactions at the end of 2024 were good candiates for re-evaluating if mining pools filter them.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;OFAC-sanctioned transactions since 2023. The OFAC-sanctioned transactions at the end of 2024 were good candiates for re-evaluating if mining pools filter them.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href="https://github.com/0xB10C/miningpool-observer"&gt;miningpool-observer&lt;/a&gt; tool
provides insights into which transactions mining pools include and which
transactions they exclude. This works by regularly building block templates with
my node and comparing them to newly mined blocks. The methodology is explained
in detail &lt;a href="https://miningpool.observer/faq#methodology"&gt;here&lt;/a&gt;. While all of the
OFAC-sanctioned transactions were mined eventually, fifteen were not mined in
the first block they could have been mined in. For these, a closer analysis can
help to determine if a mining pool is purposefully excluding these or if they
didn&amp;rsquo;t make it into the block for other reasons: For example, a well-connected
mining pool may know high-feerate transactions my node does not yet
know about. These higher-feerate can displace lower-feerate transactions my node
considered for my block template. On the other hand, it can happen that my node
already knows about a transaction, but the pool hasn&amp;rsquo;t yet sent out a mining job
to its miners that includes the transaction. These &amp;ldquo;young&amp;rdquo; transactions can&amp;rsquo;t be
used as evidence for transaction filtering.&lt;/p&gt;
&lt;h2 id="blocks-with-missing-sanctioned-transactions"&gt;Blocks with missing sanctioned transactions&lt;/h2&gt;
&lt;p&gt;In the following, I list all reports my &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt; instance issued
via its &lt;a href="https://miningpool.observer/template-and-block/sanctioned-feed.xml"&gt;RSS feed&lt;/a&gt;.
The open-source tool can be self-hosted to check mining pool blocks against your
own block templates.&lt;/p&gt;
&lt;h3 id="f2pool-block-875575-filtered"&gt;F2Pool Block 875575 (filtered?)&lt;/h3&gt;
&lt;p&gt;My miningpool-obsever instance
&lt;a href="https://miningpool.observer/template-and-block/00000000000000000002169be205e3ba9c14b4d8ead578a180d90e873983f884"&gt;reports&lt;/a&gt;
the transaction
&lt;a href="https://mempool.space/tx/16f0ada61594e454c39bcc194a7ce272c1df5b0cfb4c9020f7d6d544f709df95"&gt;&lt;code&gt;16f0ada6..&lt;/code&gt;&lt;/a&gt;
as missing from F2Pool&amp;rsquo;s block 875575. The transaction spends a TXO belonging to
an address &lt;a href="https://ofac.treasury.gov/recent-actions/20231003"&gt;sanctioned&lt;/a&gt; by
OFAC as part of sanctions against Chinese illicit drug producers and traders.
The transaction had been in my node&amp;rsquo;s mempool for more than 3 hours at the time
the block was found, which is more than enough time to propagate to other
nodes and mining pools. The transaction has 300 2-of-3 multisig inputs and a
single output resulting in a size of 86.4 kvByte and 345.88 kWU. It pays a
feerate of 3.97 sat/vByte, which is only minimally higher than the 3.92
sat/vByte feerate of the last transaction package in the block. In
&lt;a href="https://mempool.space/block/00000000000000000002169be205e3ba9c14b4d8ead578a180d90e873983f884"&gt;mempool.space&lt;/a&gt;&amp;rsquo;s
block audit, the transaction is marked as excluded: &amp;ldquo;marginal
fee&amp;rdquo;. This &lt;a href="https://mempool.space/docs/faq#how-do-block-audits-work"&gt;indicates&lt;/a&gt;
that &amp;ldquo;the transaction may have been displaced by an added transaction, or it may
have been displaced by another transaction from the mempool that was also at the
low end of the expected feerate range for the block&amp;rdquo;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-875575.png'
alt='Sanctioned transaction position if it would have been included in the block based on its feerate and weight.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Sanctioned transaction position if it would have been included in the block based on its feerate and weight.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To determine how close the sanctioned transaction was to not being included, we
can place it into its potential position in the block based on its feerate. The
package feerate distribution plot above shows that the sanctioned transaction
with a weight of 345.88 kWU would have started at roughly the 3.25 MWU mark
based on the feerate it pays. The end of the transaction is more than 400 kWU
away from the maximum block weight limit of 4 MWU. While the feerate difference
between the sanctioned transaction to the last package feerate might be
marginal, the transaction should have been included in the block. This indicates
that F2Pool could have filtered the transaction.&lt;/p&gt;
&lt;h3 id="f2pool-block-875840-too-young"&gt;F2Pool Block 875840 (too young?)&lt;/h3&gt;
&lt;p&gt;For F2Pool&amp;rsquo;s block 875840, miningpool-observer
&lt;a href="https://miningpool.observer/template-and-block/00000000000000000000f3dcb1874fc1bbec505191128275af85e70705f045ec"&gt;reports&lt;/a&gt;
that the transaction
&lt;a href="https://mempool.space/tx/26a3693de66fd12e22ce5cd828c6145b640ca2683682e852872c15206890dc76"&gt;&lt;code&gt;26a3693d..&lt;/code&gt;&lt;/a&gt; is missing.
The transaction spends from the same sanctioned address as in block 875575 and
two other sanctioned addresses related to the same Chinese illicit drug
producers and traders mentioned earlier.&lt;/p&gt;
&lt;p&gt;The transaction is marked as
&lt;a href="https://miningpool.observer/faq#transaction-tag-young"&gt;&amp;ldquo;young&amp;rdquo;&lt;/a&gt; by
miningpool-observer as it was only seen 58 seconds before the block was first
seen. My node first saw the transaction at 06:17:10 UTC, while the block header
timestamp of F2Pool&amp;rsquo;s block is 06:17:21 UTC (+11s). The miningpool-observer tool
processed the block at 06:18:08 UTC (another +47s later). It&amp;rsquo;s unclear how
accurate the header timestamp is as F2Pool&amp;rsquo;s and my clock might not be in sync
and miners might engage in timestamp rolling. We can&amp;rsquo;t know if the transaction
was filtered by F2Pool, or if it is a false-positive report. It could be that
the transaction didn&amp;rsquo;t make it into F2Pool&amp;rsquo;s mining job for timing reasons
related to transaction propagation or mining job publication. This missing
transaction can&amp;rsquo;t be seen as evidence that F2Pool filters OFAC-sanctioned
transactions.&lt;/p&gt;
&lt;h3 id="f2pool-block-875933-filtered"&gt;F2Pool Block 875933 (filtered?)&lt;/h3&gt;
&lt;p&gt;The transaction
&lt;a href="https://mempool.space/tx/5c1863780be90451dd3d4b34d366942406a5a66e45e333a7a1927a8e924d3b46"&gt;&lt;code&gt;5c186378..&lt;/code&gt;&lt;/a&gt;
is &lt;a href="https://miningpool.observer/template-and-block/000000000000000000018809ca05a4f61df44b03cb12f5bd5c5aaf05454e6e6a"&gt;reported&lt;/a&gt;
to be missing from F2Pool&amp;rsquo;s block 875933. One of the outputs being spent
belongs to a sanctioned address related to the same Chinese illicit drug
producers and traders mentioned before.&lt;/p&gt;
&lt;p&gt;The transaction had been in my node&amp;rsquo;s mempool for more than 5 minutes before the
block was found. This is generally enough time for the transaction to
propagate to the pool and to make it into a block template and mining
job. With 300 inputs and one output, the transaction has a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;85'713 vByte&lt;/span&gt;. It pays a fee
of &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;339'876 sat&lt;/span&gt;, which
results in a feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;3.965'279 sat/vByte&lt;/span&gt;. For this block, the miningpool-observer tool
also reports that F2Pool included the &amp;ldquo;extra&amp;rdquo; transaction
&lt;a href="https://mempool.space/tx/a64c4d64e2647e0ed62945d886355559946220786f0c17e76851acc48fadb646"&gt;&lt;code&gt;a64c4d64..&lt;/code&gt;&lt;/a&gt;
(among others) which wasn&amp;rsquo;t present in my node&amp;rsquo;s block template. This transaction
is a similar 300-input and one-output consolidation with a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;85'917 vByte&lt;/span&gt; (204 vByte
larger than the missing transaction). It pays a fee of &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;340'632 sat&lt;/span&gt; (756 sat more than the missing
transaction), which results in a feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;3.964'664 sat/vByte&lt;/span&gt; which is minimally lower than the
feerate of the sanctioned transaction. Both transactions didn&amp;rsquo;t have any
unconfirmed parents or children at the time the block was found. A
fee-maximizing miner should have included the sanctioned transaction
&lt;code&gt;5c186378..&lt;/code&gt; in favor of &lt;code&gt;a64c4d64..&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-875933.png'
alt='Sanctioned transaction position if it would have been included in the block based on its feerate and weight.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Sanctioned transaction position if it would have been included in the block based on its feerate and weight.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While the mempool-space &lt;a href="https://mempool.space/block/000000000000000000018809ca05a4f61df44b03cb12f5bd5c5aaf05454e6e6a"&gt;block
audit&lt;/a&gt;
marks the transaction with &amp;ldquo;marginal fee&amp;rdquo;, the package feerate distribution
chart shows there would have been more than 500 kWU of space in the block after
the transaction. This indicates that F2Pool could have filtered the transaction.&lt;/p&gt;
&lt;h3 id="f2pool-block-876028-filtered"&gt;F2Pool Block 876028 (filtered?)&lt;/h3&gt;
&lt;p&gt;For F2Pool&amp;rsquo;s block 876028, two sanctioned transactions are
&lt;a href="https://miningpool.observer/template-and-block/0000000000000000000228add6706ec30af0ffa17d932a4ed7b21ccb77c8f98a"&gt;reported&lt;/a&gt;
missing. Both &lt;a href="https://mempool.space/tx/4b0730533e2910b647127c1f7cf78994dd938e5e6168cf20e6e938ad8b9d718c"&gt;&lt;code&gt;4b073053..&lt;/code&gt;&lt;/a&gt;
and &lt;a href="https://mempool.space/tx/3d9f28246aa5a13bba60fdf6da24cef6c04e4618526001c7a8d09baec8fd0834"&gt;&lt;code&gt;3d9f2824..&lt;/code&gt;&lt;/a&gt; spent
from a sanctioned address related to the Chinese illicit drug producers and
traders mentioned before.&lt;/p&gt;
&lt;p&gt;Transaction &lt;code&gt;4b073053..&lt;/code&gt; had been in my node&amp;rsquo;s mempool for more than 10 minutes
and transaction &lt;code&gt;3d9f2824..&lt;/code&gt; for more than 6 minutes. F2Pool ended up including
the 300-input and one-output consolation transaction
&lt;a href="https://mempool.space/tx/906da5b590c773161aab093edac76ca032d4537275114dc74aec45bb888e07a9"&gt;&lt;code&gt;906da5b5..&lt;/code&gt;&lt;/a&gt;
which my node didn&amp;rsquo;t consider for the block as it has a minimally lower feerate
than the two sanctioned transactions:&lt;/p&gt;
&lt;p&gt;The sanctioned transaction &lt;code&gt;4b073053..&lt;/code&gt; has a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;84'731 vByte&lt;/span&gt; while paying &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;336'096 sat&lt;/span&gt; in fees resulting in a feerate
of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;3.966'624 sat/vByte&lt;/span&gt;.
With &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;86'079 vByte&lt;/span&gt; and a
fee of &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;341'388 sat&lt;/span&gt;, the
sanctioned transaction &lt;code&gt;3d9f2824..&lt;/code&gt; pays a feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;3.965'985 sat/vByte&lt;/span&gt;. The extra transaction
&lt;code&gt;906da5b5..&lt;/code&gt; pays &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;336'096 sat&lt;/span&gt; at a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;84'749 vByte&lt;/span&gt;, which results in a minimally lower feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;3.965'781 sat/vByte&lt;/span&gt;.
A fee-maximizing miner should have included the extra transaction &lt;code&gt;906da5b5..&lt;/code&gt;
only after the two sanctioned transactions.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-876028.png'
alt='Position of the two sanctioned transactions if they would have been included in the block based on their feerate and weight. The additional weight added by the first transaction is accounted for in the position of the second transaction.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Position of the two sanctioned transactions if they would have been included in the block based on their feerate and weight. The additional weight added by the first transaction is accounted for in the position of the second transaction.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While
&lt;a href="https://mempool.space/block/0000000000000000000228add6706ec30af0ffa17d932a4ed7b21ccb77c8f98a"&gt;mempool.space&lt;/a&gt;
tags both transactions as &amp;ldquo;marginal fee&amp;rdquo;, both sanctioned transactions could have
made it into the block based on their feerate and weight. This indicates that
F2Pool could have filtered the two transactions.&lt;/p&gt;
&lt;h3 id="f2pool-block-876255-filtered"&gt;F2Pool Block 876255 (filtered?)&lt;/h3&gt;
&lt;p&gt;The transaction
&lt;a href="https://mempool.space/tx/2d56f2f273f8b783341c380ab457af5c4693148ee2f584c3b8f1211671c424d2"&gt;&lt;code&gt;2d56f2f2..&lt;/code&gt;&lt;/a&gt; is
&lt;a href="https://miningpool.observer/template-and-block/000000000000000000011d2e9ab11e1f76f2d2476519e3a76d53c1ec273f3066"&gt;reported&lt;/a&gt;
to be missing from F2Pool&amp;rsquo;s block 876255. It spends from a sanctioned address
related to the Chinese illicit drug producers and traders. The transaction was
in my node&amp;rsquo;s mempool for over an hour. The
&lt;a href="https://mempool.space/block/000000000000000000011d2e9ab11e1f76f2d2476519e3a76d53c1ec273f3066"&gt;mempool.space&lt;/a&gt;
block-audit marks this transaction as &amp;ldquo;removed&amp;rdquo; from the expected block too.
This indicates that F2Pool could have filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/mempool-space-876255-remove-from-expected.png'
alt='A screenshot of mempool-space block audit of this block shows the sanctioned transaction as _removed_.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A screenshot of mempool-space block audit of this block shows the sanctioned transaction as &lt;em&gt;removed&lt;/em&gt;.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="sbi-crypto-block-876590-displaced"&gt;SBI Crypto Block 876590 (displaced?)&lt;/h3&gt;
&lt;p&gt;For SBI Crypto&amp;rsquo;s block 876590, the transaction
&lt;a href="https://mempool.space/tx/a8a2e53358c9985df142421cd1b76afb2562f7d5f03f6871464f8136d5958226"&gt;&lt;span style="background-color: #ffb3ba; color: black;" class="px-1 rounded"&gt;a8a2e533..&lt;/span&gt;&lt;/a&gt; is
&lt;a href="https://miningpool.observer/template-and-block/00000000000000000001e2aa7f33a53189a4ce98c7827a03d31c0af70db9b9c6"&gt;reported&lt;/a&gt;
to be missing. The transaction spends from a sanctioned address related to the
Chinese illicit drug producers and traders.&lt;/p&gt;
&lt;p&gt;The transaction had been in my mempool for 44 minutes at the time the block was
found. With a fee of &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;252'639 sat&lt;/span&gt; and a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;84'947 vByte&lt;/span&gt; it pays a feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;2.974'078 sat/vByte&lt;/span&gt;. SBI Crypto was aware of 16
transactions (see &amp;ldquo;Extra Transactions&amp;rdquo;) that paid a higher higher feerate
(between 3 and 15 sat/vByte) totaling up to 4074 vByte. These displaced the
sanctioned transaction and 12 other transactions (see &amp;ldquo;Missing Transactions&amp;rdquo;)
from the block. This allowed the pool to pick the slightly smaller 300-input and
one-output non-sanctioned consolidation transaction
&lt;a href="https://mempool.space/tx/e6b4428813830534fa24ff9498136094b0e37f1c19d58fe3f71039d16dfdfe90"&gt;&lt;span style="background-color: #f1cbff; color: black;" class="px-1 rounded"&gt;e6b44288..&lt;/span&gt;&lt;/a&gt;
paying a fee of &lt;span style="background-color: #baffc9; color: black;" class="px-1 rounded"&gt;246'969 sat&lt;/span&gt; with a size of &lt;span style="background-color: #ffdfba; color: black;" class="px-1 rounded"&gt;83'042 vByte&lt;/span&gt;. This results in a feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;2.974'025 sat/vByte&lt;/span&gt;, which is minimally
lower than the feerate of the sanctioned transaction but higher than the
feerates of the 12 missing transactions.&lt;/p&gt;
&lt;p&gt;As the sanctioned transaction would have been included rather close to the end
of the block with the 16 extra transactions, it&amp;rsquo;s worthwhile to take a closer
look if the sanctioned transaction &lt;span style="background-color: #ffb3ba; color: black;" class="px-1 rounded"&gt;a8a2e533..&lt;/span&gt; would fit into the block after the 16 extra transactions.&lt;/p&gt;
&lt;p&gt;In block 876590, the first transaction (&lt;span style="background-color: #f1cbff; color: black;" class="px-1 rounded"&gt;e6b44288..&lt;/span&gt;) with a feerate lower than the sanctioned transaction starts
after the first 3'659'828 WU of the block and weighs &lt;span style="background-color: #ffffba; color: black;" class="px-1 rounded"&gt;332'167 WU&lt;/span&gt;. The sanctioned transaction
&lt;span style="background-color: #ffb3ba; color: black;" class="px-1 rounded"&gt;a8a2e533..&lt;/span&gt; weighs
&lt;span style="background-color: #ffffba; color: black;" class="px-1 rounded"&gt;339'785 WU&lt;/span&gt; (7618 WU more).
The transaction &lt;span style="background-color: #f1cbff; color: black;" class="px-1 rounded"&gt;e6b44288..&lt;/span&gt; ends
at &lt;code&gt;3'659'828 WU + 332'167 WU =&lt;/code&gt; &lt;span style="background-color: lightgray; color: black;" class="px-1 rounded"&gt;3'991'995 WU&lt;/span&gt; (8005 WU left in block) and the sanctioned transaction
would end at &lt;code&gt;3'659'828 WU + 339'785 WU =&lt;/code&gt; &lt;span style="background-color: lightgray; color: black;" class="px-1 rounded"&gt;3'999'613 WU&lt;/span&gt; (387 WU left in block). Both don&amp;rsquo;t overflow
the maximum block weight of 4 MWU, however, there is another threshold to
consider.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-876590.png'
alt='The tail end of the package feerate distribution plot of block 876590. Shows that the sanctioned transaction would have been too large for the block as it would have overflown into the reserved weight.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;The tail end of the package feerate distribution plot of block 876590. Shows that the sanctioned transaction would have been too large for the block as it would have overflown into the reserved weight.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When building block templates, Bitcoin Core reserves a bit of block weight for
the block header and the coinbase transaction, which aren&amp;rsquo;t part of the
template. Due to the bug described in issue &lt;a href="https://github.com/bitcoin/bitcoin/issues/21950"&gt;#21950: Duplicate coinbase
transaction space reservation in
CreateNewBlock&lt;/a&gt;, Bitcoin Core
currently reserves 2x 4000 WU by default. Subtracting the actual coinbase
weight of 716 WU and the header weight of 332 WU from the reserved 8000 WU
leaves us with a reserved but unused weight of 6952 WU. After the sanctioned
transaction &lt;span style="background-color: #ffb3ba; color: black;" class="px-1 rounded"&gt;a8a2e533..&lt;/span&gt;, only
387 WU would have been left in the block, which is less than 6952 WU, and this
explains why the sanctioned transaction was not picked. However, after
transaction &lt;span style="background-color: #f1cbff; color: black;" class="px-1 rounded"&gt;e6b44288..&lt;/span&gt;, there
were still &lt;code&gt;8005 WU - 6952 WU = 1053 WU&lt;/code&gt; left to fill. These were filled with a
450 WU and a 437 WU transaction &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Leaving &lt;code&gt;1053 WU - 450 WU - 437 WU = 166 WU&lt;/code&gt; of unreserved block weight unused.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-math" data-lang="math"&gt;Block weight usage of block 876590:
320 WU # header weight
+ 12 WU # tx_count var_int weight
+ 716 WU # coinbase transaction weight
+ 3991834 WU # non-coinbase transaction weight
= 3992882 WU # weight of block 876590
+ 6952 WU # unused, reserved weight
+ 166 WU # unused, unreserved weight
= 4000000 WU # maximum consensus block weight
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This assumes that the SBI Crypto pool uses the default value for the block
weight and doesn&amp;rsquo;t set a custom value with &lt;code&gt;-blockmaxweight&lt;/code&gt;. Based on the
statistics posted by &lt;a href="https://github.com/ismaelsadeeq"&gt;@ismaelsadeeq&lt;/a&gt; in
&lt;a href="https://delvingbitcoin.org/t/analyzing-mining-pool-behavior-to-address-bitcoin-cores-double-coinbase-reservation-issue/1351"&gt;Analyzing Mining Pool Behavior to Address Bitcoin Core’s Double Coinbase
Reservation Issue&lt;/a&gt;,
it seems like the majority of pools do use the default. In the two-year
timeframe analyzed by &lt;a href="https://github.com/ismaelsadeeq"&gt;@ismaelsadeeq&lt;/a&gt;, SBI
Crypto never mined a block with a weight higher than the non-coinbase
transaction weight Bitcoin Core fills by default. They did however mine a block
with &lt;a href="https://delvingbitcoin.org/t/analyzing-mining-pool-behavior-to-address-bitcoin-cores-double-coinbase-reservation-issue/1351/4?u=0xb10c"&gt;exactly 0 WU of unreserved block weight left&lt;/a&gt;.
This indicates that use the default block weight reservation.&lt;/p&gt;
&lt;p&gt;This explains why and how the sanctioned transaction was displaced in favor of
higher feerate transactions from SBI Crypto&amp;rsquo;s block 876590 making it unlikely
that they did filter this transaction. This is supported by the fact that SBI
Crypto did include another sanctioned transaction
&lt;a href="https://mempool.space/tx/1a579eb6ae62aa0b03ada5d33a757a2253a377888867f264b5ec0cacd1807243"&gt;&lt;code&gt;1a579eb6..&lt;/code&gt;&lt;/a&gt;
related to the same sanctioned entity in the same block.&lt;/p&gt;
&lt;h3 id="viabtc-block-876614-too-young"&gt;ViaBTC Block 876614 (too young?)&lt;/h3&gt;
&lt;p&gt;The transaction &lt;a href="https://mempool.space/tx/e26f26d495d9c779fd60eb2d8498414bb32de383f7c4f5026f82742290df324a"&gt;&lt;code&gt;e26f26d4..&lt;/code&gt;&lt;/a&gt;
is &lt;a href="https://miningpool.observer/template-and-block/00000000000000000000c33b1c8597c67846792be46cf312b577b29a7a0a4c38"&gt;reported&lt;/a&gt;
as missing from ViaBTC&amp;rsquo;s block 876614. It spends from a sanctioned address
related to the Chinese illicit drug producers and traders. At the time the block
was processed by the miningpool-observer tool, the transaction had only been in
my mempool for 28 seconds. Along with the sanctioned transaction, 55 other
transactions are missing due to being too &amp;ldquo;young&amp;rdquo; and range between an age of 25
seconds and an age of 87 seconds. The sanctioned transaction might not have been
propagated to ViaBTC or used in a block template yet. It was likely not filtered
by ViaBTC.&lt;/p&gt;
&lt;p&gt;ViaBTC did mine the sanctioned transaction &lt;a href="https://mempool.space/tx/b3a25904f3a41255b2d9d8fe3117f744748130d267e1db85a825f19cda3cbb1b"&gt;&lt;code&gt;b3a25904..&lt;/code&gt;&lt;/a&gt;
43 blocks before and the transaction &lt;a href="https://mempool.space/tx/2951b5bb7aa047909315819d56a86eb9d5dec1111d467e36b051140b498a0546"&gt;&lt;code&gt;2951b5bb..&lt;/code&gt;&lt;/a&gt;
73 blocks after block 876614.&lt;/p&gt;
&lt;h3 id="f2pool-block-876655-filtered"&gt;F2Pool Block 876655 (filtered?)&lt;/h3&gt;
&lt;p&gt;F2Pool&amp;rsquo;s block 876655 is &lt;a href="https://miningpool.observer/template-and-block/0000000000000000000002081867e6a1c315265b7cc1538cff2b90b0e406ef72"&gt;reported&lt;/a&gt;
to be missing the transaction the sanctioned &lt;a href="https://mempool.space/tx/cb35835c8a17495409f0f30cae5ec074b8292da8d49a39ead13b2f399aa1cd3f"&gt;&lt;code&gt;cb35835c..&lt;/code&gt;&lt;/a&gt;.
Along with the other transactions, this transaction is sanctioned by OFAC as it
spends from an address related to the Chinese illicit drug producers and traders.&lt;/p&gt;
&lt;p&gt;The transaction had been in my mempool for more than 17 minutes at the time
F2Pool&amp;rsquo;s block arrived. With a feerate of 2.98 sat/vByte it pays more than
enough fees to be included in the block. The &lt;a href="https://mempool.space/block/0000000000000000000002081867e6a1c315265b7cc1538cff2b90b0e406ef72"&gt;mempool.space&lt;/a&gt;
block-audit agrees with this and marks the sanctioned transactions as &amp;ldquo;removed&amp;rdquo;.
This indicates that F2Pool could have filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/mempool-space-876655-remove-from-expected.png'
alt='The mempool-space block-audit marking the sanctioned transaction cb35835c.. as removed.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;The mempool-space block-audit marking the sanctioned transaction cb35835c.. as removed.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="f2pool-block-876732-filtered"&gt;F2Pool Block 876732 (filtered?)&lt;/h3&gt;
&lt;p&gt;The F2Pool block 876732 is &lt;a href="https://miningpool.observer/template-and-block/000000000000000000021918b637ab613374f1b7426087185e992f17c4571f8e"&gt;reported&lt;/a&gt;
to be missing the sanctioned transaction &lt;a href="https://mempool.space/tx/9522108df7984d234450332f11d91d6ffe130d64512dbaf77ca466f0af297e2a"&gt;&lt;code&gt;9522108d..&lt;/code&gt;&lt;/a&gt;
which is spent from an address related to the Chinese illicit drug producers and
traders. The transaction was in my node&amp;rsquo;s mempool for nearly 25 minutes before
the F2Pool block arrived and paid a high enough feerate to be included in the
block. The block-audit on &lt;a href="https://mempool.space/block/000000000000000000021918b637ab613374f1b7426087185e992f17c4571f8e"&gt;mempool.space&lt;/a&gt;
marks the sanctioned transactions as &amp;ldquo;removed&amp;rdquo; as well. This indicates that F2Pool
could have filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/mempool-space-876732-remove-from-expected.png'
alt='The mempool-space block-audit marking the sanctioned transaction 9522108d.. as removed.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;The mempool-space block-audit marking the sanctioned transaction 9522108d.. as removed.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="f2pool-block-876883-filtered"&gt;F2Pool block 876883 (filtered?)&lt;/h3&gt;
&lt;p&gt;F2Pool&amp;rsquo;s block 876883 is &lt;a href="https://miningpool.observer/template-and-block/00000000000000000001ae4885db97ca306cf65948af3df1c8bc71b76524facd"&gt;reported&lt;/a&gt;
to be missing the sanctioned transaction &lt;a href="https://mempool.space/tx/41f4dfbe4f8b4a6b79d5f22dcd5dfddf4a923e234f35e2518a4e253372df59ab"&gt;&lt;code&gt;41f4dfbe..&lt;/code&gt;&lt;/a&gt;
which spends from an address related to the Chinese illicit drug producers and
traders. The transaction had been in my mempool for more than 6 minutes before
the F2Pool found the block and paid a high enough feerate to be included in the
block. The &lt;a href="https://mempool.space/block/00000000000000000001ae4885db97ca306cf65948af3df1c8bc71b76524facd"&gt;mempool.space&lt;/a&gt;
block-audit marks the transaction as &amp;ldquo;removed&amp;rdquo;. This indicates that F2Pool could
have filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/mempool-space-876883-remove-from-expected.png'
alt='The mempool-space block-audit marking the sanctioned transaction 41f4dfbe.. as removed.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;The mempool-space block-audit marking the sanctioned transaction 41f4dfbe.. as removed.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="f2pool-block-877061-filtered"&gt;F2Pool block 877061 (filtered?)&lt;/h3&gt;
&lt;p&gt;In F2Pool&amp;rsquo;s block 877061 the sanctioned transaction &lt;a href="https://mempool.space/tx/1a97f4c4c58e08e7c9a7a89bd074c57492a96bdd63b1aa3c3e9be473adf5b8a6"&gt;&lt;code&gt;1a97f4c4..&lt;/code&gt;&lt;/a&gt;
is &lt;a href="https://miningpool.observer/template-and-block/00000000000000000001b4fe284bb6ddd29cf988578aec6d383de5a2bd6d9f31"&gt;reported&lt;/a&gt;
to be missing. As with the other transactions, it spends from an address related
to the Chinese illicit drug producers and traders. It had been in my node&amp;rsquo;s
mempool for close to 15 minutes. While the &lt;a href=""&gt;mempool-space&lt;/a&gt; block-audit labels
the transaction with &amp;ldquo;marginal fee&amp;rdquo;, it paid enough fees and would have fit into
the block. This indicates that F2Pool could have filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-877061.png'
alt='Position of the sanctioned transaction 1a97f4c4.. if it would have been included in the block based on its feerate and weight.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Position of the sanctioned transaction 1a97f4c4.. if it would have been included in the block based on its feerate and weight.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="f2pool-block-878458-filtered"&gt;F2Pool block 878458 (filtered?)&lt;/h3&gt;
&lt;p&gt;F2Pool&amp;rsquo;s block 878458 is &lt;a href="https://miningpool.observer/template-and-block/00000000000000000000cc10770e54f9be1816e11eabe1cb9bce3163ed2140be"&gt;reported&lt;/a&gt;
to be missing the sanctioned transaction &lt;a href="https://mempool.space/tx/5e2c59cb2b90dfb1dbbc315973318702440062a7800567e2b8230b4fa0501e19"&gt;&lt;code&gt;5e2c59cb..&lt;/code&gt;&lt;/a&gt;
which spends from an address related to the Chinese illicit drug producers and
traders. The transaction had been in my mempool for more than an hour. While the
&lt;a href="https://mempool.space/block/00000000000000000000cc10770e54f9be1816e11eabe1cb9bce3163ed2140be"&gt;mempool.space&lt;/a&gt;
block-audit marks the transaction as &amp;ldquo;marginal fee&amp;rdquo;, it does pay enough fees to
be included in the first half of the block. This indicates that F2Pool could have
filtered the transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/13-missing-sanctioned-transactions/feerate-distribution-878458.png'
alt='Position of the sanctioned transaction 1a97f4c4.. if it would have been included in the block based on its feerate and weight.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Position of the sanctioned transaction 1a97f4c4.. if it would have been included in the block based on its feerate and weight.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="f2pool-block-878889-block-not-full"&gt;F2Pool block 878889 (block not full)&lt;/h3&gt;
&lt;p&gt;For F2Pool&amp;rsquo;s block 878889, the one-input and one-output transaction
&lt;a href="https://mempool.space/tx/6ad80e223ce68246775ecee65ba91b72207e2c7fa2d626e49cc37f91aaea967c"&gt;&lt;code&gt;6ad80e22..&lt;/code&gt;&lt;/a&gt;
is
&lt;a href="https://miningpool.observer/template-and-block/000000000000000000009f6a572e71b2ee4439d36da7568f9acdf73e753336e8"&gt;reported&lt;/a&gt;
to be missing from the block. While the previously mentioned transactions were spent
from a sanctioned address, this transaction sent a near-dust amount of 600 sat
to an address related to a different Chinese national belonging to a different
Drug Trafficking Organization &lt;a href="https://ofac.treasury.gov/recent-actions/20190821"&gt;sanctioned by
OFAC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The transaction was in my node&amp;rsquo;s mempool for more than 16 hours and would have
fit into the F2Pool block. However, F2Pool only filled the block with only about
30% of the maximum allowed block weight. While the following F2Pool block 878894
was filled correctly again, the F2Pool blocks 878900 and 878901 were filled with
less than 2.5% of the maximum block weight. As &lt;a href="https://x.com/mononautical/status/1878348634686632409"&gt;@mononautical suggested&lt;/a&gt;,
these blocks might have been built by a node that was just restarted and didn&amp;rsquo;t
have all transactions loaded into the mempool yet. This reported missing
sanctioned transaction can not be used as evidence that F2Pool filters OFAC-sanctioned
transactions.&lt;/p&gt;
&lt;h3 id="foundry-usa-block-878898-displaced"&gt;Foundry USA block 878898 (displaced)&lt;/h3&gt;
&lt;p&gt;In Foundry USA&amp;rsquo;s block 878898, the sanctioned one-input and one-output transaction &lt;a href="https://mempool.space/tx/e415e518addaf902b472ead609d05de64d6cac1d568651c7a7f69665ef0e909b"&gt;&lt;code&gt;e415e518..&lt;/code&gt;&lt;/a&gt;
is &lt;a href="https://miningpool.observer/template-and-block/00000000000000000000e339bf00bbf501d003bba240f2a580fd3a3432312753"&gt;reported&lt;/a&gt;
to be missing from Foundry USA&amp;rsquo;s block 878898. Similar to the transaction before,
it sends a near-dust amount of 600 sat to an address related to a Chinese Drug
Trafficking Organization.&lt;/p&gt;
&lt;p&gt;The transaction had been in my node&amp;rsquo;s mempool for more than 10 days and pays a
feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;1.05 sat/vByte&lt;/span&gt;.
However, Foundry USA knew about a large 26548 vByte transaction that pays a
feerate of &lt;span style="background-color: #bae1ff; color: black;" class="px-1 rounded"&gt;1.07 sat/vByte&lt;/span&gt;
and displaced 130 lower-feerate transactions from the block. This includes the
sanctioned transaction. This missing sanctioned transaction can not be used as
evidence that Foundry filters OFAC-sanctioned transactions.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This blog post analyzes 14 blocks for which OFAC-sanctioned transactions were
reported as missing. Out of these, 11 blocks were mined by F2Pool, while SBI
Crypto, ViaBTC, and Foundry USA each mined one of the blocks. The missing
transactions from &lt;a href="#sbi-crypto-block-876590-displaced"&gt;SBI Crypto&amp;rsquo;s block
876590&lt;/a&gt; and &lt;a href="#foundry-usa-block-878898-displaced"&gt;Foundry USA&amp;rsquo;s block
878898&lt;/a&gt; were displaced by higher feerate
transactions, and the sanctioned transaction missing from &lt;a href="#viabtc-block-876614-too-young"&gt;ViaBTC&amp;rsquo;s block
876614&lt;/a&gt; could have been too young to be
included. While &lt;a href="#f2pool-block-875840-too-young"&gt;F2Pool&amp;rsquo;s block 875840&lt;/a&gt; is
missing a potentially too young transaction and &lt;a href="#f2pool-block-878889-block-not-full"&gt;F2Pool&amp;rsquo;s block
878889&lt;/a&gt; was only 30% full indicating a
problem with their block template, &lt;strong&gt;the other 9 analyzed F2Pool blocks indicate
that F2Pool might be filtering OFAC-sanctioned transactions again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After I identified four F2Pool blocks with missing sanctioned transactions in my
&lt;a href="https://b10c.me/observations/08-missing-sanctioned-transactions/#update-2023-11-23"&gt;previous analysis&lt;/a&gt;
(November 2023), the founder of F2Pool &lt;a href="https://archive.ph/UFP8l"&gt;confirmed&lt;/a&gt; that
F2Pool was running a &amp;ldquo;tx filtering patch&amp;rdquo;. This patch was disabled
for a while. While all pools with more than 5% hashrate mined an OFAC-sanctioned
transaction in the past weeks &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, F2Pool mined their &lt;a href="https://mempool.space/tx/43f6736b1e1ce73f971e2cd4f0d87d9a052e5bcee2164f857cf1756ef2af1bd5"&gt;most
recent OFAC-sanctioned transaction&lt;/a&gt; over seven
months ago on 2024-06-08.&lt;/p&gt;
&lt;p&gt;Note that a mining pool operator has the power and right to choose which
transactions to include in their blocks and which transactions to exclude.
Personally, I want to know when and if pools filter transactions - not because I
agree or disagree with US OFAC sanctions, but because I want to know how well
Bitcoin&amp;rsquo;s censorship resistance holds up against pools trying to filter
transactions. As all of the missing sanctioned transactions were picked up in
the following blocks, I assume Bitcoin&amp;rsquo;s censorship resistance has been holding
up quite well for now. We&amp;rsquo;ll see if, for example, stronger mining pool
regulations being enforced in the future, will change this. Until then, let&amp;rsquo;s
hope mining protocols like StratumV2 and DATUM, which allow miners to build and
use their own templates, gain more adoption.&lt;/p&gt;
&lt;p&gt;Not your templates, not your blocks.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;See the last two transactions on the bottom of &lt;a href="https://mempool.space/block/00000000000000000001e2aa7f33a53189a4ce98c7827a03d31c0af70db9b9c6?page=86"&gt;page 86 of block 876590&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I purposefully leave this part as an exercise for the
reader. Similar to how my miningpool-observer tool does not display when a pool
mines an OFAC-sanctioned transaction, I don&amp;rsquo;t want pictures or tables from this
blog post to be a potential source of information for regulators. Nonetheless,
the data is publicly visible on the blockchain. If a regulator wants to find out,
they will. If a reader wants to verify my claims, either take this as a small
exercise (here is a list of &lt;a href="https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses/blob/lists/sanctioned_addresses_XBT.txt"&gt;OFAC-sanctioned Bitcoin addresses&lt;/a&gt;
to start with) or feel free to reach out and I can share the methodology and
potentially some scripts.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>OpenSats Work-Log 3</title><link>https://b10c.me/funding/2024-opensats-report-3/</link><pubDate>Thu, 31 Oct 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2024-opensats-report-3/</guid><description>&lt;p&gt;This is a copy of the 3rd work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications--talks"&gt;Publications &amp;amp; Talks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://b10c.me/observations/12-template-similarity/"&gt;Block Template Similarities between Mining Pools&lt;/a&gt;: Using the Stratum jobs I&amp;rsquo;ve collected with my stratum-observer project, I was able to show that the block templates from major mining pools often match. This indicates proxy pooling and suggests mining centralization.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052"&gt;Stats on compact block reconstructions&lt;/a&gt;: I published a delving post with data on compact block reconstructions. Specifically, I looked at reconstructions that don&amp;rsquo;t need an extra round trip for transaction requests. I also used this data to conceptually review the &lt;code&gt;mempoolfullrbf=1&lt;/code&gt;-by-default &lt;a href="https://github.com/bitcoin/bitcoin/pull/30493#issuecomment-2263336312"&gt;PR&lt;/a&gt;. I plan to revisit this topic with recent data.&lt;/li&gt;
&lt;li&gt;I authored two Bitcoin Core vulnerability disclosures I was involved with to some extend: &lt;a href="https://bitcoincore.org/en/2024/10/08/disclose-large-inv-to-send/"&gt;Disclosure of DoS due to inv-to-send sets growing too large&lt;/a&gt; and &lt;a href="https://bitcoincore.org/en/2024/10/08/disclose-mutated-blocks-hindering-propagation/"&gt;Disclosure of hindered block propagation due to mutated blocks&lt;/a&gt;. I appeared on the brink.dev podcast to discuss on of these vulnerabilities. &lt;a href="https://brink.dev/podcast/6-bitcoin-core-pre-25-disclosures/"&gt;# Episode 6: Discussing Pre-25.0 Bitcoin Core Vulnerability Disclosures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I spoke at the &lt;a href="https://ebpf.io/summit-2024/"&gt;eBPF Summit 2024&lt;/a&gt; about how I’m using eBPF and the USDT tracepoints in Bitcoin Core to extract P2P network events to use them for, for example, anomaly detection: &lt;a href="https://b10c.me/talks/#monitoring-bitcoin-p2p-network-attacks-and-anomalies-with-ebpf-and-usdt-tracepoints"&gt;https://b10c.me/talks/#monitoring-bitcoin-p2p-network-attacks-and-anomalies-with-ebpf-and-usdt-tracepoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;At the “Bitcoin Burg Academy” I talked about Bitcoin Core development to an non-technical audience: &lt;a href="https://b10c.me/talks/#bitcoin-core-development"&gt;https://b10c.me/talks/#bitcoin-core-development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="projects"&gt;Projects&lt;/h4&gt;
&lt;h5 id="peer-observer-and-infrastructure"&gt;peer-observer and infrastructure&lt;/h5&gt;
&lt;p&gt;To monitor for Bitcoin P2P anomalies and attacks, I run Bitcoin Core “honeynodes” (honeypot nodes). The nodes have additional monitoring attached that is used to record data and metrics. In the last three months, my focus has been on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/0xB10C/peer-observer/pull/55"&gt;Developed&lt;/a&gt; a set of tools that can connect to a peer-observer websocket endpoint and display P2P events in real time.&lt;/li&gt;
&lt;li&gt;Configured CJDNS connectivity on some of the monitoring nodes&lt;/li&gt;
&lt;li&gt;Started a few nodes with a recent ASMap file to test and collect data on ASMap&lt;/li&gt;
&lt;li&gt;Configured some nodes with ASan, UBSan and TSan to catch potential issues&lt;/li&gt;
&lt;li&gt;All &lt;code&gt;assumes()&lt;/code&gt; normally disabled in production nodes are treat as asserts to catch potential issues&lt;/li&gt;
&lt;li&gt;I cleaned up a bunch of old Grafana dashboards and created a big playlist&lt;/li&gt;
&lt;li&gt;General maintenance of the nodes and infrastructure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="bitcoin-core"&gt;Bitcoin Core&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;I attended the CoreDev event to catch up with other Bitcoin Core developers. I showcased some of my P2P monitoring tooling and got good feedback and ideas.&lt;/li&gt;
&lt;li&gt;I have been GUIX-building and uploading signatures of GUIX build hashes for the recent Bitcoin Core releases.&lt;/li&gt;
&lt;li&gt;I helped in another round of (demo) ASMap file &lt;a href="https://github.com/asmap/asmap-data/issues/18"&gt;creation&lt;/a&gt; and &lt;a href="https://github.com/asmap/asmap-data/pull/19"&gt;publication&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Helped out in exploring a &lt;a href="https://github.com/willcl-ark/nix-ci"&gt;nix-based CI&lt;/a&gt; setup for Bitcoin Core to reduce the maintenance burden and allow for faster deployment of more CI resources.&lt;/li&gt;
&lt;li&gt;I’ve been keeping my currently open PRs rebased and ready for review.&lt;/li&gt;
&lt;li&gt;When ever suitable, I left my feedback on Bitcoin Core PRs and issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="misc"&gt;misc&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;nix flake: I have a &lt;a href="https://github.com/0xB10C/nix"&gt;nix flake repository&lt;/a&gt; for nix packages and modules of Bitcoin software I use. Next to keeping the packages and modules up-to-date, I&amp;rsquo;ve added a package for my transactionfee-info rewrite, added a miningpool-observer module and integration test, an asmap-data package, and an auto update functionality for packages.&lt;/li&gt;
&lt;li&gt;fork-observer: Based on user feedback from the Warnet project, I improved the UX a bit in &lt;a href="https://github.com/0xB10C/fork-observer/pull/47"&gt;#47&lt;/a&gt;. I also set up a &lt;code&gt;testnet4&lt;/code&gt; node and attached fork-observer to it: &lt;a href="https://fork.observer/?network=4"&gt;https://fork.observer/?network=4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;stratum-observer: While I had planned to work on stratum v2 implementation in Q3 2024, I have postponed this work for now as there is no real stratum v2 pool yet to connect to. Using the collected stratum data to show the similarities between pools seemed more important.&lt;/li&gt;
&lt;li&gt;btcffm.org: I&amp;rsquo;m maintaining the website of the Frankfurt Bitcoin meetup&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="what-do-you-plan-to-work-on-next-quarter"&gt;&lt;em&gt;What do you plan to work on next quarter?&lt;/em&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;further work on the peer-observer &amp;amp; infrastructure
&lt;ul&gt;
&lt;li&gt;keep nodes up-to-date and running&lt;/li&gt;
&lt;li&gt;switch out the current nanomsg message queue for something like NATS: &lt;a href="https://github.com/0xB10C/peer-observer/issues/56"&gt;https://github.com/0xB10C/peer-observer/issues/56&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;extract non-sensitive infrastructure parts into a separate nix flake and publish a public demo instance again&lt;/li&gt;
&lt;li&gt;collect and analyze data on the transactions requested for compact block reconstruction&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;continue to work on the projects mentioned above and in the last progress reports&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Episode 6: Discussing Pre-25.0 Bitcoin Core Vulnerability Disclosures</title><link>https://b10c.me/talks/023-brink-podcast-pre25-vulns/</link><pubDate>Thu, 10 Oct 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/023-brink-podcast-pre25-vulns/</guid><description>&lt;p&gt;I joined Gloria and Niklas on the &lt;a href="https://brink.dev/podcast"&gt;brink.dev podcast&lt;/a&gt; to talk about the recently disclosed Bitcoin Core pre-25.0 vulnerabilities.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://brink.dev/podcast/6-bitcoin-core-pre-25-disclosures/"&gt;Episode 6: Discussing Pre-25.0 Bitcoin Core Vulnerability Disclosures&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Block Template Similarities between Mining Pools</title><link>https://b10c.me/observations/12-template-similarity/</link><pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/12-template-similarity/</guid><description>&lt;p&gt;Different mining pools sending out the same or a similar block template to
miners is an indicator for proxy pooling. Knowing about proxy pools is important
when discussing mining pool centralization. To find similarities between mining
pool block templates, I compare the Merkle branches pools sent in the stratum
jobs and calculate a similarity score. This shows pools with similar templates
and allows building a relationship graph between the pools.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In April 2024, I &lt;a href="https://x.com/0xB10C/status/1780611768081121700"&gt;reported&lt;/a&gt;
that pools like BTC.com, Binance Pool, Poolin, Braiins, and possibly other
pools&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; sometimes have the same template and custom
transaction prioritization as AntPool. While motivated by &lt;a href="https://x.com/mononautical/status/1777686545715089605"&gt;on-chain transaction
flows&lt;/a&gt;, my reporting was
mainly based on visual observations over a few days. To further
back these claims with data and have a base for further analysis, I started to
record the stratum jobs published by major Bitcoin mining pools. In this post, I
use a similarity score to compare the pool templates and develop a pool
relationship graph showing pools that share templates.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Looking at the merkle branches that mining pools send to miners as part of stratum jobs, it&amp;#39;s clear that the BTCcom pool, Binance pool, Poolin, EMCD, Rawpool, and possibly Braiins* have exactly the same template and custom transaction prioritization as AntPool. &lt;a href="https://t.co/KTjFWtTXEP"&gt;https://t.co/KTjFWtTXEP&lt;/a&gt; &lt;a href="https://t.co/xhCrdvkOH8"&gt;pic.twitter.com/xhCrdvkOH8&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1780611768081121700?ref_src=twsrc%5Etfw"&gt;April 17, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h3 id="stratum-jobs-and-merkle-branches"&gt;Stratum jobs and Merkle branches&lt;/h3&gt;
&lt;p&gt;Stratum jobs don&amp;rsquo;t contain the full list of transactions included in the block
template. A miner only needs to construct a block header, which can be done
without knowing the full template contents. Modern miners exhaust the 32-bit
nonce in the block header quite fast and can then either update the timestamp in
the header, roll the version a la overt ASICBoost, or change the so-called
&lt;em&gt;extranonce&lt;/em&gt; in the coinbase transaction, which causes the Merkle root to
change. For this, miners need the coinbase transaction, information about the
&lt;em&gt;extranonce&lt;/em&gt;, and the Merkle branches to calculate a new Merkle root.&lt;/p&gt;
&lt;p&gt;The list of Merkle branches in stratum jobs contains just the information
required to calculate the Merkle root. To build the Merkle root, the coinbase
transaction is hashes together with the first Merkle branch, the result is then
hashed with the second Merkle branch, which is then again hashed with the third
Merkle branch. The Merkle root is reached once all Merkle branches have been
hashed together.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/explainer.drawio.png'
alt='How to construct a block header from a stratum job: Shows the stratum job on the left and a block header on the top. The individual transactions, the Merkle branches, and the Merkle tree are shown.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;How to construct a block header from a stratum job: Shows the stratum job on the left and a block header on the top. The individual transactions, the Merkle branches, and the Merkle tree are shown.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The first Merkle branch &lt;code&gt;b0&lt;/code&gt;, which is hashed with the coinbase transaction, is
the txid of the first non-coinbase transaction in the block. The second Merkle
branch &lt;code&gt;b1&lt;/code&gt; consists of the txids of the third and fourth transaction hashed
together. The third Merkle branch &lt;code&gt;b2&lt;/code&gt; consists of the hash of the fifth and
sixth txid hashed together and the seventh and eighth txid hashed together. The
number of transactions included in each merkle branch grows exponentially.
While the third branch consists of 4 transactions, the eighth branch already
includes 128 transactions.&lt;/p&gt;
&lt;h3 id="similarity-score"&gt;Similarity-score&lt;/h3&gt;
&lt;p&gt;To measure the similarity of block templates by different pools, I record the
stratum jobs published by major mining pools with my work-in-progress
&lt;a href="https://github.com/0xB10C/stratum-observer"&gt;stratum-observer&lt;/a&gt; tool. While some
pools offer multiple stratum endpoints distributed across the globe, I choose
the endpoints located closest to me to reduce latency. Note that different
stratum endpoints from the same pool might serve different jobs.&lt;/p&gt;
&lt;p&gt;A naive approach to measure the similarity would be to compare the Merkle
branches included in mining jobs by calculating the share of branches that match
across two pools. For example, if two pools include 10 Merkle branches in their
jobs, and the first 7 Merkle branches match, this would result in a 70% match score.
However, as the number of transactions in the Merkle branches rises exponentially,
the first Merkle branches only contain a few transactions while the later
ones contain the majority. The result would show branch similarity, but wouldn&amp;rsquo;t
reflect template similarity.&lt;/p&gt;
&lt;p&gt;A weighted approach, where the first branches weigh less than the later branches
closer reflects template similarity. Matching branches can be weighted by the
maximum number of transactions they can contain. The weighted similarity score
of the Merkle branches $A$ and $B$ is&lt;/p&gt;
&lt;p&gt;$$
\sum_{A_i \text{ matches } B_i}^l {\frac{1}{2}^{1 + l - i}}
$$&lt;/p&gt;
&lt;p&gt;where $i$ is the one-indexed position in the list of Merkle branches and $l$ is
the minimum length of $A$ and $B$. A similarity score of 100% indicates that
the templates match while a similarity score of 0% indicates that no branch
matches. The example from above, where the first 7 of 10 Merkle branches
match, results in a 12.5% weighted similarity score.&lt;/p&gt;
&lt;p&gt;The similarity score is strongly affected by transaction ordering. Two block
templates may include the same transactions but as soon as the transaction order
is slightly different, the Merkle branches won&amp;rsquo;t match and the similarity score
won&amp;rsquo;t indicate similarity.&lt;/p&gt;
&lt;h3 id="evaluation"&gt;Evaluation&lt;/h3&gt;
&lt;p&gt;The described similarity score is applied to stratum job data collected between
2024-06-01 and 2024-09-12. The data consists of 690k stratum jobs across 24
pools and pool configurations. My test_0xB10C Pool_ used as a
reference, &lt;em&gt;AntPool&lt;/em&gt;, &lt;em&gt;BTC.com&lt;/em&gt;, &lt;em&gt;Binance Pool&lt;/em&gt;, &lt;em&gt;Braiins&lt;/em&gt;, the &lt;em&gt;CKPool&lt;/em&gt; solo
pool, the &lt;em&gt;DEMAND&lt;/em&gt; stratum v1 solo pool, &lt;em&gt;F2Pool&lt;/em&gt;, &lt;em&gt;Foundry&lt;/em&gt;, &lt;em&gt;Luxor&lt;/em&gt;,
&lt;em&gt;MaraPool&lt;/em&gt; and a development endpoint &lt;em&gt;Marapool (dev)&lt;/em&gt;, Ocean Pool&amp;rsquo;s four template
providers &lt;em&gt;Ocean (default)&lt;/em&gt;, &lt;em&gt;Ocean (ordis)&lt;/em&gt;, &lt;em&gt;Ocean (core)&lt;/em&gt;, &lt;em&gt;Ocean
(datafree)&lt;/em&gt;, &lt;em&gt;Poolin&lt;/em&gt;, the &lt;em&gt;PyBlock&lt;/em&gt; solo pool, &lt;em&gt;SBICrypto&lt;/em&gt;, &lt;em&gt;SecPool&lt;/em&gt;,
&lt;em&gt;SigmaPool&lt;/em&gt;, &lt;em&gt;SpiderPool&lt;/em&gt;, &lt;em&gt;Ultimus&lt;/em&gt;, and &lt;em&gt;ViaBTC&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pools generally publish at least one new mining job every minute with a
frequently used interval being 30 seconds. To be able to compare recent jobs by
different pools to each other, I sampled and compared the most recent jobs every
five minutes.&lt;/p&gt;
&lt;p&gt;The following similarity matrix gives an overview of which templates are
similar. The similarity scores shown in the matrix can be grouped into three
groups. First, a high-similarity group with scores between 99% and 80%
containing, for example, &lt;em&gt;AntPool-Poolin&lt;/em&gt; with a 99% similarity score, and
&lt;em&gt;AntPool-BTC.com&lt;/em&gt; with a 98% similarity score. Secondly, a low-similarity group
with scores between 35% and 20%. This group contains, for example,
&lt;em&gt;Braiins-Poolin&lt;/em&gt; with a score of 32%. The third group is the no-similarity group
with a similarity score below 5%. This group contains, for example, the &lt;em&gt;Ocean
(datafree)-ViaBTC&lt;/em&gt; pool combination with a similarity score of 0%.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/overview.png'
alt='A similarity matrix showing the mean similarity score for all 276 pool combinations. Higher (yellow) means more similar.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A similarity matrix showing the mean similarity score for all 276 pool combinations. Higher (yellow) means more similar.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="high-similarity-pools"&gt;High-similarity pools&lt;/h4&gt;
&lt;p&gt;The pool combinations in the high-similarity group regularly send out mining jobs
based on the same template. The high-similarity score combinations are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AntPool - Poolin: 99%&lt;/li&gt;
&lt;li&gt;AntPool - BTC.com: 98%&lt;/li&gt;
&lt;li&gt;BTC.com - Poolin: 99%&lt;/li&gt;
&lt;li&gt;SecPool - SigmaPool: 97%&lt;/li&gt;
&lt;li&gt;Braiins - Ultimus: 89%&lt;/li&gt;
&lt;li&gt;SpiderPool - Binance Pool: 81%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on these combinations, a preliminary relationship graph can be
constructed. A relationship between AntPool, BTC.com, Poolin, and other pools
has been assumed before based on &lt;a href="https://x.com/mononautical/status/1777686545715089605"&gt;coinbase reward
consolidations&lt;/a&gt;. Braiins
and Ultimus Pool both have been seen &lt;a href="https://theminermag.com/news/2023-12-28/bitcoin-mining-pool-block-reward-antpool-hashrate/"&gt;consolidating coinbase
rewards&lt;/a&gt;
with AntPool and other pools in the past, too. The relationship between
SigmaPool and SecPool isn&amp;rsquo;t surprising, as the SigmaPool stratum endpoint
&lt;code&gt;eu1.btc.sigmapool.com&lt;/code&gt; only publishes mining jobs with the &lt;code&gt;Mined by SecPool&lt;/code&gt;
tag in the coinbase transaction. The SigmaPool stratum endpoint is a proxy for
the SecPool endpoint. SpiderPool mined its first block in the spring of 2024.
The relationship with Binance Pool is interesting.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/preliminary-graph.drawio.png'
alt='A preliminary relationship graph based on high similarity scores. This graph is updated below.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A preliminary relationship graph based on high similarity scores. This graph is updated below.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="similarity-over-time"&gt;Similarity over time&lt;/h4&gt;
&lt;p&gt;By plotting the similarity scores of the high- and low-similarity pool
combinations over time, it becomes apparent that the BTC.com and Binance Pool
mining pools switched to a different template provider in the observed time
frame. The BTC.com pool switched to the Braiins and Ultimus templates at 9 am UTC
on 2024-06-18 for about 24 hours. Binance Pool switched from SpiderPool to the
AntPool-Poolin-BTC.com template on 2024-08-23.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/similarity-over-time.png'
alt='Pools with an average similarity score &amp;gt;10% plotted over time.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Pools with an average similarity score &amp;gt;10% plotted over time.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Binance Pool switching from SpiderPool to AntPool-Poolin-BTC.com can be observed
in the coinbase transaction tags sent along with the stratum jobs too. Until
2024-08-23, the Binance Pool endpoint I was connected to sends out jobs with the
&lt;code&gt;SpiderPool/&lt;/code&gt; coinbase tag. After the switch, it mainly includes the tag &lt;code&gt;binance/&lt;/code&gt;. A
relationship between Binance Pool and BTC.com has already been observed in
CoinMetrics&amp;rsquo;s State-of-the-Network #249 &lt;a href="https://coinmetrics.substack.com/p/state-of-the-network-issue-249"&gt;&amp;ldquo;Following Flows V: Pool
Cross-Pollination&amp;rdquo;&lt;/a&gt;.
While my data does not contain any jobs with &lt;code&gt;binance/&lt;/code&gt; in the coinbase tag before 2024-08-23, there were certainly blocks mined with a &lt;a href="https://mempool.space/mining/pool/binancepool"&gt;Binance Pool tag&lt;/a&gt;.
Possibly, different Binance Pool endpoints publish different jobs.&lt;/p&gt;
&lt;p&gt;From 2024-08-23 on, the Binance Pool jobs sometimes also contain the tag &lt;code&gt;Mined by SecPool&lt;/code&gt;. The Binance Pool endpoint seems to transparently, without changing
the coinbase transaction, proxy the mining jobs from SecPool from time to time.
This partly explains the similarity score of 10% between Binance Pool and
SecPool.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/coinbase-tags-binance.png'
alt='Coinbase tags in stratum jobs by Binance Pool&amp;#39;s stratum endpoint'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Coinbase tags in stratum jobs by Binance Pool&amp;rsquo;s stratum endpoint&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With this information, we can update the relationship graph.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/relationship-graph-2.drawio.png'
alt='Updated pool relationship graph.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Updated pool relationship graph.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When looking for switches in coinbase tags from other pools, Braiins and Luxor
stand out. Braiins normally uses the &lt;code&gt;/slush/&lt;/code&gt; coinbase tag from its predecessor
Slush. However, since mid-July 2024, they started to transparently proxy
Foundry&amp;rsquo;s mining jobs from time to time. This partly explains the 10% similarity
score between Braiins and Foundry. On 2024-08-23, the day Binance Pool switched
from SpiderPool to AntPool-Poolin-BTC.com, Braiins transparently proxied the
jobs from Binance Pool for a few hours. Note that the Binance Pool &lt;a href="https://web.archive.org/web/20240820182013/https://pool.binance.com/en"&gt;website&lt;/a&gt;
shows a popup referring to UltimusPool as a &amp;ldquo;Strategic Business Partner&amp;rdquo; and
mentions that UltimusPool, who have the same block templates as Braiins,
provides &amp;ldquo;technical services&amp;rdquo; for Binance Pool.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/coinbase-tags-braiins.png'
alt='Coinbase tags in stratum jobs by Braiins stratum endpoint'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Coinbase tags in stratum jobs by Braiins stratum endpoint&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Luxor stratum endpoint seems to transparently proxy the jobs from the
SBICrypto pool from time to time. Interestingly, the Luxor and SBICrypto pool
only have a similarity score of 2%.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/coinbase-tags-luxor.png'
alt='Coinbase tags in stratum jobs by Luxor&amp;#39;s stratum endpoint'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Coinbase tags in stratum jobs by Luxor&amp;rsquo;s stratum endpoint&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="low-similarity-pools"&gt;Low-similarity pools&lt;/h4&gt;
&lt;p&gt;To closer analyze the relationships in the low-similarity groups, it makes sense
to inspect where the Merkle branches of the pools with 20%-30% similarity match.
The number of Merkle branches in a job depends on
the number of transactions in the template, which depends on the pool&amp;rsquo;s mempool.
More transactions in the template mean more Merkle branches send in the mining
job. Currently, most mining jobs include 12 or 13 Merkle branches, which
corresponds to up to 4096 and 8092 transactions. The following graphic shows the
share of matching Merkle branches for each branch in the jobs. For example, when
comparing the Merkle branches of Ocean (core) and CKPool (solo), 70% of all
Merkle branches matched at position 2 when the jobs contained 12 Merkle branches.
The 70% can be found in the top-left chart, on x = 2 and y = 12.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/where-do-the-templates-match.png'
alt='Where do the Merkle branches match? Four examples.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Where do the Merkle branches match? Four examples.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The chart includes four hand-picked examples&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. The &lt;em&gt;Ocean (core)- CKPool (solo)&lt;/em&gt;
example in the top-left should be a good baseline for the Merkle branch
similarity between likely well-connected but unmodified Bitcoin Core nodes.
These two pools have a similarity score of 1%. While branch position 1 matches
more than 80% of the time, around position 7 the branches only match in less
than 10% of the cases. The last branch positions never match. This is likely
caused by small, normally occurring differences in the pools mempools.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Poolin-Antpool&lt;/em&gt; comparison in the top-right shows the extreme case when the
Merkle branches almost always completely match. These two pools have a
similarity score of 99%. For nearly all positions, 99% or more of the branches
across all branch positions match.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;ViaBTC-Ocean (datafree)&lt;/em&gt; example in the bottom-left shows the other extreme
when the Merkle branches rarely match. These pools have a similarity score
of 0%. ViaBTC is known for its custom transaction prioritizations through its
&lt;a href="https://www.viabtc.com/tools/txaccelerator"&gt;transaction accelerator&lt;/a&gt; and the
Ocean &lt;a href="https://ocean.xyz/docs/datafreepolicy"&gt;datafree node policy&lt;/a&gt; filters out
all data-carrying transactions. These block templates are expected to
be as different as it gets. There are very few matches between the branches at
position 1 and no matches at the later positions.&lt;/p&gt;
&lt;p&gt;With a baseline and examples for both extremes, we can better categorize pool
combinations from the low-similarity group. The example in the bottom right shows
&lt;em&gt;Braiins-AntPool&lt;/em&gt; with a similarity score of 32%. The first few branch
positions nearly always match. At positions 8 and 9, around 50% of the branches
still match. At the last positions, often more than 25% of the templates match.
This is significantly better than the baseline and raises the question of why, for
example, the &lt;em&gt;Braiins-AntPool&lt;/em&gt; templates share more transactions than other pool
combinations.&lt;/p&gt;
&lt;p&gt;It has been &lt;a href="https://x.com/mononautical/status/1775165873516667128"&gt;previously&lt;/a&gt; &lt;a href="https://x.com/0xB10C/status/1780611775940972692"&gt;observed&lt;/a&gt; that
AntPool, Braiins, and other pools sometimes prioritize the same low-fee
transactions by putting them at the beginning of the block at the same time. One
explanation could be that the templates are built from two nodes connected to
each other. Both nodes receive the same prioritization. Sometimes their mempools
match, and slight mempool differences cause a lower similarity score.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/12-similiar-templates/relationship-graph-3.drawio.png'
alt='Final pool relationship graph including high- and low-similarity data as well as temporary relationships. Percentages are the average similarity scores.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Final pool relationship graph including high- and low-similarity data as well as temporary relationships. Percentages are the average similarity scores. Pools not included here don&amp;rsquo;t show any significant similarities to other pools.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Looking at the network share of these nine pools can help to put these relationships
into perspective. In the &lt;a href="https://archive.ph/ZY62K"&gt;past month&lt;/a&gt;, AntPool mined
24.8% of the blocks, Binance Pool 2.86%, SpiderPool 2.67%, SecPool 2.14%, Luxor
1.89%, Braiins 1.7%, BTC.com 0.82%, Poolin 0.4%, and UltimusPool 0.31%. In
contrast, Foundry mined 31.31% of the blocks.
The high-similarity pool combination of AntPool-BTC.com-Poolin has therefore a
network share of 26.02%. Braiins and Ultimus Pool together have a share of 2%.
All pools together have had a 37.6% share of the network hashrate over the past
month, which is significantly larger than Foundry&amp;rsquo;s share. That said, while the
block templates might be unusually similar between some of these pools, and some
pools might be engaging as proxy pools for others here and there, it&amp;rsquo;s
not proven that there is a single entity behind these nine pools. Yet, it adds
more data points to the discussion around mining pool centralization.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://x.com/mononautical"&gt;@mononautical&lt;/a&gt; &lt;a href="https://x.com/mononautical/status/1777686545715089605"&gt;reported about&lt;/a&gt;
coinbase transaction outputs from multiple pools, including AntPool, Braiins,
Binance Pool, SecPool, and F2Pool spent in the same transaction. Yet, the F2Pool
templates show no similarity to any of the other pools. However, it is known that
F2Pool &lt;a href="https://b10c.me/observations/11-invalid-blocks-783426-and-784121/"&gt;runs its own nodes&lt;/a&gt;
and &lt;a href="https://b10c.me/observations/09-non-standard-transactions/"&gt;builds its own block templates&lt;/a&gt;.
It&amp;rsquo;s expected that F2Pool templates don&amp;rsquo;t match with, for example, AntPools
templates.&lt;/p&gt;
&lt;p&gt;More insights can be extracted from the stratum job data. For
example, looking at the job update arrival time might be interesting as the jobs
from some pools arrive at the same time. It might be interesting to look at the
custom transaction prioritizations and where these match across pools. The
coinbase output value could be analyzed. Pools often having a similar coinbase
output value might be peering with each other. Generally, it might make sense to
directly peer two otherwise independent Bitcoin Core nodes to see how similar
the templates get.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;My post mentioned EMCDPool and RawPool. According to
mempool.space, &lt;a href="https://mempool.space/mining/pool/emcdpool"&gt;EMCDPool&lt;/a&gt; mined
its last block over a year ago and RawPool has been &lt;a href="https://mempool.space/mining/pool/rawpool"&gt;inactive for three
years&lt;/a&gt;. While their stratum endpoints still publish jobs, I didn&amp;rsquo;t include them in this analysis.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;A collection showing all possible 276 pool combinations can be found
&lt;a href="https://b10c.me/data/observations/12-similiar-templates/all-where-do-the-templates-match.png"&gt;here&lt;/a&gt; (7.1 MB, ~3500x12000px).&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Monitoring Bitcoin P2P network attacks and anomalies with eBPF and USDT tracepoints</title><link>https://b10c.me/talks/022-ebpf-summit-2024/</link><pubDate>Wed, 11 Sep 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/022-ebpf-summit-2024/</guid><description>&lt;p&gt;I spoke at the &lt;a href="https://ebpf.io/summit-2024/"&gt;eBPF Summit 2024&lt;/a&gt; about how I&amp;rsquo;m using eBPF and the USDT tracepoints in Bitcoin Core to extract P2P network events to use them for, for example, anomaly detection.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=MQ8drkp-sHY"&gt;Recording (YouTube)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Bitcoin Core Development</title><link>https://b10c.me/talks/021-bitcoin-core-development-burg/</link><pubDate>Sat, 31 Aug 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/021-bitcoin-core-development-burg/</guid><description>&lt;p&gt;At the &amp;ldquo;Bitcoin Burg Academy&amp;rdquo; I talked about Bitcoin Core development to an non-technical audience.
Often when I talk to non-technical Bitcoiners, they don&amp;rsquo;t really know much about the Bitcoin Core open-source software project but are very interested to learn more about it.
In this talk, I covered the basics starting from the beginning with Satoshi, to the early years without Satoshi, to how Bitcoin Core development works now.
I detail how Bitcoin Core developers communicate, I briefly introduce GitHub, Issues, PRs, and review, and I discuss what jobs maintainers have and how Developer Funding works.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.google.com/presentation/d/1w1DsEPPlc96gynhFXYvpmxOmKjttaEWjlfT6zFzTTeE/edit?usp=sharing"&gt;Slides (Google Slides)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>OpenSats Work-Log 2</title><link>https://b10c.me/funding/2024-opensats-report-2/</link><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2024-opensats-report-2/</guid><description>&lt;p&gt;This is a copy of the 2nd work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications"&gt;Publications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I’ve been looking at mining pool behavior during forks while building out the stratum-observer stratum job collecting (see below). I regularly covered forks on mainnet with tweets (such as these &lt;a href="https://x.com/0xB10C/status/1804143652962185531"&gt;1&lt;/a&gt;, &lt;a href="https://x.com/0xB10C/status/1803082081385246738"&gt;2&lt;/a&gt;, and &lt;a href="https://x.com/0xB10C/status/1795876514074079615"&gt;3&lt;/a&gt;) and then ended up publishing a short-form &lt;a href="https://www.youtube.com/shorts/AO85Ou3_ob4"&gt;explainer video&lt;/a&gt; and a blog post on &lt;a href="https://b10c.me/blog/014-mining-pool-behavior-during-forks/"&gt;Mining Pool Behavior during Forks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I’ve looked into compact-block reconstructions on mainnet after another bitcoiner reached out asking for stats on them. I noticed that, since most miners have switched to -mempoolfullrbf=1 by default, enabling full-RBF in Bitcoin Core by default would help block propagation. I &lt;a href="https://github.com/bitcoin/bitcoin/pull/30493#issuecomment-2263336312"&gt;commented on this&lt;/a&gt; on the current “enable full-RBF by default” Bitcoin Core pull request.&lt;/li&gt;
&lt;li&gt;I posted an extended version of the &lt;a href="https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052"&gt;compact-block reconstruction stats on delving-bitcoin&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="projects"&gt;Projects&lt;/h3&gt;
&lt;h4 id="peer-observer--infrastructure"&gt;peer-observer &amp;amp; infrastructure&lt;/h4&gt;
&lt;p&gt;To monitor for Bitcoin P2P anomalies and attacks, I run 11 Bitcoin Core “honeynodes” (honeypot nodes) on four continents across three different hosting providers. All nodes have additional monitoring attached that is used to record data and metrics. As leaking the node IP addresses would defeat the purpose of the honeypot, the public interface &lt;a href="https://public.peer.observer/"&gt;https://public.peer.observer/&lt;/a&gt; is redacted. I’ve been providing access to interested and trusted developers and community members on an ad-hoc basis.&lt;/p&gt;
&lt;p&gt;Over the past months, I’ve been mentoring a Summer of Bitcoin mentee as he has been working on some of the initial anomaly detection and alerting setup. I’ve also been supporting him in his journey learning Rust and more about Bitcoin and Bitcoin Core internals.&lt;/p&gt;
&lt;p&gt;Other things include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On going maintenance of the nodes and infrastructure. For example, coordinating with the MIT DCI to increase disk size on one of the nodes, or setting up new ARM instances with more CPU and disk IO compared to the previous instances.&lt;/li&gt;
&lt;li&gt;I build a tool that converts the peer-observer events (e.g. nodes connecting and disconnecting, P2P messages being send and received) to messages published on a websocket. This allows for realtime visualizatioins such as &lt;a href="https://x.com/0xB10C/status/1797904155593548273"&gt;https://x.com/0xB10C/status/1797904155593548273&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Working with my mentee, we noticed a few bugs setting his infrastructure up. For example, &lt;a href="https://github.com/0xB10C/peer-observer/pull/22"&gt;https://github.com/0xB10C/peer-observer/pull/22&lt;/a&gt;, which we fixed together&lt;/li&gt;
&lt;li&gt;Build out further dashboards&lt;/li&gt;
&lt;li&gt;Set up a Bitcoin Core v0.21.1 node to monitor eventual exploitation of &lt;a href="https://bitcoincore.org/en/2024/07/31/disclose-addrman-int-overflow/"&gt;Bitcoin Core :: Disclosure of remote crash due to addr message spam&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="stratum-observer-step-1"&gt;stratum-observer (step 1)&lt;/h4&gt;
&lt;p&gt;Following my &lt;a href="https://x.com/0xB10C/status/1780611768081121700"&gt;tweet&lt;/a&gt; about Antpool proxy pools, I started to build out proper tooling to observe stratum jobs by mining pools. In the past months, I primarily worked on step 1, which is support for stratum v1, a simple and WIP &lt;a href="https://stratum.miningpool.observer/"&gt;website&lt;/a&gt; showing stratum-job updates in real-time and collecting data on stratum jobs into a database for later analysis.&lt;/p&gt;
&lt;p&gt;As other realtime monitoring solutions have popped up (e.g. stratum.work), I’ve decided to hold off on publishing my WIP website until it’s in better shape. The plan for step 2 is to build out support for stratum v2 and to expose more metrics and information on the frontend.&lt;/p&gt;
&lt;h4 id="transactionfee-info"&gt;transactionfee-info&lt;/h4&gt;
&lt;p&gt;REDACTED&lt;/p&gt;
&lt;p&gt;I picked up work on a new open-source Rust backend for transactionfee.info. The old Go backend (see &lt;a href="https://b10c.me/projects/transactionfee-info-2020-version/"&gt;transactionfee.info (2020 version)&lt;/a&gt;) is closed source and I’m not planning to maintain it any further. Over the last weeks, I tried to bring the new backend closer to being ready to replace the old backend. This will also allow interested contributors to contribute new charts and new metrics.&lt;/p&gt;
&lt;h4 id="bitcoin-core"&gt;Bitcoin Core&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Over the last few months, the Bitcoin Core guix.sigs repsoistory, containing signatures for release builds from developers, had problems with mismatching build hashes. These were only detected after being merged and used together with a release. I &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1207"&gt;had build a CI job that reports mismatches&lt;/a&gt;, however, it turned out that getting the CI to comment on a PR was harder as initially thought. Together with willcl-ark, we &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1263"&gt;fixed the automated GitHub action&lt;/a&gt; to comment a SHASUM summary on PRs as soon as they are opened.&lt;/li&gt;
&lt;li&gt;I’ve been keeping my currently open PRs rebased and ready for review.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="misc"&gt;misc&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;I reworked the fork-observer front-end animations for new blocks and forks in &lt;a href="https://github.com/0xB10C/fork-observer/pull/41"&gt;https://github.com/0xB10C/fork-observer/pull/41&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I’ve been keeping my &lt;a href="https://github.com/0xB10C/nix"&gt;collection of Nix modules and packages for software I&amp;rsquo;ve written&lt;/a&gt; up-to-date. I use this for my own setups. I started adding test for newly added packages and hope to add more tests for the older packages soon.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve been reviewing nixpkgs PRs for Bitcoin related software - e.g. &lt;a href="https://github.com/NixOS/nixpkgs/pull/322524"&gt;https://github.com/NixOS/nixpkgs/pull/322524&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="plans-for-next-quarter"&gt;&lt;em&gt;Plans for Next Quarter?&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;start to work on stratum-observer step 2&lt;/li&gt;
&lt;li&gt;continue working with my Summer of Bitcoin mentee on peer-observer alerts and anomaly detection&lt;/li&gt;
&lt;li&gt;continue to work on the projects mentioned above&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Mining Pool Behavior during Forks</title><link>https://b10c.me/blog/014-mining-pool-behavior-during-forks/</link><pubDate>Mon, 15 Jul 2024 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/014-mining-pool-behavior-during-forks/</guid><description>&lt;p&gt;I have recently been looking at mining pool behavior during forks. Which block
does a pool choose to mine on during a fork? Do they behave rationally and mine
on their own block? In this post, I&amp;rsquo;ll detail the mining pool behavior during
forks and give some recent examples of pool behavior.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I&amp;rsquo;ve also published a short-form video&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; on this topic on &lt;a href="https://x.com/0xB10C/status/1811390920744468502"&gt;Twitter&lt;/a&gt; and &lt;a href="https://www.youtube.com/shorts/AO85Ou3_ob4"&gt;YouTube&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Bitcoin, a &lt;em&gt;blockchain&lt;/em&gt; fork usually happens when two mining pools find a
block based on the same previous block at roughly the same time. The fork is
usually resolved with the next block building on top of one of the two fork-blocks.
One block becomes part of the active chain (&lt;em&gt;wins&lt;/em&gt;) while the other one becomes
stale (&lt;em&gt;loses&lt;/em&gt;).&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/a-basic-fork.png'
alt='A fork at height 45342: Pool A and Pool B both mined a block at height 45342. Pool B mined block 45343 on top of its own block and won. Pool A&amp;#39;s block 45343 became stale.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;strong&gt;A fork at height 45342&lt;/strong&gt;: Pool A and Pool B both mined a block at height 45342. Pool B mined block 45343 on top of its own block and won. Pool A&amp;rsquo;s block 45343 became stale.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;During a fork, the game theory is as follows: Pools that mined one of the fork
blocks always want to mine on their own block and never on the competing
fork-block. The chance of finding a block is equal to the share of network
hashrate the pool has, and does not depend on who found the previous block.
A pool with 5% of the network hashrate has a 5% chance to find a block on the
competing pool&amp;rsquo;s block and a 5% chance of finding a block on his own block.
However, mining on its own block, the pool can find the winning block and yield
two blocks, the fork-block and the winning-block. Mining on the competing block,
it can only yield the winning-block.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/game-theory.webp'
alt='Pool B yields only one block when mining on top of Pool A&amp;#39;s block. However, it can yield two blocks when mining on its own block.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Pool B yields only one block when mining on top of Pool A&amp;rsquo;s block. However, it can yield two blocks when mining on its own block.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Pools that mined neither of the fork-blocks can freely choose which block to
build on. By default, they usually end up mining on the block they validate
first. Their chance to find a block is equal to their network hashrate.
It does not matter which fork-block other pools are mining on.&lt;/p&gt;
&lt;h2 id="recent-examples"&gt;Recent Examples&lt;/h2&gt;
&lt;p&gt;The block pools are mining on is sent along with the mining jobs
the pools public stratum servers publish. To analyze pool behavior, I&amp;rsquo;ve been
recording the stratum jobs during forks. Note that there are possibly multiple
public or even private stratum endpoints that could publish different jobs.
The data I have might be incomplete and not show the full picture.&lt;/p&gt;
&lt;h3 id="viabtc-and-antpool-at-height-848860"&gt;ViaBTC and AntPool at height 848860&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Originally posted &lt;a href="https://x.com/0xB10C/status/1804143652962185531"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;During the fork at height 848860 between ViaBTC and AntPool, AntPool started
off by briefly mining an empty-block-job&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, a mining job with no transactions in the block, on their own block but then switched to mining on ViaBTC&amp;rsquo;s block. By switching to ViaBTC&amp;rsquo;s block they abandoned their
block and lost the chance to find the fork- and the winning-block. However, it
turned out that Foundry was mining on the AntPool block and found a block on it.
Despite abandoning its block, AntPool was able to include one block in the
active chain.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/848860.png'
alt='ViaBTC and AntPool at height 848860'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;strong&gt;ViaBTC and AntPool at height 848860&lt;/strong&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="antpool-and-foundry-at-height-848968"&gt;AntPool and Foundry at height 848968&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Originally posted &lt;a href="https://x.com/0xB10C/status/1804472014788313320"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;108 blocks later, AntPool and Foundry both mined a block at height
848968 resulting in another fork. Again, AntPool started off with an
empty-block-job mining on their own block before switching to Foundry&amp;rsquo;s block &lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.
Foundry found a block 848969 causing AntPool&amp;rsquo;s block 848968 to become stale.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/848968.png'
alt='AntPool and Foundry at height 848968'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;strong&gt;AntPool and Foundry at height 848968&lt;/strong&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="antpool-and-foundry-at-height-851170"&gt;AntPool and Foundry at height 851170&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Originally posted &lt;a href="https://x.com/0xB10C/status/1810247962594824500"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, both Foundry and AntPool found a block at height 851170 and AntPool
starts off with an empty-block-job&lt;sup id="fnref2:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; on its own block before switching to Foundry&amp;rsquo;s
block. AntPool ended up finding a block on top of Foundry&amp;rsquo;s block and both AntPool
and Foundry end up with one block each. If AntPool had stayed on their own
block, they could have ended up mining both blocks.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/851170.png'
alt='AntPool and Foundry at height 851170'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;strong&gt;AntPool and Foundry at height 851170&lt;/strong&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="foundry-and-antpool-at-height-848477"&gt;Foundry and AntPool at height 848477&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Originally posted &lt;a href="https://x.com/0xB10C/status/1803082081385246738"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The fork at height 848477 between Foundry and AntPool was different. All pools,
including Foundry, started mining on top of AntPool&amp;rsquo;s block 848477. About 30 seconds&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;
in, Foundry switched to a different block at height 848477 mined by Foundry, and they
end up finding block 848478 building on top of it. In this case, Foundry successfully
switched to their own block, yielding two blocks in the active-chain while AntPool&amp;rsquo;s
block became stale.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/014-mining-pool-game-theory-during-forks/848477.png'
alt='Foundry and AntPool at height 848477'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;strong&gt;Foundry and AntPool at height 848477&lt;/strong&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="why-isnt-antpool-mining-on-their-own-blocks"&gt;Why isn&amp;rsquo;t AntPool mining on their own blocks?&lt;/h3&gt;
&lt;p&gt;The question of why AntPool, who have been around since 2015 and have
mined 10% of all Bitcoin blocks in existence, are not switching to their own
blocks during forks remains open. Aren&amp;rsquo;t forks frequent enough to bother?
Probably, calling &lt;code&gt;preciousblock&lt;/code&gt; along with the &lt;code&gt;submitblock&lt;/code&gt;
RPC when submitting a newly found block to their nodes would do the trick.
Maybe some part of their pool software is incompatible with this?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2024-08-05: A previous version of this blog post was called &amp;ldquo;Mining Pool Game Theory during Forks&amp;rdquo;.
I&amp;rsquo;ve since changed the title to &amp;ldquo;Mining Pool Behavior during Forks&amp;rdquo; as I think
this better reflects the contents.&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/AO85Ou3_ob4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;AntPool often mines an empty-block-job for 30 seconds after
a new block. These empty jobs aren&amp;rsquo;t based on a block template generated by a
Bitcoin Core node and probably originate from a custom mining job creation
software. When the job creation software learns about a new AntPool block, it
immediately publishes an empty-block-job for it and the block is probably
submitted to a Bitcoin Core node. Probably at around the same time, the Foundry
block arrives and is validated on the Bitcoin Core node. A Bitcoin Core node,
by default, will mine on the block it validated first. This means that if they don&amp;rsquo;t
manually switch, Foundry&amp;rsquo;s blocks must have reached the nodes first.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Foundry implemented a &amp;ldquo;switch-to-own-block&amp;rdquo; logic and my guess is that they didn&amp;rsquo;t bother, or forgot, to implement sending a new mining job for it. The switch happened with the next job update they sent out. Foundry’s job updates happen every 30 seconds.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>OpenSats Work-Log 1</title><link>https://b10c.me/funding/2024-opensats-report-1/</link><pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2024-opensats-report-1/</guid><description>&lt;p&gt;This is a copy of the 1st work-log I sent to &lt;a href="https://opensats.xyz"&gt;OpenSats&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2024-opensats-grant/"&gt;my LTS grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;h1 id="how-did-you-spend-your-time"&gt;&lt;em&gt;How did you spend your time?&lt;/em&gt;&lt;/h1&gt;
&lt;h3 id="publications"&gt;Publications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://b10c.me/observations/10-viabtc-blocks-without-witness-data/"&gt;ViaBTC&amp;rsquo;s mutated blocks without witness data&lt;/a&gt;: Last year, errors like ERROR: AcceptBlock: bad-witness-nonce-size in the Bitcoin Core debug.log caused confusion. As these were coming up again, I took a look into why they’re happening. Turns out this is a problem with ViaBTC’s pool implementation and nothing to be done on the Bitcoin Core side.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/blog/012-viabtc-spv-vulnerability-disclosure/"&gt;Vulnerability Disclosure: Wasting ViaBTC&amp;rsquo;s 60 EH/s hashrate by sending a P2P message&lt;/a&gt;: While looking at the code of the ViaBTC pool server to learn more about the mutated blocks, I noticed a security vulnerability and responsibly disclosed it.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/blog/013-one-year-update-on-linkinglion/"&gt;Update on LinkingLion: Reduced activity and a statement by LionLink Networks&lt;/a&gt;: LionLink, the ISP where the &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion&lt;/a&gt; connections originate from, issued a statement and subsequently the connections from LinkingLion dropped for a couple of hours before starting again. LinkingLion is still active and possibly linking IP addresses to Bitcoin transaction broadcasts.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/observations/11-invalid-blocks-783426-and-784121/"&gt;Invalid F2Pool blocks 783426 and 784121 (April 2023)&lt;/a&gt;: F2Pool mined two invalid blocks in early April 2023. I still had notes from looking into these and decided to publish them.&lt;/li&gt;
&lt;li&gt;Following &lt;a href="https://twitter.com/mononautical/status/1777686545715089605"&gt;mononauts post&lt;/a&gt; about major mining pools sharing a custodian and directly mining to that custodian, I looked into the stratum jobs they send and found that the BTC.com pool, Binance pool, Poolin, EMCD, Rawpool, and possibly Braiins have exactly the same template and custom transaction prioritization as AntPool.
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/0xB10C/status/1780611768081121700"&gt;https://twitter.com/0xB10C/status/1780611768081121700&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/0xB10C/status/1783126815822717080"&gt;https://twitter.com/0xB10C/status/1783126815822717080&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;During the halving, I ran a &lt;a href="https://www.youtube.com/live/C6D9OEjnWmA?feature=shared&amp;amp;t=846"&gt;Bitcoin Halving Monitoring Stream&lt;/a&gt; with the goal to keep track of possible selfish mining and reorgs due to the anticipated high reward of mining block 840000. While there weren’t any reorgs, there was a possible &lt;a href="https://twitter.com/0xB10C/status/1781477794762965193"&gt;reorg attempt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="projects"&gt;Projects&lt;/h3&gt;
&lt;h4 id="peer-observer--infrastructure"&gt;peer-observer &amp;amp; infrastructure&lt;/h4&gt;
&lt;p&gt;To monitor for Bitcoin P2P anomalies and attacks, I now run 12 Bitcoin Core “honeynodes”
(honeypot nodes) on four continents across three different hosting providers. All nodes
have additional monitoring attached that is used to record data and metrics. As leaking
the node IP addresses would defeat the purpose of the honeypot, the public interface
&lt;a href="https://public.peer.observer/"&gt;https://public.peer.observer/&lt;/a&gt; is redacted. I’ve been
providing access to interested and trusted developers and community members on an
ad-hoc basis.&lt;/p&gt;
&lt;p&gt;I’ll be mentoring someone as part of my &lt;a href="https://www.summerofbitcoin.org/project-ideas-details/bitcoin-core/r/recmJ4Yco7njzScxx"&gt;Summer of Bitcoin project&lt;/a&gt;
“peer-observer: Anomaly detection and alerting for Bitcoin Core P2P events”. The goal is
to extend peer-observer with proper alerting and to experiment a bit with proper anomaly
detection.&lt;/p&gt;
&lt;p&gt;The infrastructure work for peer-observer includes (but not limited to):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up four low powered ARM nodes in a new datacenter.&lt;/li&gt;
&lt;li&gt;Decommissioning of two nodes used during early development in 2022 and 2023&lt;/li&gt;
&lt;li&gt;Enabling detailed debug logging on the nodes and daily log rotation of debug.log files&lt;/li&gt;
&lt;li&gt;Automated FTP backup of old debug.log files for future use&lt;/li&gt;
&lt;li&gt;Use client certificate authentication instead of basic auth&lt;/li&gt;
&lt;li&gt;Update nodes to Bitcoin Core 27.0rc1 release candidate as well as 26.1 and 25.2 release candidates&lt;/li&gt;
&lt;li&gt;Rework Grafana dashboards and add a dashboard playlist for TV mode&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="fork-observer"&gt;fork-observer&lt;/h4&gt;
&lt;p&gt;After &lt;a href="https://twitter.com/0xB10C/status/1757875766635757689"&gt;noticing&lt;/a&gt; and
&lt;a href="https://github.com/btcsuite/btcd/issues/2121"&gt;reporting&lt;/a&gt; an issue with a stuck
btcd node connected to my fork-observer instance, I &lt;a href="https://github.com/0xB10C/fork-observer/pull/34"&gt;added an RSS feed for
lagging nodes&lt;/a&gt; (to be able to
easily alert on stuck nodes) and &lt;a href="https://github.com/0xB10C/fork-observer/pull/36"&gt;added an RSS feed for offline nodes&lt;/a&gt;.
Also, &lt;a href="https://github.com/0xB10C/fork-observer/pull/37"&gt;exposed and started showing node implementation&lt;/a&gt;
along with some &lt;a href="https://github.com/0xB10C/fork-observer/pull/38"&gt;general refactoring&lt;/a&gt;.
For the halving stream, I &lt;a href="https://github.com/0xB10C/fork-observer/pull/39"&gt;added a fullscreen&lt;/a&gt; mode.&lt;/p&gt;
&lt;h4 id="bitcoin-core"&gt;Bitcoin Core&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;I tested hebasto’s proposed Bitcoin Core build system change from CMake to autotools on NixOS: &lt;a href="https://github.com/hebasto/bitcoin/issues/121"&gt;https://github.com/hebasto/bitcoin/issues/121&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I opened PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/29636"&gt;#29636&lt;/a&gt;, &lt;a href="https://github.com/bitcoin/bitcoin/pull/29877"&gt;#29877&lt;/a&gt;, &lt;a href="https://github.com/bitcoin/bitcoin/pull/29549"&gt;#29549&lt;/a&gt;, have been keeping &lt;a href="https://github.com/bitcoin/bitcoin/pull/26593"&gt;#26593&lt;/a&gt; and &lt;a href="https://github.com/bitcoin/bitcoin/pull/25832"&gt;#25832&lt;/a&gt; up-to-date and &lt;a href="https://github.com/bitcoin/bitcoin/pull/28998"&gt;28998&lt;/a&gt; was merged.&lt;/li&gt;
&lt;li&gt;I’ve also been experimenting with a possible continuous benchmarking solution for the Bitcoin Core CI. See &lt;a href="https://github.com/bitcoin/bitcoin/issues/27284"&gt;27284&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I attended the CoreDev meeting in Berlin in early April and presented my peer-observer work. I also offered to help other developers with data/stats/insights for their proposals or PRs. This resulted in five developers reaching out during and after the event requesting data (mempool data, network-adjusted time data for &lt;a href="https://github.com/bitcoin/bitcoin/pull/29623"&gt;29623&lt;/a&gt;, benchmarking &lt;a href="https://github.com/bitcoin/bitcoin/pull/29491"&gt;#29491&lt;/a&gt;, non-standard tx stats for the great consensus cleanup, orphan transaction stats and tooling, …).&lt;/li&gt;
&lt;li&gt;GUIX builds and hash mismatch tooling: After submitting my reproducible GUIX build signatures for Bitcoin Core 25.2rc2, 27.0rc1, and 27.0 a binary hash-mismatch was noticed. This could be &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1201#issuecomment-2070385083"&gt;tracked down&lt;/a&gt; to me switching to a new build setup. As we don’t have any alerting for hash-mismatches, I &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1207"&gt;PR’d a CI job&lt;/a&gt; that comments a summary of the hashes on each PR. The goal is to learn about future mismatches as early as possible to be able to investigate them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="misc"&gt;misc&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;I’ve been keeping my &lt;a href="https://github.com/0xB10C/nix"&gt;collection of Nix modules and packages for software I&amp;rsquo;ve written&lt;/a&gt; up-to-date. I use this for my own setups. However, till I get around to adding tests and further service hardening, I don’t recommend anyone else using it.&lt;/li&gt;
&lt;li&gt;I need a quick and dirty transaction dependency visualization and build &lt;a href="https://github.com/0xB10C/tx-family-tree"&gt;tx-family-tree&lt;/a&gt;. A live version can be found here: &lt;a href="https://0xb10c.github.io/tx-family-tree/"&gt;https://0xb10c.github.io/tx-family-tree/&lt;/a&gt; (this loads quite slow and isn’t really meant to be used)&lt;/li&gt;
&lt;li&gt;I’ve started a &lt;a href="https://bitcoin-data.github.io/non-standard-transactions/"&gt;dataset of non-standard transactions&lt;/a&gt; I’ll sporadically update. Generated with &lt;a href="https://github.com/0xB10C/find-non-standard-tx"&gt;https://github.com/0xB10C/find-non-standard-tx&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="plans-for-next-quarter"&gt;&lt;em&gt;Plans for Next Quarter?&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;continue to work on the projects mentioned above&lt;/li&gt;
&lt;li&gt;build out a stratum job monitoring tool to provide everyone access to the pool&amp;rsquo;s job information (inspired by &lt;a href="https://twitter.com/0xB10C/status/1780611768081121700"&gt;https://twitter.com/0xB10C/status/1780611768081121700&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Start to work with my Summer of Bitcoin mentee on peer-observer alerts and anomaly detection&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Invalid F2Pool blocks 783426 and 784121 (April 2023)</title><link>https://b10c.me/observations/11-invalid-blocks-783426-and-784121/</link><pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/11-invalid-blocks-783426-and-784121/</guid><description>&lt;p&gt;My notes on the two &lt;code&gt;bad-blk-sigops: too many sigops&lt;/code&gt; invalid blocks, 783426 and 784121, mined by F2Pool in April 2023.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;On April 1st, 2023, F2Pool mined an invalid block at height 783426. Bitcoin Core nodes rejected the block with the reason &lt;code&gt;bad-blk-sigops&lt;/code&gt; and the note &lt;code&gt;too many sigops&lt;/code&gt;. On April 6th, 2023, F2Pool mined another &lt;code&gt;bad-blk-sigops&lt;/code&gt; block at height 784121. Mining an invalid block with a valid proof-of-work is an expensive mistake. F2Pool lost more than USD $300k in block rewards with these two invalid blocks.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: ConnectBlock(): too many sigops
ERROR: ConnectTip: ConnectBlock 00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4 failed, bad-blk-sigops
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While invalid blocks are normally not relayed on the Bitcoin P2P network, these blocks were likely announced as BIP-152 compact blocks. To ensure fast block propagation, compact blocks are relayed across BIP-152 high-bandwidth connections before they are fully validated.&lt;/p&gt;
&lt;p&gt;BitMex Research &lt;a href="https://twitter.com/BitMEXResearch/status/1642151592609607685"&gt;reported&lt;/a&gt; about the first invalid block and &lt;a href="https://twitter.com/provoost"&gt;Sjors Provoost&lt;/a&gt; began analyzing it by &lt;a href="https://twitter.com/provoost/status/1642157884552146945"&gt;counting sigops&lt;/a&gt;. A sigop is a Bitcoin script opcode that performs a signature check. For example, &lt;code&gt;OP_CHECKSIG&lt;/code&gt;, &lt;code&gt;OP_CHECKSIGVERIFY&lt;/code&gt;, &lt;code&gt;OP_CHECKMULTISIG&lt;/code&gt;, and &lt;code&gt;OP_CHECKMULTISIGVERIFY&lt;/code&gt;. As signature checks are computationally expensive, blocks are limited to 80000 sigops. The sigops in, for example, &lt;code&gt;OP_IF ... OP_ENDIF&lt;/code&gt; branches are counted even if they aren&amp;rsquo;t executed. Similarly, sigops in coinbase inputs are counted but won&amp;rsquo;t ever be executed &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;With the activation of SegWit, the sigops limit was &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#sigops"&gt;raised from 20000 to 80000&lt;/a&gt;. At the same time, the &lt;em&gt;cost&lt;/em&gt; of sigops in pre-SegWit scripts (e.g., P2PKH, P2SH, ..) was quadrupled. For example, a &lt;code&gt;OP_CHECKSIG&lt;/code&gt; in a &lt;code&gt;P2PKH&lt;/code&gt; output is counted as 4 sigops but was counted as 1 sigop before SegWit activation. In &lt;code&gt;P2WSH&lt;/code&gt; a &lt;code&gt;OP_CHECKSIG&lt;/code&gt; is counted as 1 sigop.&lt;/p&gt;
&lt;p&gt;Sjors &lt;a href="https://twitter.com/provoost/status/1643682991133130752"&gt;counted 80003&lt;/a&gt; sigops for the first invalid block. Three sigops more than the limit of 80000 sigops allows. While some joked the first block on April 1st was an &amp;ldquo;&lt;a href="https://twitter.com/salvatoshi/status/1642161204683501568"&gt;April F2Pool&amp;rsquo;s block&lt;/a&gt;&amp;rdquo;, on April 6th the second invalid F2Pool block arrived and made it clear that this wasn&amp;rsquo;t a one-time event nor an expensive April F2Pool&amp;rsquo;s joke.&lt;/p&gt;
&lt;p&gt;The second invalid block also counted 80003 sigops. At this point, speculation arose that F2Pool was likely running custom software with a bug. Sjors &lt;a href="https://twitter.com/provoost/status/1644067075491614735"&gt;guessed&lt;/a&gt; that F2Pool didn&amp;rsquo;t correctly count the sigops of the coinbase transaction. In a conversation with an F2Pool engineer, I learned that F2Pool had an old patch to Bitcoin Core, which reduced the sigops reserved for the coinbase transaction to a single sigop. This should probably act as a block-building optimization in cases where the sigops limit is the limiting factor to including a high-fee transaction. Before SegWit, 100 sigops were reserved for the coinbase, and as part of the &lt;a href="https://github.com/bitcoin/bitcoin/commit/2b1f6f9ccf36f1e0a2c9d99154e1642f796d7c2b#diff-6bf9d2e37d503f2f3381cd3a41c26e0b9670f26c2cd4571e98d2af9e3767c1a1R118"&gt;SegWit changes&lt;/a&gt;, &lt;code&gt;nBlockSigOpsCost&lt;/code&gt; was increased to 400 sigops.&lt;/p&gt;
&lt;p&gt;Reserving only a single sigop in the coinbase was fine before SegWit when using a P2PKH output as the &lt;code&gt;OP_CHECKSIG&lt;/code&gt; in the P2PKH output was counted as one sigop. After the activation of SegWit, F2Pool would either have to increase the reserved sigop count from one sigop to four sigops or switch to a P2WPKH output (no sigops) or a P2WSH output with a single sigop &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Since increasing the coinbase reserved sigops count, F2Pool hasn&amp;rsquo;t mined another invalid block. However, they now mine blocks with two P2PKH outputs in the coinbase&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. As these both cost 4 sigops, if they had hard-coded their reserved sigop count to 4, adding an extra P2PKH coinbase output might have caused an invalid block again.&lt;/p&gt;
&lt;p&gt;The F2Pool engineer told me that F2Pool now reserves 100 sigops for the coinbase (400 is the default). This can be observed on-chain. In the recent past, F2Pool&amp;rsquo;s blocks 821336, 824444, 824931, and 824934 had a sigop count of 79907. Subtracting the 8 sigops from F2Pool&amp;rsquo;s coinbase from 79907 results in 79899 sigops. With 100 sigops reserved for the coinbase, this equals 79999 sigops. This matches the Bitcoin Core mining algorithm, which &lt;a href="https://github.com/bitcoin/bitcoin/blob/d04324a7056a735c1127ba8ccdc720a16e7281a3/src/node/miner.cpp#L205"&gt;fills blocks to one sigop below the 80000 limit&lt;/a&gt; &lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;table class="table table-sm table-bordered"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;height&lt;/th&gt;
&lt;th&gt;sigops&lt;/th&gt;
&lt;th&gt;pool&lt;/th&gt;
&lt;th&gt;hash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;821336&lt;/td&gt;
&lt;td&gt;79907&lt;/td&gt;
&lt;td&gt;F2Pool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00000000000000000000fa5888308cfcdf29ab782112bc071074081da9fcda3a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;824444&lt;/td&gt;
&lt;td&gt;79907&lt;/td&gt;
&lt;td&gt;F2Pool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;000000000000000000016e1916d655ad057b871654ba1006d267b2f7c8f6ab9f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;824931&lt;/td&gt;
&lt;td&gt;79907&lt;/td&gt;
&lt;td&gt;F2Pool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00000000000000000002ba7ba36fb73ebc989eec6eed842a2e5da5a38e94fed4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;824934&lt;/td&gt;
&lt;td&gt;79907&lt;/td&gt;
&lt;td&gt;F2Pool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0000000000000000000090b2fe804461fd6e2171da8c14c567f96ffdcb905fc0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Other pools, such as Braiins&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; (784123), MARA Pool (e.g. 820460), and ViaBTC (e.g. 824442) mined blocks with 79603 sigops. In these blocks, they use a P2PKH output with 4 sigops. With the default &lt;code&gt;nBlockSigOpsCost&lt;/code&gt; of 400, these blocks also reached the Bitcoin Core miner limit of 79999 sigops (79603 - 4 + 400). Other pools that use a P2SH-P2WSH, P2WPKH, P2WSH, or even P2TR coinbase output, such as Foundry USA (e.g. 783426), Binance Pool (784124), AntPool (790224), Luxor (821174), BTC.com (822717), and SBI Crypto (824443) all mined blocks with 79599 sigops in the recent past. Here, the coinbase output does not have sigops, and they use the default coinbase reserved sigops count of 400.&lt;/p&gt;
&lt;p&gt;An interesting case is the Ocean.xyz pool. Here, the largest miners are paid directly to the miner-specified address in the pool&amp;rsquo;s coinbase output. Thus, their coinbase sigops count is based on which addresses the miners specify. The pool only allows P2PKH, P2SH, SegWit, and Taproot addresses. If they allowed raw P2MS scripts, an attacker could fill the coinbase with 5 P2MS outputs having 80 sigops each to exhaust the default sigops reserved for the coinbase. This would produce an invalid block if the transactions in the block would have more than 79600 sigops. Similarly, 100 P2PKH outputs with 4 sigops each are required to exhaust the sigops reserved for the coinbase. Currently, all but one Ocean block have 20 coinbase outputs, which includes an OP_RETURN SegWit commitment and their pool fee output. At maximum, there were two P2PKH outputs with 4 sigops each in, for example, block 829513.&lt;/p&gt;
&lt;p&gt;Looking at the valid blocks between height 760000 (2022-10-23) and 836428 (‎2024-03-26), there are only 99 blocks with more than 70000 sigops. In median, in this time frame, the blocks had 10348 sigops with the 25th percentile having 6407 and the 75th percentile having 13950 sigops.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/11-f2pool-invalid-blocks/sigops-per-block.png'
alt='Block sigops per height'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Block sigops per height&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If you found this interesting, you might also like my notes on the &lt;a href="https://b10c.me/observations/07-invalid-block-809478/"&gt;invalid MARA Pool block 809478&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;For future reference, the invalid block at height 783426 had the hash &lt;code&gt;00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4&lt;/code&gt; (&lt;a href="https://b10c.me/data/observations/11-f2pool-invalid-blocks/00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4-header.hex"&gt;header&lt;/a&gt;, &lt;a href="https://b10c.me/data/observations/11-f2pool-invalid-blocks/00000000000000000002ec935e245f8ae70fc68cc828f05bf4cfa002668599e4.hex"&gt;full block&lt;/a&gt;). The invalid block at height 784121 had the hash &lt;code&gt;000000000000000000046a2698233ed93bb5e74ba7d2146a68ddb0c2504c980d&lt;/code&gt; (&lt;a href="https://b10c.me/data/observations/11-f2pool-invalid-blocks/000000000000000000046a2698233ed93bb5e74ba7d2146a68ddb0c2504c980d-header.hex"&gt;header&lt;/a&gt;, &lt;a href="https://b10c.me/data/observations/11-f2pool-invalid-blocks/000000000000000000046a2698233ed93bb5e74ba7d2146a68ddb0c2504c980d.hex"&gt;full block&lt;/a&gt;).&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This description is based on Pieter Wuille&amp;rsquo;s answer &lt;a href="https://bitcoin.stackexchange.com/a/117359/63817"&gt;https://bitcoin.stackexchange.com/a/117359/63817&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Mining pools often have large amounts of funds in the same coinbase output address. They might want to wait before switching to a new address version and script type until it&amp;rsquo;s more battle-tested.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;F2Pool&amp;rsquo;s coinbase transactions separate the first few sats of each block to an extra address in an additional P2PKH output. Under the &lt;a href="https://docs.ordinals.com/overview.html#rarity"&gt;Ordinal Theory&lt;/a&gt;, the first Satoshi of each block is an &lt;code&gt;uncommon&lt;/code&gt; sat. There are marketplaces where these outputs are offered and sold for more than their bitcoin value. This is MEV.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;This was surprising to me. During validation and when connecting, Bitcoin Core checks that a block hasn&amp;rsquo;t &lt;strong&gt;more&lt;/strong&gt; (&lt;code&gt;&amp;gt; MAX_BLOCK_SIGOPS_COST&lt;/code&gt;) than 80000 sigops. So a block with 80000 sigops should be allowed. This was introduced in &lt;a href="https://github.com/bitcoin/bitcoin/pull/7600"&gt;PR 7600&lt;/a&gt;. A &lt;a href="https://github.com/bitcoin/bitcoin/pull/7600/files#r67020333"&gt;review comment&lt;/a&gt; noted that this check isn&amp;rsquo;t correct. However, as the sigops limit is rarely ever reached and 400 sigops are reserved for the coinbase, an off-by-one error isn&amp;rsquo;t too big of a deal.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Braiins mined block 824446 with 79599 sigops. They switched to a P2SH-P2WSH output for the coinbase reward.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Update on LinkingLion: Reduced activity and a statement by LionLink Networks</title><link>https://b10c.me/blog/013-one-year-update-on-linkinglion/</link><pubDate>Thu, 28 Mar 2024 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/013-one-year-update-on-linkinglion/</guid><description>&lt;p&gt;This is an update on the LinkingLion entity, presumably linking Bitcoin transactions to IP addresses, I &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;published&lt;/a&gt; about a year ago. Yesterday, LionLink Networks AS issued a &lt;a href="https://LinkingLion.net"&gt;statement&lt;/a&gt; on their non-affiliation with the LinkingLion entity and on the same day, LinkingLion activity significantly dropped.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Exactly a year ago, I published about &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion&lt;/a&gt;, an entity that&amp;rsquo;s presumably linking transactions to IP addresses. The entity has been opening hundreds of connections per minute to my monitoring nodes for the last year.&lt;/p&gt;
&lt;p&gt;On March 27, 2024, LionLink Networks AS, who are announcing the IP addresses ranges used by the entity, published a statement on &lt;a href="https://LinkingLion.net"&gt;LinkingLion.net&lt;/a&gt;. In the statement, the LionLink Networks AS denies any involvement with the LinkingLion entity other than announcing the IP addresses ranges. I have no reason to believe that the AS is involved with LinkingLion entity other being the common factor in announcing the IP addresses used by the LinkingLion entity. I understand that the play on words &amp;ldquo;LinkingLion&amp;rdquo;, being a combination of LionLink (the AS that announces IP addresses used by LinkingLion), &amp;ldquo;Linking&amp;rdquo; as in linking IP addresses to Bitcoin transactions, and &lt;a href="https://arxiv.org/abs/1701.04439"&gt;Dande&lt;strong&gt;lion&lt;/strong&gt;&lt;/a&gt; as privacy protocol helping against transaction linkage of transactions to IP addresses, is, from a public relations standpoint, unfortunately close to their company name.&lt;/p&gt;
&lt;p&gt;To clarify, I don&amp;rsquo;t think the LionLink Networks AS is the LinkingLion entity.&lt;/p&gt;
&lt;p&gt;Coincidentally, I&amp;rsquo;m happy to report that since yesterday (about 2024-03-27 3pm UTC), I&amp;rsquo;m not receiving connections from two of the three IPv4 ranges anymore, and I am seeing reduced numbers of connections from the third IPv4 range. I currently don&amp;rsquo;t have data on the IPv6 range at hand. Maybe the LinkingLion entity is switching IP ranges or adapting their strategy or software? Or it&amp;rsquo;s just a network degradation or outage with coincidental timing?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;209.222.252.0/24&lt;/code&gt;: no new connections&lt;/li&gt;
&lt;li&gt;&lt;code&gt;91.198.115.0/24&lt;/code&gt;: no new connections&lt;/li&gt;
&lt;li&gt;&lt;code&gt;162.218.65.0/24&lt;/code&gt;: reduced connections to ~100/min with previously about 200/min - 400/min&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2604:d500::/32&lt;/code&gt;: no data&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/013-linkinglion-update/total-new-linkinglion-connections-per-subnet.png'
alt='Total new connections from LinkingLion per minute per /24 subnets on 2024-03-27'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Total new connections from LinkingLion per minute per /24 subnets on 2024-03-27&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On the Bitcoin Core side, there&amp;rsquo;s work being done by &lt;a href="https://github.com/vasild"&gt;vasild&lt;/a&gt; and reviewers &lt;a href="https://github.com/bitcoin/bitcoin/pull/29415"&gt;to broadcast transactions via short-lived Tor or I2P connections&lt;/a&gt;, when possible. This should help against entities like LinkingLion. I personally haven&amp;rsquo;t been able to take a detailed look at this proposal.&lt;/p&gt;
&lt;h3 id="update-2024-03-29"&gt;Update 2024-03-29&lt;/h3&gt;
&lt;p&gt;LinkingLion is opening connections again.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/013-linkinglion-update/total-new-linkinglion-connections-per-subnet-2024-03-29.png'
alt='Total new connections from LinkingLion per minute per /24 subnets on 2024-03-29'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Total new connections from LinkingLion per minute per /24 subnets on 2024-03-29&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://opensats.org"&gt;OpenSats&lt;/a&gt; supports &lt;a href="https://opensats.org/blog/vasil-dimov-receives-lts-grant"&gt;Vasil&lt;/a&gt;, myself, and others with a Long-Term-Support grant to work Bitcoin Core and monitoring like these. Consider &lt;a href="https://opensats.org/donate"&gt;donating to OpenSats&lt;/a&gt; if this is work you find worth supporting. Additionally, the &lt;a href="https://dci.mit.edu/"&gt;MIT DCI&lt;/a&gt; is providing me with server infrastructure that I use to monitor for P2P anomalies and attacks, like, for example, the behavior by the LinkingLion entity.&lt;/p&gt;</description></item><item><title>Vulnerability Disclosure: Wasting ViaBTC's 60 EH/s hashrate by sending a P2P message</title><link>https://b10c.me/blog/012-viabtc-spv-vulnerability-disclosure/</link><pubDate>Wed, 20 Mar 2024 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/012-viabtc-spv-vulnerability-disclosure/</guid><description>&lt;p&gt;In January, while investigating &lt;a href="https://b10c.me/observations/10-viabtc-blocks-without-witness-data/"&gt;a misbehaving client on the Bitcoin P2P network&lt;/a&gt;, I found a
vulnerability in ViaBTC&amp;rsquo;s, the fourth largest Bitcoin mining pool, SPV mining code that allowed
a remote attacker to waste ViaBTC&amp;rsquo;s 60 EH/s hashrate by sending a single, crafted Bitcoin P2P
message. I responsibly disclosed this to ViaBTC, and they awarded a bug bounty of 2000 USDT.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Improper Input Validation in the SPV mining module of the &lt;a href="https://github.com/viabtc/viabtc_mining_server"&gt;ViaBTC mining server&lt;/a&gt; (&lt;a href="https://github.com/viabtc/viabtc_mining_server/issues/16"&gt;not fixed on GitHub&lt;/a&gt;, fixed in a closed-source version) allows a remote attacker to waste the pools hashrate by letting it mine on an old block (i.e. DoS) by sending a modified, old block via the P2P network.&lt;/p&gt;
&lt;p&gt;ViaBTC&amp;rsquo;s SPV mining module and Bitcoin P2P client, &lt;em&gt;bitpeer&lt;/em&gt;, did not check the merkle root of
received blocks, allowing an attacker to make ViaBTC&amp;rsquo;s miners mine on an old block by sending a
modified previous block. While ViaBTC fixed this vulnerability in their systems, the open source
&lt;a href="https://github.com/viabtc/viabtc_mining_server"&gt;viabtc_mining_server&lt;/a&gt; is still affected.&lt;/p&gt;
&lt;h4 id="viabtc-mining-server"&gt;ViaBTC Mining Server&lt;/h4&gt;
&lt;p&gt;In March 2021, ViaBTC published a version of its mining server on GitHub. While this repository
hasn&amp;rsquo;t been updated since summer 2021, the underlying software, likely with a few modifications and
patches, is still in use by ViaBTC. The mining server is made up of multiple modules. For the
disclosed vulnerability, the interesting modules are the &lt;em&gt;bitpeer&lt;/em&gt; module and the &lt;em&gt;jobmaster&lt;/em&gt;
module. The &lt;em&gt;jobmaster&lt;/em&gt; module is responsible for producing mining jobs by talking to a Bitcoin Core
node. The &lt;em&gt;bitpeer&lt;/em&gt; module is a Bitcoin P2P client and connects to multiple other nodes on the
Bitcoin P2P network. The &lt;em&gt;bitpeer&lt;/em&gt; client broadcast ViaBTC&amp;rsquo;s newly found blocks to Bitcoin nodes and
notifies the &lt;em&gt;jobmaster&lt;/em&gt; about new blocks received over the network.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/012-viabtc-disclosure/viabtc-mining-server.drawio.png'
alt='Infrastructure diagram of the ViaBTC mining server'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Infrastructure diagram of the ViaBTC mining server. Based on &lt;a href="https://github.com/viabtc/viabtc_mining_server/tree/main?tab=readme-ov-file#overall-structure"&gt;this overview&lt;/a&gt;.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="spv-mining"&gt;SPV mining&lt;/h4&gt;
&lt;p&gt;Mining pools want to minimize the time they spent mining on an old block, when a new one has been
found by another pool. To quickly switch to a new block, ViaBTC&amp;rsquo;s &lt;em&gt;bitpeer&lt;/em&gt; module sends details, like the new block
height and the block hash, to the &lt;em&gt;jobmaster&lt;/em&gt;. If the new block height is &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/jobmaster/jm_job.c#L1303-L1313"&gt;&lt;code&gt;current block height + 1&lt;/code&gt;&lt;/a&gt;, the &lt;em&gt;jobmaster&lt;/em&gt; switches to SPV (Simple Payment Verification) mining mode. In SPV mining mode, a mining job is issued to miners
without having validated the new block. Without having validated the transactions in the previous
block, the SPV mining job can only be an empty block (only a coinbase transaction).&lt;/p&gt;
&lt;h4 id="vulnerability"&gt;Vulnerability&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/bitpeer/bp_peer.c#L510C12-L510C25"&gt;&lt;code&gt;process_block()&lt;/code&gt;&lt;/a&gt; function in &lt;code&gt;bp_peer.c&lt;/code&gt; is called when a new block message arrives over the
P2P network. In this function, it&amp;rsquo;s first checked that the message contains a block header with enough
proof-of-work (higher than the current difficulty requirement). Then, the &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki"&gt;BIP-34&lt;/a&gt; block height is
extracted from the coinbase. If this height is larger than the best know height, the block is send
to the &lt;em&gt;jobmaster&lt;/em&gt; by calling &lt;code&gt;send_block_nitify()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here, checking that the block header has a valid and enough proof-of-work to be an SPV-valid block
defends against most attacks. The cost of mining a block header with enough proof-of-work is high.
For an attacker, it does not make sense to spend that hashrate to attack ViaBTC with an otherwise
invalid block. A rational actor would use the hashrate to mine for them selves.&lt;/p&gt;
&lt;p&gt;However, ViaBTC only checks the header. They don&amp;rsquo;t verify that the transactions in the block match
the merkle root in the header. This means, the coinbase transaction and thus the &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki"&gt;BIP-34&lt;/a&gt; block
height in the coinbase transaction can be modified. Since they use the height in the coinbase to
determine if they should SPV mine, the pool can be tricked into mining on a valid, but old, header
with an arbitrary coinbase transaction attached.&lt;/p&gt;
&lt;h4 id="exploit"&gt;Exploit&lt;/h4&gt;
&lt;p&gt;To exploit this vulnerability, an attacker needs to send a crafted block message to a ViaBTC &lt;em&gt;bitpeer&lt;/em&gt;
client. ViaBTC runs multiple instances of &lt;em&gt;bitpeer&lt;/em&gt; in different data centers distributed around the
globe. Each instance opens connections to multiple listening nodes on the P2P Bitcoin network. On my
nodes I counted zero to two connections from &lt;em&gt;bitpeer&lt;/em&gt; instances per node. In total, I saw seven
different IP addresses, which indicates there are at least seven instances running. These bitpeer
peers can be detected as they always use the same fake user agent of &lt;code&gt;/Satoshi:0.19.0.1/&lt;/code&gt; and only
set the &lt;code&gt;WITNESS&lt;/code&gt; service flag. The IP addresses always belong to AWS-owned IP ranges. Connections
from &lt;em&gt;bitpeer&lt;/em&gt; instances can, for example, be listed with the following command.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ bitcoin-cli getpeerinfo | jq &amp;#39;.[] | select(.subver == &amp;#34;/Satoshi:0.19.0.1/&amp;#34; and .services == &amp;#34;0000000000000008&amp;#34;)&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A block header used for this attack should be from the same difficulty adjustment period to meet the
difficulty requirements. It does not matter which coinbase transaction is chosen. The &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki"&gt;BIP-34&lt;/a&gt;
height in the coinbase input needs to be current network height + 1. To send this block to a bitpeer
instance, the &lt;a href="https://github.com/bitcoin/bitcoin/pull/28287"&gt;recently introduced&lt;/a&gt;, test-only &lt;code&gt;sendmsgtopeer&lt;/code&gt; Bitcoin Core RPC call can be used.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ bitcoin-cli sendmsgtopeer &amp;lt;peer-id&amp;gt; &amp;#34;block&amp;#34; &amp;lt;block hex&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="local-and-remote-verification"&gt;Local and remote verification&lt;/h4&gt;
&lt;p&gt;To make sure the vulnerability is practicably exploitable before I disclose it, and not only a
theoretical issue, I tested it against a local test setup using the &lt;a href="https://github.com/viabtc/viabtc_mining_server"&gt;viabtc_mining_server&lt;/a&gt; code
available on GitHub.&lt;/p&gt;
&lt;p&gt;I build, installed, and configured a &lt;em&gt;bitpeer&lt;/em&gt; and &lt;em&gt;jobmaster&lt;/em&gt; instance to use a local Bitcoin Core
node. The &lt;em&gt;bitpeer&lt;/em&gt; instance was configured to connect to a second local Bitcoin Core node via P2P. To
be able to start the &lt;em&gt;bitpeer&lt;/em&gt; and &lt;em&gt;jobmaster&lt;/em&gt; instances I needed to comment out some startup actions
for services I was unable to configure (mostly due to missing documentation). Once set up, I constructed
a block composed of a real block header and a coinbase transaction encoding a height a few hundred
blocks into the future. I send the block to the &lt;em&gt;bitpeer&lt;/em&gt; instance, and it notified the jobmaster
about the new block. I couldn&amp;rsquo;t tell from my patched &lt;em&gt;jobmaster&lt;/em&gt; instance if it would have switched to
SPV mode as it crashed due not being configured correctly. This made it hard to verify the vulnerability
locally.&lt;/p&gt;
&lt;p&gt;At this point I wasn&amp;rsquo;t sure if this vulnerability could even be exploited against ViaBTC. Surely
they are running an updated and maintained version of their mining server? I contemplated trying to
verify the vulnerability against ViaBTC&amp;rsquo;s production mining pool. As far as I could tell, there is
no ViaBTC testnet pool available. I knew that ViaBTC would, by default, only SPV mine for 30
seconds, which made the possible damage done manageable: about $1000 of lost revenue per attack. If
the vulnerability could be exploited, I could supply ViaBTC with log timestamps and the information
about the block I send. Showing that the vulnerability can be exploited against their production
pool increases the chances of them taking this seriously. I feared they wouldn&amp;rsquo;t take this seriously
if I only a reported a vulnerability in unmaintained version of their mining server they uploaded to
GitHub three years ago&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I ultimately decided to go ahead with trying to verify the vulnerability against the production
pool. I attempted this only once, while taking great care not to unnecessarily harm ViaBTC or it&amp;rsquo;s
customers.&lt;/p&gt;
&lt;p&gt;I choose block 826284 mined a few hours earlier. This block has only a coinbase transaction and is
comparatively quite small, which made it easy to handle in a text editor and on the command line.
However, vulnerability can be exploited with larger blocks just as well. At the time, block 826337
was the most recent block. My Bitcoin Core node saw it at &lt;code&gt;2024-01-19 00:55:46&lt;/code&gt;. The 53 block
difference between these blocks was big enough to make it clear to ViaBTC that a successful
exploitation isn&amp;rsquo;t just a small reorg.&lt;/p&gt;
&lt;p&gt;Block 826284 encodes an original block height of &lt;code&gt;ac9b0c&lt;/code&gt; in the coinbase transaction. I set the
height to &lt;code&gt;e29b0c&lt;/code&gt; (826338; current height of 826337 + 1) in the coinbase using a text editor. Note
that the BIP34 height in the coinbase is encoded in little-endian.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;  original ac9b0c - 826284
current e19b0c - 826337
  modified e29b0c - 826338
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The following shows the modified block 826284. The only difference to the original block is that the
coinbase height has been modified from &lt;code&gt;ac9b0c&lt;/code&gt; to &lt;code&gt;e29b0c&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0000cd2765e111fa8f2870ae4fdaa564907e501ac5ab12d0cab6020000000000000000007908d44825dc7bc91fb883fa5b2
083f0f95641aa4ac518bb968c2f29ae61a00f4357a96569d80317397f083301010000000100000000000000000000000000
00000000000000000000000000000000000000ffffffff6403█e29b0c█2cfabe6d6d654b01255ebb409c165395395b3f3c2
301f032f53df45cba5ed5d266dc2c786010000000f09f909f092f4632506f6f6c2f73000000000000000000000000000000
00000000000000000000000000000000000000000500ae970800000000000422020000000000001976a914c6740a12d0a7d
556f89782bf5faf0e12cf25a63988ac1ebc4025000000001976a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac
00000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733bdb2a04b4ccf74792cc6753c27c
5fd5f1d6458bf00000000000000002c6a4c2952534b424c4f434b3acd2e3ba1354794d09aabccd650c2155ae16cd9830cc9
b0d57aecd423005ba3a64940a53f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To track which previous block ViaBTC mines on, I set up a &lt;a href="https://github.com/0xB10C/stratum-watcher/tree/who-mines-what"&gt;patched version&lt;/a&gt; of achow101&amp;rsquo;s
&lt;a href="https://github.com/achow101/stratum-watcher"&gt;stratum-watcher&lt;/a&gt;. This connects to the ViaBTC stratum server and listens for new mining jobs.
My patch prints which previous block is specified in the mining jobs. If ViaBTC sends out a
new mining job with the block hash of 826284, I&amp;rsquo;d know that ViaBTC is vulnerable.&lt;/p&gt;
&lt;p&gt;I send the modified block to one of my &lt;em&gt;bitpeer&lt;/em&gt; peers using the &lt;code&gt;sendmsgtopeer&lt;/code&gt; RPC call on
&lt;code&gt;2024-01-19 at 00:56:28&lt;/code&gt; UTC. My Bitcoin Core node with &lt;code&gt;net&lt;/code&gt; debug logging showed that the block
was sent: &lt;code&gt;sending block (409 bytes)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At the same time, I saw that the ViaBTC stratum servers I was connected to send out a new mining
job switching to mining on block &lt;code&gt;0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8&lt;/code&gt;.
This meant I had successfully confirmed that the vulnerability is exploitable against the production
ViaBTC mining pool. All other pools kept mining on &lt;code&gt;0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[..]
00:56:25,877: btc.f2pool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:25,924: btc-eu.f2pool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:28,678: btc.viabtc.io mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,687: btc.viabtc.com mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,699: btc.viabtc.com mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,904: btc.viabtc.io mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
00:56:32,710: solo.ckpool.org mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:35,854: stratum.kano.is mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
[..]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exactly 30 seconds later (ViaBTC&amp;rsquo;s default SPV-mining timeout), ViaBTC switched back to the correct previous
block &lt;code&gt;0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017&lt;/code&gt;. In these 30 seconds,
ViaBTC&amp;rsquo;s miners wasted about 1.8 sextillion hashes (1.8×10²¹ or 1.8 zeta hashes) mining on an old
block. No block was found during these 30 seconds by neither ViaBTC nor another pool. If a ViaBTC
miner had found an empty block, the block would have been &lt;strong&gt;in&lt;/strong&gt;valid&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; as the height
commitment in the coinbase would been 826338 and not 826285 as expected with the previous block hash
in the header.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00:56:49,503: [..]slushpool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:51,422: [..]slushpool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,645: btc.viabtc.io mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,677: btc.viabtc.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,721: btc.viabtc.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,899: btc.viabtc.io mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:57:02,589: solo.ckpool.org mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:57:05,867: stratum.kano.is mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I stopped digging further at this point and responsibly disclosed the vulnerability to ViaBTC.&lt;/p&gt;
&lt;h4 id="impact"&gt;Impact&lt;/h4&gt;
&lt;p&gt;The vulnerability has a denial-of-service impact with a measurable associated business cost. &lt;a href="https://www.viabtc.com/pricing"&gt;By
default&lt;/a&gt;, ViaBTC miners are paid according to PPS+ (Pay Per Share Plus): this means, miners are paid
even if ViaBTC does not find a block or isn&amp;rsquo;t rewarded for finding a block. When the vulnerability
was discovered, ViaBTC had around 12% of the network hashrate. Assuming 144 blocks per day, that are
12 blocks per day found by ViaBTC. With each block worth at least 6.25 BTC, that&amp;rsquo;s 75 BTC or $3M USD
per day at $40k USD/BTC. This is ~$35 USD per second. Exploiting the vulnerability once for 30 seconds
costs ViaBTC at least $1000 USD.&lt;/p&gt;
&lt;p&gt;When a &lt;em&gt;bitpeer&lt;/em&gt; instance sends a SPV mining notification, it increases the best known height and
does not notify for the same height again. This means, a single &lt;em&gt;bitpeer&lt;/em&gt; instance can only be used
once per block to exploit the vulnerability. However, since there are at least seven bitpeer
instances running, in theory, a malicious actor (e.g., a competing mining pool) could have exploited
the vulnerability at least seven times per block. A attack over a longer time-frame would be detectable
not only by ViaBTC but also on e.g., &lt;a href="https://fork.observer"&gt;fork.observer&lt;/a&gt; as ViaBTC would have an abnormal stale block rate.
An attacker could also be more careful and exploit this sporadically over a longer time-frame, which
could cause serious financial damage if undetected.&lt;/p&gt;
&lt;h4 id="communication-with-viabtc"&gt;Communication with ViaBTC&lt;/h4&gt;
&lt;p&gt;ViaBTC offers a bug bounty program (mainly scoped for their &lt;code&gt;viabtc.com&lt;/code&gt; website) where they list a
Zendesk support email to report vulnerabilities to. I didn&amp;rsquo;t find a dedicated security contact or
way to send an encrypted message. My responsible disclosure included all information I know about
the vulnerability, details how I verified the vulnerability against the ViaBTC production pool, how
they can verify it against a local setup, a discussion of a potential impact if exploited multiple
times, a recommendation on how to fix the vulnerability, and some general notes on running old &lt;code&gt;C&lt;/code&gt;
code with seemingly no test or fuzz coverage for critical business infrastructure. I also included
that it would be nice if they could notify potential other pools using the same software.
Additionally, I choose to send the ViaBTC CEO and author of the ViaBTC mining server, Haipo Yang, a
short summary of the vulnerability and it&amp;rsquo;s potential impact.&lt;/p&gt;
&lt;p&gt;The next day, ViaBTC confirmed they&amp;rsquo;ve received the vulnerability. Three days later, ViaBTC assigned
it severity level 1 of 3 and awarded a 500 USDT bounty. I followed up asking for a reasoning behind
the level 1 classification. They re-classified the vulnerability as level 2 and raised the bug bounty
reward to 2000 USDT. They also noted that their risk scoring system and monitoring had detected my
verification attempt. Furthermore, they claim, a fix was implemented immediately, even before I
reported the vulnerability. If true, then the classification as level 2 under &amp;ldquo;certain asset losses&amp;rdquo;
makes sense. The vulnerability couldn&amp;rsquo;t have been exploited over a longer time range causing level 3
&amp;ldquo;serious asset loss&amp;rdquo; (i.e., ViaBTC paying their PPS+ miners out of their pockets and their PPLNS
miners moving away due ViaBTC&amp;rsquo;s &amp;ldquo;bad luck&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;While ViaBTC responded quick, funneling the communication with the &amp;ldquo;Security Risk Team&amp;rdquo; through a
support agent isn&amp;rsquo;t ideal. Their responses were short and at first I thought they don&amp;rsquo;t understand
the vulnerability, maybe due to a noticeable language barrier. ViaBTC didn&amp;rsquo;t provide details on how
they fixed the vulnerability (they, obviously, don&amp;rsquo;t need to) and didn&amp;rsquo;t respond to my offer to
re-test the vulnerability. I choose to re-test nonetheless before publishing this disclosure.
Ignoring the rough edges, it was still a positive experience working with them.&lt;/p&gt;
&lt;h4 id="timeline"&gt;Timeline&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;2024-01-18: vulnerability detected and successfully exploited locally&lt;/li&gt;
&lt;li&gt;2024-01-19:
&lt;ul&gt;
&lt;li&gt;Confirmed ViaBTC production mining pool is vulnerable&lt;/li&gt;
&lt;li&gt;Responsible disclosure of the vulnerability to ViaBTC&lt;/li&gt;
&lt;li&gt;Disclosure of a vulnerability summary to Haipo Yang, ViaBTC CEO and pool software author&lt;/li&gt;
&lt;li&gt;Vulnerability was fixed immediately after being detected by ViaBTC&amp;rsquo;s &amp;ldquo;risk scoring system&amp;rdquo; (claimed by ViaBTC)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2024-01-20: ViaBTC confirms the vulnerability has been received&lt;/li&gt;
&lt;li&gt;2024-01-24: ViaBTC classifies vulnerability as level 2 and awards a 2000 USDT bug bounty&lt;/li&gt;
&lt;li&gt;2024-02-06: I re-tested that the vulnerability is indeed fixed&lt;/li&gt;
&lt;li&gt;2024-03-20: Public disclosure&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="notes"&gt;Notes&lt;/h4&gt;
&lt;p&gt;To my knowledge, only ViaBTC was affected. I hope ViaBTC informed other known users of their
software about the vulnerability. I contacted a few people in the mining pool community who asked
around if anyone is using the ViaBTC mining server, however it doesn&amp;rsquo;t seem there&amp;rsquo;s much usage
besides ViaBTC. As far as I can tell, all &lt;em&gt;bitpeer&lt;/em&gt; connections I receive are from ViaBTC. However,
someone might be using the GitHub version to host a mining pool for another coin. I opened an
&lt;a href="https://github.com/viabtc/viabtc_mining_server/issues/16"&gt;issue&lt;/a&gt; in the repository to make potential users aware.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t know for sure if this hasn&amp;rsquo;t been expoited in the wild, but I don&amp;rsquo;t have any reason
to assume it was before I validated the vulnerability against the ViaBTC production mining pool.
If ViaBTC&amp;rsquo;s &amp;ldquo;risk scoring system&amp;rdquo; really detected my attempt, it would likely have detected other attacks too.&lt;/p&gt;
&lt;h4 id="reflection"&gt;Reflection&lt;/h4&gt;
&lt;p&gt;I think, it was the right decision to test if ViaBTC&amp;rsquo;s production pool is vulnerable. The claimed
internal alarms made sure they took this vulnerability serious and fixed it before I could report
it. Additionally, it removed the necessity for ViaBTC to verify a theoretical vulnerability in a
test environment – they had confirmation that it works. Still, I could have been more careful when
exploiting the vulnerability by choosing the current block to mine an empty block on. This would
have cost them the transaction fees, but not the whole block reward. Yet, this might also have not
been alerted by their &amp;ldquo;risk scoring system&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Running the C code, as uploaded on GitHub, as a production mining server seems scary to me. As far as I
can tell, there is no unit testing (there seems to be a few years of in-the-wild-usage-testing
though…), no fuzz-testing, error handling seems to be done by returning a failed line number, etc. I
personally wouldn&amp;rsquo;t run this in production. Especially not with 12% of Bitcoin&amp;rsquo;s hashrate. I suspect the
vulnerability existed since Haipo originally wrote the ViaBTC mining server in &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/bitpeer/bp_peer.c#L3"&gt;2016&lt;/a&gt;. While I
briefly checked that there weren&amp;rsquo;t any scary buffer overflows in the code that accepts P2P messages
before publishing this disclosure, I decided not to spent more time digging deeper. Usually,
disclosing a vulnerability ends up bringing in more eyes on the code. In this case, it wouldn&amp;rsquo;t
surprise me if there are more vulnerabilities to be found.&lt;/p&gt;
&lt;p&gt;This once again confirms my impression that some mining pools aren&amp;rsquo;t as technologically advanced as one
might think. Especially older pools might still be stuck with a bit of technical dept. However,
ViaBTC seems to have adequate an alerting and monitoring setup.&lt;/p&gt;
&lt;h4 id="credits"&gt;Credits&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://twitter.com/willcl_ark"&gt;Will Clark&lt;/a&gt; reminded me about the weird P2P network behavior of what we found out to be ViaBTC
&lt;em&gt;bitpeer&lt;/em&gt; P2P clients. Furthermore, I found the discussion with Will about the vulnerability and how
to best handle it helpful. The &lt;a href="https://dci.mit.edu/"&gt;MIT DCI&lt;/a&gt; provides me with servers I use to run Bitcoin Core nodes
alongside some monitoring tools. Having access to multiple nodes was helpful in collecting the
initial data about the &lt;em&gt;bitpeer&lt;/em&gt; clients. My day-to-day work of, for example, monitoring Bitcoin
network anomalies, was funded by &lt;a href="https://spiral.xyz/"&gt;Sprial&lt;/a&gt; and &lt;a href="https://hrf.org/"&gt;Human Rights Foundation&lt;/a&gt; when I found the
vulnerability and is now funded by &lt;a href="https://opensats.org/"&gt;OpenSats&lt;/a&gt; while writing this disclosure. Without their support,
I wouldn&amp;rsquo;t be able to work on Bitcoin full-time. Consider donating to them if you want to support my
and others work on Bitcoin.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;However, who knows who is still using this…&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;A previous version of this post clailmed that the block would have been valid but stale.
This was incorrect. AJ made me aware of this in &lt;a href="https://twitter.com/ajtowns/status/1770780046707302514"&gt;https://twitter.com/ajtowns/status/1770780046707302514&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>ViaBTC's mutated blocks without witness data</title><link>https://b10c.me/observations/10-viabtc-blocks-without-witness-data/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/10-viabtc-blocks-without-witness-data/</guid><description>&lt;p&gt;I noticed multiple &lt;code&gt;ERROR: AcceptBlock: bad-witness-nonce-size&lt;/code&gt; errors in the debug log of my Bitcoin Core node. These indicate that a block my node received is invalid and not accepted. It turned out that these are ViaBTC&amp;rsquo;s blocks, broadcast by their mining pool software, where transaction witness data is missing. In this post, I&amp;rsquo;ve written down my notes on this observation.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Some Bitcoin Core node operators might have noticed the following error in the log of their listening node:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: AcceptBlock: bad-witness-nonce-size, ContextualCheckBlock: invalid witness reserved value size
ERROR: ProcessNewBlock: AcceptBlock FAILED (bad-witness-nonce-size, ContextualCheckBlock: invalid witness reserved value size)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This error can be found &lt;a href="https://bitcointalk.org/index.php?topic=5197705.0"&gt;on bitcointalk&lt;/a&gt; as early as November 2019 and was a topic in a &lt;a href="https://bitcoin-irc.chaincode.com/bitcoin-core-dev/2023-05-08"&gt;bitcoin-core-dev&lt;/a&gt; IRC meeting in May 2023 as well as in multiple conversations I had. As part of my ongoing Bitcoin P2P network anomaly monitoring, I decided to look into the origin of this error.&lt;/p&gt;
&lt;p&gt;The log message tells us that, while processing a newly received block, a block validation check failed with the reason &lt;code&gt;bad-witness-nonce-size&lt;/code&gt; and the note: &lt;code&gt;invalid witness reserved value size&lt;/code&gt;. Bitcoin Core&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; validates new blocks in &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L4229-L4237"&gt;four steps&lt;/a&gt;. &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L3762"&gt;First&lt;/a&gt;, the block header&amp;rsquo;s proof of work, the timestamp, and the block version are checked. As a &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L3624"&gt;second step&lt;/a&gt;, the block&amp;rsquo;s properties that don&amp;rsquo;t require context are checked. These checks verify that the merkle root matches the transactions, the block is not too large, that only the first transaction is a coinbase transaction, the transactions are consensus valid (inputs and outputs not empty, output value not too large, &amp;hellip;), and that the block is under the sigops limit. The checks in the &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L3811"&gt;third step&lt;/a&gt; involve context. The transaction locktime, that the coinbase script starts with a block height, &lt;strong&gt;that the witness commitment in the coinbase transaction is valid&lt;/strong&gt;, and that the block weight is below the maximum weight is checked. The &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L2148"&gt;fourth and final step&lt;/a&gt; checks, for example, the UTXO&amp;rsquo;s, input scripts, and block reward. If all checks are successful, the block is accepted.&lt;/p&gt;
&lt;p&gt;The witness commitment is checked in step three. As specified in &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#user-content-Commitment_structure"&gt;BIP 141&lt;/a&gt;, the witness commitment is the hash of the &lt;code&gt;witness root hash&lt;/code&gt; and &lt;code&gt;witness reserved value&lt;/code&gt;. The &lt;code&gt;witness root hash&lt;/code&gt; is the root of a merkle tree using the witness transaction hashes, &lt;code&gt;wtxid&lt;/code&gt;&amp;rsquo;s, as leaves. Compared to the merkle root in the block header, the witness root also commits to the transaction witness data. The &lt;code&gt;witness reserved value&lt;/code&gt; is a 32 byte value stored in the coinbase witness. It&amp;rsquo;s currently unused, but can be used by future softforks to add a new commitment. The coinbase witness is required to be exactly &lt;a href="https://github.com/bitcoin/bitcoin/blob/44d8b13c81e5276eb610c99f227a4d090cc532f6/src/validation.cpp#L3860-L3862"&gt;one 32 byte element&lt;/a&gt;, otherwise, the &lt;code&gt;bad-witness-nonce-size&lt;/code&gt; error is raised. The &lt;code&gt;witness reserved value&lt;/code&gt; was previously called &lt;code&gt;witness nonce&lt;/code&gt; and the error was likely never changed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vtx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;scriptWitness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vtx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;scriptWitness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlockValidationResult&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BLOCK_MUTATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;bad-witness-nonce-size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;strprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s : invalid witness reserved value size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__func__&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The proof-of-work of the received blocks is valid, which means that someone invested a large amount of energy into mining the block. As Bitcoin Core only processes new blocks it does not yet know, this could indicate that my node received a block shortly after being mined. Maybe the block came directly from a mining pool?&lt;/p&gt;
&lt;p&gt;Digging deeper, the Bitcoin Core &lt;code&gt;debug.log&lt;/code&gt;, with the &lt;code&gt;net&lt;/code&gt; category turned on, reveals that these blocks are sent to us with an unsolicited (we didn&amp;rsquo;t ask for this block in a &lt;code&gt;getdata&lt;/code&gt; message) Bitcoin P2P &lt;code&gt;block&lt;/code&gt; message. These &lt;code&gt;block&lt;/code&gt; messages stand out, as blocks on the Bitcoin network are typically relayed as compact blocks&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. The peers sending these blocks are all inbound connections. The version message they send to us contains the user agent &lt;code&gt;/Satoshi:0.19.0.1/&lt;/code&gt;, a version of 70015, and has only the &lt;code&gt;NODE_WITNESS&lt;/code&gt; service flag set. The user agent is clearly fake as Bitcoin Core version 0.19.0.1 never sets only the &lt;code&gt;NODE_WITNESS&lt;/code&gt; service flag. The peer&amp;rsquo;s IPv4 addresses all belong to IP ranges owned by AWS - but belong to different AWS regions. The logs also show the block hash of the received block. Looking up the block hashes in a block explorer shows that the blocks are all mined by ViaBTC and, apparently, a valid version of them was relayed and accepted by the network.&lt;/p&gt;
&lt;p&gt;On GitHub, an old version of the &lt;a href="https://github.com/viabtc/viabtc_mining_server/"&gt;ViaBTC mining server&lt;/a&gt; can be found. The &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/bitpeer/bp_peer.c#L32"&gt;user agent&lt;/a&gt;, &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/bitpeer/bp_peer.c#L31"&gt;version&lt;/a&gt;, and &lt;a href="https://github.com/viabtc/viabtc_mining_server/blob/4891c9ccbadc773919cbb3a53beca1b610fb6543/bitpeer/bp_peer.c#L183"&gt;service flag&lt;/a&gt; match the observed peers. These connections are made using the ViaBTC &amp;ldquo;&lt;em&gt;bitpeer&lt;/em&gt;&amp;rdquo; module. ViaBTC runs multiple &lt;em&gt;bitpeer&lt;/em&gt; instances that connect to the Bitcoin P2P network to propagate ViaBTC&amp;rsquo;s blocks and feed blocks from other pools into their mining server.&lt;/p&gt;
&lt;p&gt;To figure out why the blocks received from ViaBTC&amp;rsquo;s &lt;em&gt;bitpeer&lt;/em&gt; peers don&amp;rsquo;t have a valid &lt;code&gt;witness reserved value&lt;/code&gt;, I started recording the P2P communication between my Bitcoin Core node and the &lt;em&gt;bitpeer&lt;/em&gt; clients. Inspecting the transactions in the received blocks showed that all transactions are serialized without the witness data. A SegWit commitment in the coinbase exists. Without witness data, the SegWit commitment in the coinbase transaction can&amp;rsquo;t be verified as the &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#extensible-commitment-structure"&gt;witness reserved value&lt;/a&gt; is missing. The verification fails with the reason: &lt;code&gt;bad-witness-nonce-size&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following shows the raw coinbase transaction from block 832535 mined by ViaBTC. The witness parts of the transaction, missing from blocks send by ViaBTC&amp;rsquo;s bitpeer clients, are colored in red. These are the SegWit maker &lt;code&gt;00&lt;/code&gt; and flag &lt;code&gt;01&lt;/code&gt;, as well as the witness stack size &lt;code&gt;01&lt;/code&gt;, witness element size &lt;code&gt;20&lt;/code&gt;, and the witness reserved value &lt;code&gt;0000000000000000000000000000000000000000000000000000000000000000&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;
&lt;p style="font-family: monospace"&gt;01000000&lt;span style="color: #b10c00;"&gt;0001&lt;/span&gt;010000000000000000000000000000000000000000000000000000000000000000ffffffff620317b40c1b2f5669614254432f4d696e6564206279206b7a616c67736176652f2cfabe6d6d7182edded3ec31023b380b5ecb0cfdb8bc19b7b64b038620edc9f3f0d73115ad100000000000000010cdbc33017bb0ae8f449fc454faf2010000000000ffffffff0327609e26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3a5f520faece8244c1a89cc42225671c5e95eabe95f00186984778b119005d76cd0000000000000000266a24aa21a9ede8f8b5f251110e1c7b7c6f9e4082514e245b6fdb10b06e8285835f8c80eec86c&lt;span style="color: #b10c00;"&gt;01200000000000000000000000000000000000000000000000000000000000000000&lt;/span&gt;00000000&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;While the ViaBTC &lt;em&gt;bitpeer&lt;/em&gt; clients incorrectly broadcast the ViaBTC blocks without witness data, the blocks (with witness data) still somehow propagate through the network. Probably, ViaBTC&amp;rsquo;s Bitcoin Core nodes announce the blocks as compact blocks to their peers. It&amp;rsquo;s not clear to me where in the ViaBTC mining server the witness data is lost. In most cases I&amp;rsquo;ve observed, the block messages from ViaBTC &lt;em&gt;bitpeer&lt;/em&gt; instances arrive after the compact blocks&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. Thus, fixing this might only slightly improve the block propagation of ViaBTC&amp;rsquo;s blocks on the Bitcoin network. I&amp;rsquo;m not sure if the &lt;em&gt;bitpeer&lt;/em&gt; clients are required at all to ensure the blocks propagate well.&lt;/p&gt;
&lt;p&gt;I let ViaBTC know about this in late February 2024, and they thanked me for informing them. However, I don&amp;rsquo;t know if they are working on a fix for their mining server.&lt;/p&gt;
&lt;p&gt;In the recently merged pull request &lt;a href="https://github.com/bitcoin/bitcoin/pull/29412"&gt;#29412&lt;/a&gt; an early mutation check for blocks received in a &lt;code&gt;block&lt;/code&gt; message was added. The mutation check is now done right after receiving the block before doing any other block validation. If a mutation is detected, we disconnect the peer. Previously, we only checked blocks we hadn&amp;rsquo;t seen before for mutations. Now, all blocks are checked. This means we also disconnect more aggressively. Starting with the upcoming Bitcoin Core version 27.0, ViaBTC&amp;rsquo;s &lt;em&gt;bitpeer&lt;/em&gt; clients will be disconnected for each mutated block they send.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Here specifically version 26.0 as the block processing will slightly change in version 27.0.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The &lt;code&gt;block&lt;/code&gt; message is however used during initial block download.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Bitcoin Core only logs the error when the &lt;em&gt;bitpeer&lt;/em&gt; &lt;code&gt;block&lt;/code&gt; message arrives before the compact block message.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Long-Term-Support Grant from OpenSats for 2024 and 2025</title><link>https://b10c.me/funding/2024-opensats-grant/</link><pubDate>Thu, 01 Feb 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2024-opensats-grant/</guid><description>&lt;p&gt;&lt;a href="https://opensats.org"&gt;OpenSats&lt;/a&gt; supported my work on Bitcoin in 2024 and 2025 with a Long-Term-Support grant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opensats.org/blog/0xB10C-receives-lts-grant"&gt;Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Spiral Work-Log Q4 2023</title><link>https://b10c.me/funding/2023-sprial-report-q4/</link><pubDate>Wed, 31 Jan 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2023-sprial-report-q4/</guid><description>&lt;p&gt;This is a copy of the Q4 2023 work-log I sent to &lt;a href="https://spiral.xyz"&gt;Spiral&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2023-sprial-grant/"&gt;my grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;p&gt;In Q4 of 2023 and January 2024, among other things, I’ve worked on my fork-observer, the miningpool-observer and the peer-observer tools. I’ve also maintained my bitcoin-data related repositories, worked on macOS and FreeBSD support for Bitcoin Core’s tracing framework, and build a tool to find non-standard transactions. I’ve written blog posts about CPU usage of Bitcoin Core peers, about OFAC sanctioned transactions missing from F2Pool blocks, and a post about non-standard transactions being mined.&lt;/p&gt;
&lt;p&gt;In more detail in no particular order:&lt;/p&gt;
&lt;h3 id="bitcoin-core-cpu-timings-per-relay-type"&gt;Bitcoin Core CPU timings per relay-type&lt;/h3&gt;
&lt;p&gt;There is an &lt;a href="https://github.com/bitcoin/bitcoin/issues/28462"&gt;ongoing effort&lt;/a&gt; in Bitcoin Core to increase the number of outbound block-relay-only connections from 2 to 8 with the goal of helping against network partition attacks. However, first the &lt;a href="https://github.com/bitcoin/bitcoin/pull/28463"&gt;number of inbound slots need to be raised&lt;/a&gt; to accommodate more
block-relay-only connections. More inbound slots also means higher resource usage. I’ve focused on CPU usage of Bitcoin Core peers, while others looked at memory and bandwidth usage.&lt;/p&gt;
&lt;p&gt;I developed a framework to measure per peer CPU usage on a busy mainnet node. By recording the time spent in the SendMessage() and ProcessMessage() functions, it can be estimated how expensive adding more outbound and inbound block-relay-only connections are. As expected, block-relay-only connections are quite cheap. Full results can be found in &lt;a href="https://delvingbitcoin.org/t/cpu-usage-of-peers/196/2"&gt;https://delvingbitcoin.org/t/cpu-usage-of-peers/196/2&lt;/a&gt;, and I also left some &lt;a href="https://github.com/bitcoin/bitcoin/pull/28463#issuecomment-1810591988"&gt;commentary&lt;/a&gt; on the PR approach, which ended up allowing the authors to improve the approach.&lt;/p&gt;
&lt;h3 id="fork-observer"&gt;fork-observer&lt;/h3&gt;
&lt;p&gt;When forks, or invalid blocks on the Bitcoin network happen, it’s important to know the involved parties. My &lt;a href="https://fork.observer/"&gt;fork.observer&lt;/a&gt; tool &lt;a href="https://github.com/0xB10C/fork-observer/pull/31"&gt;now&lt;/a&gt; tries to identify the miner, where possible. This also supports blocks on the default Signet.&lt;/p&gt;
&lt;p&gt;&lt;img src="data:image/png;base64,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" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Along with general maintenance, I’ve also added a feature that produces an &lt;a href="https://fork.observer/rss/invalid.xml?network=1"&gt;RSS feed for invalid blocks&lt;/a&gt; because we saw three of them in 2023.&lt;/p&gt;
&lt;h3 id="miningpool-observer"&gt;miningpool-observer&lt;/h3&gt;
&lt;p&gt;As some mining pools started censoring Inscriptions, I’ve invested some time into being able to detect inscriptions with &lt;a href="https://github.com/0xB10C/rawtx-rs/pull/13"&gt;add: detect ordinal inscription reveals in inputs&lt;/a&gt; and added an inscription tag to inscription transactions missing from blocks.&lt;/p&gt;
&lt;p&gt;Also, I found it was time to revisit the miningpool-observer transaction package building code. I changed it to closer match Bitcoin Core’s ancestor package building code. This allows for a much cleaner feerate distribution graph.&lt;/p&gt;
&lt;p&gt;&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAloAAAEbCAIAAACN1b3pAAA4OklEQVR4Xu2dCXQUxb7/Oef/3rvX9+71uj3u0+NV0avIHiDsIGHfLrIzCqJsCkR2AUG2kLCLgIAsAiKKbGFfwxZ2kCUshiB7CFsgrAkEkJDU/zfzyxQ11ZOZZGaS2zN8P+d7+lRXVfd0V1fXt6pnpjrfwz8eQRAEQdBTrnzGKAiCIAh62qTb4YOHf0AQBEFQoMpohE7skPIJAAAAIKBxaooOdph6/4G+EQAAABBYkNm5skNKvJd6X98IAAAACCzu2fwQdggAAOCpBnYIAAAAwA4BAAAA2CEAAAAgYIcAAACAgB0CAAAAwks7LPB2IakyFSrryV6wOHJp7LE4PTYLen7Rlw7gn+8WDalZZ9r072X8gYMxpcpUUDI+IeHCBT3KDu3n5MlTwnZ2eloWqEc7+btpXbr2cEzPLi4OWEUtdik9k0e0/rjd3J/n6bF2XBSaT0hKum48EXmCQcHlpn8/iyOzWVCS+g0br1qzVo8FAAAFb+1w+46dt2zcvnNHT/aC5pbWCxdH6rFZQHbYpm2HBw8eHIs73qhpCzqqmzdv6pkc2bV7jx5lwNg0Z0WOjtZ7uMAJOkIZ1jN5hGs7zE6heUNWdvjQBvVR3nynMEfCDgEAPsdbOzx0+IgW2affgIJFSsxfuEiNpLFjcPlKO3ft5tUNmzbT8pP2n3IDdzAmpmzFKpRn67btnEGOCWrUrs8xzS2t3ilcvN+AgbyqQnZIu5KrY77+hltVtdGcM/fntwsVo2aRD1jun1tJCoweO44OWxhGhy0//KhIidJ8wDJSDS9fsUruTTiODlevXUcHUPG9ar+fOCE3oUa/75df0bncN0z3ox4w5Vy0eEmR4qXI6R1zPUE9mKiNm+gEO3YKVVO3bd9ZonTZj9t1pNU7d5LpBL/+ZgKnJienUAY61HeLBn0/6weOVO1w2oyZlUNqkNOnpaVxjFZownBZVTZu2lK3QaMqITVPnTotI2nbceMn0mH07vuljJwxczbFhHbrmZUdyjD1OR4/fiwcC8pYyMTmLdHlKr5HlYqfFqh2SDvM7WEuAMAf8bEdUgw3QBWqVBscFi4jU1NTU1JSqOVduy5K2OyQ2m5qKC9eukSrn3YOzcjIoCEdWdHeX/cJw3irToP3yTIpQHZIDiHjGc0Ohb0NlY3mylWriwYFU+D69Rs//PiTMAx0KD+5Wup965mqdvhWwSJkG3fv3pWNsto6y7B6tNIOqd2nXdE+L1++QjkPHznKm5QqW5FGsbdu3zY2/Zod/jxvPrX+1CGoWaeBY8ZM5B76fzX4vWq1KEDGph7qyNFj09PTaQ8tPmhdvVY9tsA9e38VdjtMSLhAH0FuNHac1SZVO+RPX7V6jdyhsdC0y6ry1aChZMDn4uMpG30WR1I4eus26gfQ5Rg4eCjFjBrzNW1OH3T+fAJFGstExpCH0eXgsFpQxkL+om9/6m3QiJmMfOr0GUKxw4aNm/lqJA0ACDC8tUOpM2fP0oCgWq26aiotI0aMGjBwCMdwEyxsdhh3/HeZU0KR1M0XBjtUW0lji+nWDsmZOn/eXc1gbNllWLVDbl6F7cCuXEnUcsqwUzuk1AcPMsd/mzZH8ziYIhdHLuVI44lodijjjTkZGa9l5mfFTvdAQzEa7wrlWhDkRhx2+rCUxpc8nFILzelldcrsOXPlWFBmu3DxIodpyQM+gjpSxv0UUOpYyeDyHCkLatnylU4L2b51JmyHNNht1+EzLQkAABhv7VAdHZItqQ/BmAqVQ6QBSOSzR0a2XzRE4LBqMDIyK9zaIXHp0uXipcoWsD/oy6YdPskhxKLFS7RIGc7KDmVOuVrA9rBUjVHxiR1KnO5h3foN/PxZ8zAOSzukQSENsNio6Apu275TOBaa08sqobE+7TAx0dqBoJJp0tzC8cZPVGMuXrpkPBEtZtCQMKEUFF16NdW4T4bsMLRbT2M8AABIfGmH1NTKx1mSIWERVavX1iJVO7x//4Fsp8ZPnMRhS6s2CxYtlnlcN2TZ+e6QSU1N5SR+YChR96/aoTy7Y3HHuXEv4MzP1KNV7fDOnWSOVAcuxs0l3thhenq6Y6LzPTi1Q+PosFHTFmz/vC3boVpoTi+rZOu27RWqWEf5wvblq2s7lKPDqdNnGM9Ui+nwWRfhODp0WsjKFlZ4dBi5ZJkxCQAAGF/aIceQM9Hg4LfYWLVlp8EZBXbs3MW+5XR0uG//gZLB5Tk8euy4Zi0/lBmoOftp3i/C9uNDftanIn9ZGnf898bNWhaw/d5SKI3m59177t6zlwI0eC1WsgwF7t27t3rtOrkHtZXUvju8Tc2t4hz9BgwMCi738OHDjZu2yEj1aKUdfjPhW9oVmb323WFu2OFXg4ZymM5LDpic7kGzw3Px8WlpacbvDqd/P4tHh+0/7VykeCm2Q2OhaZdVQuVDqXTiS5evoGvqwg7ld4d0JHRpjGdawP7L0itXEkuXq8SPrLXvDrVC7tWnn/W7w9u3abf80yH53SGdndrNAgAAiY/tkBgWMYIa0FZt2nLLxVSqWp3a3M6fd797964w2OHpM2ep/RowcIj6uIwcjtxI/rK0bYdPqeGrVbfB9h3WpllF/u+wao3a02bMlPGy0Tx1+gy113QA/b8aLFOr16pXQPllqYx38svS4qWiNm6SGai1pV1N+HayuhUdLa+qvyylnZMZVKgccvx3h1+WyjAHJB7bIbH31310VFVCai5fscqYKsOaHdKh0lYzZs7mVPW7w/DhI98uVGz/gYOVQ2qwHQrHQhOGy6pCA0Qqt4mTpkQuXebCDoXNel3/spRVpETp4SPHcKRaUMZCFrbTDC5fqVzF9/ivitovS1etXiNzAgAA45UdAv/F9e9fAADgaQN2+JQCOwQAABXYIQAAAAA7BAAAAGCHAAAAgIAdAgAAAAJ2CAAAAAjYIQAAACBghwAAAIDwxg7Xrd9AKvB2IVomXr2q/YktcukyddUbylSorEcppKamzpu/UI/NCcVLlaXlluitZ86e1dMcqdug0f4DB+UrFLLPuPET9ahsoH2cZzvJDtrLKb3HV/9orN+wsR5lR36E26vvzcHIN0FKmltaazHekP26p52F8cBUsjrlx48f823LophuPXrrmTwiq0/0CRXH6PPxakSfyNBismp/rlxJnPfLAgosXb7iwsWLFAjt1vPRo0daNp4a1wXaVJENGzfjwPTvZ6mzcTGTpkyl5R9//KHFSxIuXDh0+EhIzTq8SoXJtZpL9Z/vFpXxtIw5dJjfi8f46uad/N00dZVa3d9iY9UYI/Kie1yLDsbEaO8aYo7FHVffk+qUjz5pb6x1llZttPZZnpfrW4bx3A4ZeUAU6NNvABUiv9WWP5sOrnDxkqPGfC3zH//9RMEiJbh+kJPN+mEObXLr1q1KVatXrV47JcX6YryGTZpXDqlR71+ZTSFlaNWm7Zy5P8udEJShVNmKscfiaCc/z5tfoXIIz59J1Khdnx20/aedhe3tCu8ULi5s01IL26sTqQ2SM5mFDx9ZwDYHGDVJVCMpid+ZIGwTkpWr+N7Dhw95lW4wzknh30+coJzkVZzUpLmFp0IVtppRrVZdamtq1mlAm5+Ljxf2mcaE7UX2FMnTpNHtIc+CtyXCwoe/XagYTy0mt2LkKi0/7RxKJ9K95xdUmEOHDecMvyxYVKhYSZ5oe9/+AyVKl+WSHxIWQVfhsy6fc7ZOXbq+WzRo6vQZZDa0q55f9JU1pnylqnRPUonxp3QO7SZLg5kxc3apMhXkDKVUznQYPAc3LWvVbcBJfJyxsceoamqfTsVSu37D1h+341VZGsJWdHSl7t27x6vC3hDQp1AJV63xZMbwDZs2c2mcP5/AV79th8xD0q6asJcbv3KE9kPNH8fTx9FHnzx5iuoAnRRvQjnpaL8aNJTzUDXeuGmLepryKnAdaG5pxTmZNm070Oack6oxXxFOmvDtZCr2EydPysxZ1T3a3OlZ0OUICi7Hb+Xk+2vHzl20T9l2k1UXDQretDmaj5Aqm/rCNadQIdDlkJOzd+wUSjuUFVvYzoJqo7wZtSqX1SdS3ZMlc+v2bcqzeUs0N6/Tpn9PV1k9teYzrPXtjQHp921+UXWc1fxKDk9/e1D6A5tPPdfTGjN8bUb+L9Ljrohvt1jzH08U+fukT9tmDf+1ezrpbObsh1aofEaMGksnQqejNh1Ut3kaWzpgukcowI0DxdOVihgxSthelL1k2XIK0FnTdafGR9jmr//yq0F0jdLS0vgloHzKjAxzZaZyID+jHV6+fEWmyk2+mzaDKknjZi3l5lRcwubQPOdluw6fUZUWthfBCoMdEuqbCeTNS4eqdR/p2tHyg9YfC9srfaga820oKxhno1aabvxvJ38nN+RDJWkVjPZAqxzmeTz4kLgW/atRU04y1iIqFn6LOE89LeG3DHGY9zx23ATywgMHY7i20LHJXoIGbaj1TeXx0KXnmNlz5v7b7JAD/MJC+uxdu/cYXzvOb5znjhI5mdbJ4p3wVaQKyi0XR6qtoQrthCsWZ5NXa83a9VSsv+7bTyVC/QiK2bZ9p9MBluyhs3n07f+VsHXBOFWelxomGxO2u0XYqzLBLxbm9wxLuOLKz5U1m1zQOM6gI+S3Jg0cPPTqtWvqRwtlJ1p8HHUxbI0sN5ScquURtnuAlnRvczYVrjFyYlI+SN4DNdlKRqud0DIxMVGt1nyOWkFRk8HtDkOf/uNPT96kyJn53bzCVhp0txjfGKUeibDNwC6TZCRffWpKqLK5vmp8pejg1SslU/kdy7xKlYfnrVVvIT5NedW4DhhfJCLsOakac1HTed29e5dfd6zOqSuyqHvykJyeBb+1gw+MXyrCba7skHFmqhIft+soY7KiSInSwr5zys8HLM1P2M7i9p078mZk+GWlbj+RO/68cwpTA0cXiGuOempHLoqdp0X50elDV2WcvCo2Hc/YF5+ZRCYnbHaY8kD0W2p1vunbM9gON8RZl8XDrRmMo0O2w6O/WZtUzSS+6Nufbq6166JWrFxNtkedNmE/HurtUUWlPjrVRs2EqB/Aq6PHjhOOhkRQF5YDcj/ro6yDb66c3Oi1+MBac8hrV65aLTdkZGlQD56GBGQG7FVsCUY7VEuPb16nLZtqh1xtGLk5tRzUS+PhrHZGbJyygg0aOkw4fq66yrWI66HTWiRsB6BN9C9s9X/MuPHy0Uhw+Uqch+3QqYMwh48cpdaDh08q1NoLU9khB+Rn04lxbWC4q8UYn3PytnwVhe2lesL+sNQ4Hp/3y4LBYeFyJ7ytPAwalAhb1++tgkWoilPnVG4YtXFT7Xr/kqvaAyvaG3VdqXrRKkvm5J1TKo3hZFIB26uPSVwbtOMsUryUUCor2Ym2Wz4LDlPPiEf6VGj8IJrjGaMdjvn6G2G7Z+RgVyjPlvlVFcJ+TzLy1lLhGsMvtRCOJcmlIXNyWyBs06bLpxlayXOY+oPXkpKE8unUueZxJ8FjCLIWrTTIhKid4rBQRoe8KqcCF8rH8dWnPXB3J6urxgHjlZKpqvVev36DR5NUjdXNhd0OtTrAUIFwl4JzympMlykm5hB3/G/csL6ZWeK07lGldX0WwnZglJOaEmMGDpP9qN3zrOBC4BKmEjB+rnYzyionsv5E2VXiLixbBXkAbUUXiC6x9hHEi73S79wXr/RLpzEirYavsfoiS9js8NdzYvcZa/jKnczRIdN2jjWclR1yWLsl32/SggZnFKDS47473W7UAZJHxTVh/MRJnJ9fGc0nS/ZJnQO6iKqlUesfMXK0sN1xnbp0lXs4fz6Bmhq6TD/Pmy/3wNAN26ZtB7mqGl7TFh8IW4eDvZwjtYD6Hj31Iac2kOJ+gBy0CfseZAWjUaw8DDY8CduhrGA0+BOG763k8agl7LQWCZvVsSXTbUIFTvcgDWcL2IehnIcycNWSo0Nh81d+uZ6K3FAbVLAdciee+qAmskOqMVR7Nm7aUrpcJZm/ZHD5qA0b+VmZaoenTp9ZtHgJb0t3IG1LA0fuoTu1w02bo6kLNmXqdM0OqRLTdZ39w4+PbYMhugbc7+ZUqrVU72kkweNFhpKOHP1Na5KE7d1DW7fvUN+fLs+RekORS5fxnumw6dioI8N3iDzO6K3baGzK/SY6ZR6RUK2ivueGTZvbdexEhSDPgjcht6AblT6UP0h+HEM7oeNU46nvRqdP5WO0wy5de+z9dR+XPLVHdEZVQmpSOPZYHHUFdu7azbWfDuDCxYtcY+jTp38/a1jECL63+VM0O6TmgwqKWkYa39+//+DmzZuUn3PSiUeMGMWPmziG+xzy08kOhe1lWzRwLFuxirC9MUqWxqVLl+f+PI+KUW3EXdghJdFQgw5DtUPh7KrVbdCIioLufL5SdPXVKyULU9rhjp27+AGasFVj6qerp0kuyDcq1wGqTpxT2N7c2eGzLmSlshpzPFsIDUYpg9ZgOa17NAg2ngXlpOtCtYWHKXxvU7NIDS7vk7bt2CmUtqKaJq8InTgFEq/aXMUZqh3SWIGsYveeveq3G3QWtH95M8oqJ7L+RKpjdKY0KGE7pMCCRYsrVKnGXwoUKlaS+qbqqRFvDrS64KTojJd6WwPEoJUZG+Mymk63+hw/LP1bj/TtpzJoODg5WrfD63cFDSgfWp/GZeLCDqnQuLQpcOv2bWGr+TQ2onuBj4rNhuoDNcpU4fn1O/xVCz/NonEwX1NJAdvjEFmXeA/cf/1lwSIeLVEkVfIG7zehijpw8FBqGNXNOUB3h7Q6OZ6joxo0JIzKUL5tRm0Z+Obllo3dXUK7+v3ECR7EN7e0oorNO5cVjAyb3L1zaDeq4dxSSdgOZQXjr6I0O6Q7i+8FtYSd1qKZs3+gxvDRo0fqF3vyBGlzqlRUl+gw+JkN2yE7iKVVm+TkFDp42QWh7kjfL63PUYShkWQ7pGtHV5YOL6/t0DOcPmKSyHYEmA1tNO8x3D03IdrdBXx1M1I/yfVdn03W/JZxxPoLmDxC+244l1i2fKXxK4ysIOdYsVJ/3Ap8Ql7b4dffTHi3aJDrSuarOxD4HO/tkMaFQcHlzp47pyeYA9ihhvc34+q162jkMfuHH/WEHFL32/Tne6Z/Eak/F80lSpQuW6FyCD/wzwO035i4QI4Rgc/JazsEAAAATAjsEAAAAIAdAgAAALBDAAAAQHhjh48fP+ZftXqA8QcLxpjso23r+ge13nwQ4/2PC7yk/1eD9ahcwMUEacyu3Xv0KIWw8OFbt+9QYzZviZZ/YGDWrovaEr1V/rK8gG1+lgnfTqZwQsKFpi0+mDd/ofyrorD9r2tx5NJ6/2p8/foNWh0cFj75u2ly87cLFaOP2L1nr8y/YuXqAwdjhPJD1jIVKicmJsrfXmt/WpLz46Snp5er+J46FVaXrj1kzeHfgtNV4Mm95M45g8zGO09NTeVVAIDJ8dwOCyj/oORZyuSMRN9M+Da0W88rVxKLlCjNMyRR5CftP5VTTPFWPKMV/yGPd8X/fe7YKdT401Nq7HheD57aTZtkoXNoN3ViLWGfqi05OYUjqW3ifw7xR9+6dUtt7DZs2sxzTQnbH4xKlC67fUfm/6kjRo6mU5MzwAmbHUZt3CQn31LPQs6/x59inKzo5MlT6nR0DO1w1eo1lPPhw4ely1Xi0xSOM13x/7VLla1IS/7XJjlH955fcAwdM88Bwf+VFvYDoBKgE+e/f9GHqrMp8gx2tDlPqseFo15H/h/emrXrCxYpEbVhI+c/deo0nay8pgVs06TJfZIF8hxvVLaybkiatfyQJyWQcIbDR44uX7HqxMmTfPU5smRwec7D/1ZkZPHyGXHOaTNmksP9+NM8PhLtQ6nW3b5z58jR3+TOXdhhu46dOED1lmqRZofzFy5i/+M/tFEpTZv+vbA5N380z3ojD0DuXJ2EDwBgWjy3Qzk65PZU2CdI4+aAWkaelItnG5JtBNuY2mbRTqgBlTE8xw91z3l+B4b3vGrNWmqds5rabd4vC+TEWtIFOcn1bFK0QzmjpoRj5FGpB0zuxaMTdVIxLgoeqdAgZtDQYVlNVsRoOyR7Fvbm/vPuT2bDkpHUoNOwiZ1V2iFnyJzr4bR10g7NDnl58+ZNjlc/lGewq9ugEU+StG37Tu06sh02eL+JsP3vmO1w67btwu5G2uhQzjDHG/LUUBKeGsOpHRLUlfl53nz+czSPIGVS9Vr17NmFnM+BU/lPwTGHDlNnQhaaeo68OnzkGGGfU43OIis7JFfm4Sx5f+LVq0Y7FLbpXiOXLpP/75b1qk+/ARcuXuR+mDwAuXPpsgAAM+MDO6RmUZ2Vh5sDajXYk9Q5nWWAlnJGK2pEqIWSGZzO8cOp9IlDwiKymtrt1u3bcmKtHTt3cZLWtnJYm79q95690lp+mvcLP93i1l87bEadbUQ9C2GbZmXSlKnSTownwoM5kcUOef4LHnNoM11R/4CnlREGO1SfasqZXVXDEPZZizSroMKha0RtOh+Vdh3Z1XiydWEvEIYnodDsUM4wx5+i2uGhw0cK2J8lyJlRZU5yehod/n7ihPvRYWg3Dqijw+nfz6L6QwNWp6NDGkDzJGHUv+n/1WDqqdAITz5tlr0HYZsOm2uFPFR1V2yHQ4cNp8G9tEPq6vEsVkHB5eR0M3LCLblz4+S0AAAT4rkdCtvsf3KSHmocqY8s7O2R0Q6HRYyYNmMmTzFFq3JGqxKly1JzJmfS4jl+aBSozvEzfuIkGpcUKVGaHNGpHUaMHC0n72Zn4qnaeMJMnk2KJ4zmw9PmbaJInmsq9ljcxElTzsXHaw++1JZRtUP1LGROnqvX6WRF1Cir09ExTu1Qm+mKG1n+msqFHS5YtHj12nXUsvP+XdthcHnrYGvm7B/kpLLqdeRPpKEn7Y2OgQpQbsilR4NOniaNI3mGOTnHmzY6ZOTokPdAw/3ordvU7w5p9ZsJ31I4Pv58s5Yf/rJgEX93yN/qkZlFLllG58t/jh44eOh302bIzcndqUxkT4ih0h452upeNPiTp08GSfZJlUqbF16+qEHYapFxdMhIO6QRLQ/NqWLLnZOnajtftnylUGwSAGBOvLLD7KM1xMC/IPvxflYRScKFC65/hmNEfdyde8CxAHiagR0CV9BwlsZS8illYHPjxk2f/xBUDuIBACYnj+wQAAAAMDOwQwAAAAB2CAAAAMAOAQAAAAE7BAAAAATsEAAAABCwQwAAAEDADgEAAAABOwQAAACEN3Y4fG3Guet6pHloMePJe/Ku9HoyDXRe8uji5ZvTfTa3GQAAgNzDczv8a/d0PQoYOFOmlh4FAADAfMAOcxfYIQAA+AWww9wFdggAAH6Bb+ww4abYcToz3HbOky/tmM/n6zHCtrmUnpYF323V9/P3PtndVnL+/Y84EF+nJS1vTp0jkxKafCLDKmRppLtRWyh8rpqTV9szl0P76VGwQwAA8BNy0Q4fPBIv900ftjqD7DDlgXhzYHqtiQ7u9dFsa86MDFE8PP2nvdbwcz3Tm03PKDk8vfHUjPKjrZnHRGWMWp9B+xF2O1x5JOOVfunnb1j3IN10Q1xG4bB0+kS3aHZ4wdKRlnfmL73aP5ztMHXXvrOVGtyYZH0HL8OWdqac9W2ubIdpSTfOVX3/0eVEa/49+yn/lZ4D2Q5p85S1m7RtAQAAmByf2aEc6kk7/N8vrBnIosgOX+qdmXncxicjPLbD/LYR3sj1mXZIy4u3rKm/J4pLt612eO+hdfVMksPokHPK0WHYamsSf6JryA55tKfa4bVh1hfWsx3e/G42Le/HHJWbUOazleonr1wv7HZ4pqzVGs9WqEfLxC/DORvZ4Z0Fy+4f+k1uaM0JOwQAAH/AZ3ZoHB3KDGSHbwxwYlRsh+p+2OTSbBF37otDCVY75KS1sRlsh4WGptPHvdDLwQ5n7NCfo2aF09FhypqNwm6HdxatkJkZtrTEPkOF3Q7j61lomdC8HS2Tl6zibGSH8XWt8SqwQwAA8Aty0Q77Lc24cU98u8X6sLT+pIxVRzMOnBdbT+qjw5HrMqZvz5i588noULPDRlOtGwr7w9J/9Lcm/62HdVl5bPrVZGsSrZ6+JvoscW+KTu3wXPUmNKpjOzxTrvYfp87e27JDbkKW9ij+wplydaw5bXZ4qV23h3EnLrS0bmvNf+48jQv5YSnvVt1WXQUAAGBOfGOHuYccHfopsEMAAPALzG6H/g7sEAAA/ALYYe4COwQAAL8Adpi7wA4BAMAv8NwOMYW3WzCFNwAA+Aue2yEAAAAQMMAOAQAAANghAAAAADsEAAAABOwQAAAAELBDAAAAQMAOAQAAAOFndmixsJa98rmeBAAAAHgB7BAAAADwTzuM/DvsEAAAgC/xSztc8Wf9LbsAAACAN/ilHS7/E+wQAACAL4EdAgAAAP5phwvzwQ4BAAD4kry2wwJvF6LlwZiYKiE1OYYCDRs34/DeffukOKcDsEMAAAC5Q57aYZ0G77PJUYCWPXr37dqjl57Jjgs7jPx/sEMAAAC+JO/s8NKlyyQ2uZGjx9IyKLhcidJl1TxLli6Xgh0CAADIM/LODus2aCTsY74+/QbQsmadBjVq11fzpCnADgEAAOQZeWeHDJvc0d9iq9aozTHvVavVpLkTe4MdAgAAyDPy2g6zD+wQAABAnuGXdmgVAAAA4DtghwAAAEAe2mFQcLnEq1cbvN+EwsVLlV0fteHXfft37tq9dl3UxElT9Nzu7PCzD1P0VAAAAMBT8s4OibcLFRsWMUJk/UeLteujpFzbYa18GydPFlu36lkAAAAAD8hTOyT27T9w9+7drP5okZycIuXaDlm361iuVbO0/r/Nek4AAAAgJ+SdHdJAMOHChRYftKZw6XKVlixbfvjI0YMxMStWro4YMUrP7e5hqaoP/hd2CAAAwCvyzg5zSvbtcFa+DnpOAAAAICcEgh1GPYcfmgIAAPCKQLDDPa/BDgEAAHhFINjhmWBLVJSeFwAAgAe0a6fHPCUEgh2mNrRUff6onhkAAEDOgR36sR2SeuUbv3q1nh0AAEBOgR3muh3WqF3/wsWLtes3FL6YlUZX+/Z//aueHQAAQE6BHea6HTLjxk+kZd/+X9Gyeq16ITXr6Dns5MwOLZbL73fJl0988om+EQAAgOwDO8wLOyxdrhIHspqVZkhYeJeu3Vk5tcNMTZ1KpggAAMAzYIe5bodlK1ZJtJGRkeHbWWk0jXKyMwBAJuXLC+oysurV01MBgB3muh3mFI/t8Jthd/fv1zcFADBkhxK/s8OTJ/UY4HNgh4Fjh3FRF155Rd8UAMD4tR126qTHAJ8DOwwcOxSHDuHrQwCyAnYIXAM7DCA7tFgslS7u2KFvDQAQsEPgDthhQNkhfl8KQFbADoFrXn9dj3lK8I0dtmrVauHChenp6bzarVs3x3RP8MoOLZaFC60/nAMAaOSlHW7aJMLC9EhvgB3mAbBDr+zw1KlT6urjx4/VVc/w0g5FZGTHjmLVKn0fADzl5KUd9usnwsP1SG+AHeYBsEOv7FDzv7S0NHXVM7y1Q4v1rU8YIAKgATsEroEdemWHJUuWVFfr+eIm84EdJiU9/7w4e1bfDQBPM7BD4BrYoVd2KKz3WPlXXnklKCgoODhYT/MIH9ihxXLxIgaIADgAOwSugR16a4c+xyd2mCkh/v53fWcAPJ3ADoFrYIcBbocYIwLAwA6Ba2CHvrTDmjVr6lE5x5d2OHFifLwoWlTExOi7BOBpA3YIXAM79NAOr127FutIPDmPL/ClHZLatqUdVK6M2wk87cAOgWtghx7aYfaZv3BR+087c7h4qbLrozb8um//zl27166LmjhpimNeKz62Q5YQb74pFi/Wdwx8Cx5NmxnYIXAN7NArO+zUqVPdunXv33eTU9reyNFjaRkUXK5E6bJqhuTkFKlcsUO71taf0v7ZyOb5Igvni/OhqlYVISE5U1jYE23dKoYPF8uXWwOaDk3aQUt/sRl/Oc6nE3PaIVXvEyf0SCOwwzwAduiVHTI3b96sUKHCoEGD9AQ70g779BtAy5p1GtSoXV/NsGbdeqlctUPT6lq1bCm2iK75r305541hk/932JB8w8jmnUoz7zL59nNAvgzW6NZO1bSpg4sbRbsyRho9PoBlZsxph0T79nqMEdhhHgA79IEdSpo3b96UmkxHrl67FjFydGJiYnp6eulylZYsW374yNGDMTErVq6OGDFKyyxy6WEpBHmt6zX03ok3MvZsstLe1y3R+XVRH0jVD29Y+0MsY39ISkRmypiUIzXIt0btYD2bL1l2rUhvvilee01EROjdKdYzz2QGhg619paIUqUyOxMff6x3L3bvzmwDYId5AOzQB3a4efPm5557bt68ebwaGhrqmO6e47+fkIIdQtDTqcfN9U4D61Qpa7cgOmQYSesHsIYWiZQymrf2dESTauROJY3cxWOPwAB26JUdBgUF9e/fX4/NOUeO/iYFO4QgyORKrvfErU+WtLr1b4Wto/ao//vE6NDfVIhc1MKqX5pEdn5Rt2ejAat67jmrE7dsmenBU6ZY3ffwYb2N9AmwQ6/sMDeAHUIQBLmQcQBtVHzbYaRjYZGkBZblU0Lj+na8KUe6r76q++4rr4g//1kfAWdHs2bpj7izKfPgAzvs2LHjW2+9lZSURAPEli1bWiwWj0eKQ8LC+/UfyIIdQhAEPdUaNuxU90lk5DyqZoW1jGtbJq55hctk52zh8qtlL/HWDskLORAaGrp+/XoOR0dHP8nhKbBDCIIgyI188Xpdxls7fOaZZzjgk3ccqsAOIQiCIDcyjx3+7W9/06N8BOwQgiAIciPz2OF///d/RzpDz5c9hoSFkwtK6cnGgoAgCIKeZpnHDnMP2CEEQRDkRrBDCIIgCDKdHfbt2zcoKCghIUFP8ALYIQRBEORGZrNDJi0trU2bNnXr1k1OTtbTsseQsPC6DRqxYIcQBEGQG5nTDplbt26VKVNGj805sEMIgiDIjUxoh48ePWrdujUNDVNSUvQ0j4AdQhAEQW5kQjv0ObBDCIIgyI3MY4cVK1aMjY3VYz1l/oJFUrBDCIIgyI3MY4fEw4cPX3/99SZNmly9elVP8wLYIQRBEORGprJDSVJSUvPmzV999dU1a9boaTkHdghBEAS5kTntUKVKlSp6VA6BHUIQBEFuZH47dEvxUmVpuXPX7rXroiZOmqInww4hCIIgtzK5Hfbr10+PMjBv/kJahtSsoyfYgR1CEARBbmQeO9y3b99kR+bMmZORkaHnc6RClWpshyVKW8eIkiFh4YWLl2TBDiEIgiA3Mo8dekbxUmWLFC9VqkyFXn2s40in9gk7hCAIgtzI3+1Q2B+WEu9Vq9WkucUx0QrsEIIgCHIjs9nh+vXrCxYs+Oyzz4aEhLzwwgudOnXSc+Qc2CEEQRDkRmazw9TUVFoWLlyYV0+cOOGQ7BGwQwiCIMiNzGaHDx48oGXjxo15NT093SHZwKrVaypVrc7ZqoTUbNi4mZ4DdghBEAS5ldns8L333hO29x326tXr+PHj+fPn13M4smrNWmH9HWlE1x699DQ7sEMIgiDIjcxmhx5QrKT1nYjaHy1UYIcQBEGQGwWAHRINGzerUbu+GjNl6nQp2CEEQRDkRmazw1u3bnGA56MZP368Q7KBJcuWV6pa/dGjRwJ/tIAgCII8ltnskH8Uk5KSoid4AewQgiAIciOz2WH37t1pWaaM9etAIiEhwSHZI2CHEARBkBuZzQ4fPHgwZswYuTpw4EAl0UNghxAEQZAbmc0OcwPYIQRBEORGsEMIgiAIgh1CEARBkP/bYaFiJbdu2x65dNnOXbvXrouaOGmKngN2CEEQBLmVCe0wKSlp27ZtW+3oyc6Y/N20kJp19Fg7sEMIgiDIjcxmh2+99ZYe5Y7ylaoKwyRtQ8LCyQWl1CQrxoKAIAiCnmaZzQ4f5/CAho/M/FdGrz7WWWwyMjIckm3ADiEIgiA3yqH7uMA3dli1alU9ymtghxAEQZAbmc0O/2RAz5FzYIcQBEGQG5nNDnMD2CEEQRDkRrBDCIIgCDKRHS5dupSWlQzo+XIO7BCCIAhyI/PYYe4BO4QgCILcCHYIQRAEQbBDCIIgCIIdQhAEQZAFdghBEARBFvPZYUxMTKFChYYOHcqrVapUcUj2CNghBEEQ5EZms8O5c+eqqzdu3FBXPQN2CEEQBLmR2ewwMTFRXb127Zq66hmwQwiCIMiNzGaHL7/8srr6zjvvqKueATuEIAiC3MhsdkhERUU1a9asSZMma9as0dMM9O775eTvplFg567da9dFTZw0Rc8BO4QgCILcyoR2mFPYDkNq1tET7MAOIQiCIDcKGDssUbqsGjkkLPzDjz5hwQ4hCIIgNzKPHcbFxUU6Mnv27JdeeknP50hiYuKoMV9fv37jYEzMipWrI0aM0nNgdAhBEAS5lXns0LfQ6LC5pRULdghBEAS5UaDaoQrsEIIgCHIjc9rh+fPnT9vR03IO7BCCIAhyI7PZ4T/+8Y/ExMSCBQumpaV169YtPj5ez5E9Yo8dk4IdQhAEQW5kNjtMSkqiZaFCmQa2ZcsWh+Rsc+xYnBTsEIIgCHIjs9nh/v37aTly5EhejY2NdUj2CNghBEEQ5EZms8OGDRtyoEyZMvnz5+/Xr59juifADiEIgiA3Mpsd+oohYeE9e/dlwQ4hCIIgNwpUO1SBHUIQBEFuZDY7/Omnn/Qor4EdQhAEQW5kNjtsYEDPkT3OnD0nBTuEIAiC3Mhsdugr9h84KAU7hCAIgtwoUO1QBXYIQRAEuZHZ7LBVq1YLFy5MT0/n1W7dujmmewLsEIIgCHIjs9nhqVOn1NXHvjg+2CEEQRDkRr6wG8Y3dqj5X1pamrrqGbBDCIIgyI3MZoedO3deuHAhh0+ePNmlSxfHdE+AHUIQBEFuZDY7ZGJjY48eParHegrsEIIgCHIjc9qhb4EdQhAEQW5kNjtMTk5+9dVXq1atyqstW7Z0SHbHu0WD9CjYIQRBEORWZrPD/v37q6tnz55VV12zas3arj166bGwQwiCIMitzGaH9+7dc7HqgqO/xaakpHTr0ZtXh4SFV69VjwU7hCAIgtzIbHb48ssvq6vFihVTV11AnleqTIV3ChfXEzA6hCAIgtzKbHYobM9LX3zxxeeff75Pnz56mjvk6FAFdghBEAS5kQnt0OfADiEIgiA3gh1CEARBkIns8OWXX46IiJhgQM+Xc2CHEARBkBuZxw69JGrjJj3KDuwQgiAIcqOAsUOie68+ITXrcHjipClSsEMIgiDIjQLJDrMCdghBEAS5UcDY4Zivv9m1e8/CxZF6AuwQgiAIcquAsUMXwA4hCIIgN4IdQhAEQRDsEIIgCIJghxAEQRBkgR1CEARBkAV2CEEQBEGWALLDlatWR2/dNmPmbD0BdghBEAS5VcDYIdO95xd6FOwQgiAIcqtAssNGTVs8fPiQw99M+FYKdghBEAS5UcDY4fTvZ12+fCUxMVFPwOgQgiAIcquAsUMXwA4hCIIgN4IdQhAEQRDsEIIgCIJghxAEQRBkgR1CEARBkAV2CEEQBEEW2CEEQRAEWWCHEARBEGSBHUIQBEGQBXYIQRAEQRbYIQRBEARZYIcQBEEQZIEdQhAEQZAFdghBEARBlgCyw8uXr9DywMEYPQF2CEEQBLlVwNghI+0w5tBhKdghBEEQ5EaBaoenTp2Wgh1CEARBbhQwdpiampqYmBi1cZOegIelEARBkFsFjB1qrFy1Rgp2CEEQBLlRoNrh3n37pMgO1dUly5Zf/PRTf1fcBx8YI/1LgXEKCR07GuP9S4FxIYyRfqcAOAu/PgVpEDt371Ytg6QbjDv8xg5r1W2gnao/KnzEKGOkf0m7Lv6o0V+P27VnrzHev1S1Rm1jpH+pdv2Gxki/07DhI42R/qUAuKnHjBuv2eHESVN0g3GHuexQRXtY2qS5RV31U37+Zb4e5W84eYjtb0QuWfbYdw9Y/l3Ub9hYj/I3mrb8UI/yQ+b+/Ise5W8EwE29dNmKR48eqTGwQ7MDOzQDsEOTADs0CQFwU8MO/Q/YoRmAHZoE2KFJCICbOsDtEAAAAMgzzG6HwyJGFCpWMinpOq8ejIkpWKTEwsWRjrnMCA1BqM9FSkxMTEtLK1WmAp9Fx06hFKbRCS31bczHocNHWn/cTq6q5S97lJWqVpcZzEmR4qVkWL0uX341iCMTEi580v5TmceEVK1Ru06D9+Wqel+8WzSII03ex5/78zw65qgNG3mVqg1fCKEcea8+/ajKyU3Mxo0bN+m2/fCjT2RMw8bNqoTUpMDyFas4JiMjg+4RmcGE0PGXLlfp1q1bvMpXgS/B2HETONL8NzU1qh+0/liu0lWgayGUuuTBTW12O+Sb5813CvMqtwiDw8LVPOZkwreTZbhMhcrCdhanz5zlWli+UlW2w+KlyspsJuTW7duqHarl37TFBxwZ2q2nzGBCZv/wo3rbqNeF7PDevXsUqFarbk7vnLyEjIQDK1au5oB6X/Tu+yUtyd3bdjDvKRA/zbM+VAwuX4lX5U0tlCaMKpuZ7XDO3J9peSzuON/F835ZwPE9evclO9y1ew+F+3812Mx2ePXatfT0dArUrt+QY9R+Od0IHDD5TU2Qo8v7umuPXhxYsGgx9ag8vqlNbYfHfz+Ret/66Q2bNOeYkaPH0nJL9FYll0mh/vumzdEjRo09Fx/PdzudxQ8//sSpFEO1kLr8DtuYEtUO1fKn00lNTV23fsNvsbEygzlR7VC9LmSHllZtKHL02HE5vXPynu49v+CAdl/w5aCeSvtPOz/JbUpo5PRZl885TH3EuOO/v1etlrDdDnv2/nr9+o3k5BQz26GwdWTlHdGpS1cOBAWXIzvkhxCNm7U0sx0Sp06featgEboWvFrxvWonT56igTuFz5w96xc39dp1UY8fP5b3dYnSmYOKzqHd6Nb2+KY2tR0mJV2/eOmSsF0wjunTbwAtF0cuVbOZnC/69mc7pLNYtWYtR/LTiSIlSjtkNSWqHarlT21xqzZtqQkw+Z0jHO1QQteF7JB6wdFbt9FqTu+cPKZR0xYPHz7ksHZfkB3u238gYuRok9vhnTvJ3Xr01iLj489fuHiR74X3m7QQtufzWh6z8ccff2zeEk2BgYOHckzNOg3IDmn4e/vOHRo4mtwOGXV0LmzjKg74xU09aEiYUO7rGrXry3iyQ49valPbobA9wj595izd5zyuogEyNQTvFC6u5zMfNJA6GBMz4dvJly9fmTp9RkzMIW6t6ArtP3Dw+1k/8DOKjp1C9S3NRGJiYosPWtOSwnSHqOVPdkh9sfVRZu9I0sE3bfEBnwK1X+p1ITu0folbtqLI+Z2Tl0z/fhYdLZ1CSkrK1u07hHJfCJsd0uiEAia3Q/KMRBu8WjQomML8dI7scM7cn4cOGy7MbYdUkWhoPmjoMPK8latW3717d8zX3yxZtvzwkaP83SFfCDPbIfWlhoRF0Fm0/PAj/taDjvZaUlLh4tbRobANsPzipub7OiHhAl0FuqNXrFwdMWIUDW3JDj2+qc1uhwAAAEAeADsEAAAAYIcAAAAA7BA8VXz5pfU/Cf92Hjx4oMXUqVNHi3HB119/rUd5xKZNTt4zyhiPMCvS0tL0KAD8E9ghMC/Nmln/V+tDXNjh559n/gEgDzCazb/FDl1gPMIXX3xRi8k+//Ef/8GBzZs3FytWjMODBmXOgTB06FAOMAULFlRXAcgzYIfAvMAOneJ3dvjJJ59woEaNGkWKFOHwM888wwHYITAJsENgXlQ7lM3osmXLOLB379769TP/bxQWFsZ/K65QIXOKDfKM0FDrn1geP348ffp0jmQ7PHfuHK8K2x/DOaDaYbt2mX+1TEhIkJHMtWvXOJCYmMj7ocP47LPPOHLEiBEceP311zlw4MABDqi0b9+eA1u3br1v+0O9tMNFixZxYM+ePSkpKRQ4efIkx0jYDi9fvjxx4kQt6dlnn6Xl2LFj//KXv3BMt27dhPXvffHHjx/nmO7du3NAPiz9448/OFCrlvVP8USTJk04EB1t/YOdyNoO+WEpHX9cXBzH5M+f3yGHQtOmTalI+Q+UU6ZkzrAMOwQmAXYIzItqh716Zc7DRPz444/C5kMypmjRohyYOnUqB9QhlDQn4+hQmqu0w/Xr1z9Jdgn7inoYPDsUERUVJSONbN++XYZbtLD+91za4X/913/JJD794OBgGcPQqZETz549W4snRo0aRcvnnnsuPNz6l7L09PSkpCQKlCtn/T+cBtsh+zHTs2fm1FxknzIyJiZGZMMOZUyfPn2eJDvCnYORI0cePXqUpwoTsENgGmCHwLyodkhjjtftDBs2TDj6UIMGDTgwZ84cDqh22KhRIw6wHdKoi8yP3/E0c+ZMTpJ2SI4iP+h1u4+qSDNr3bq1cDwMydWrV/UohTt37sjwG2+8IRQ7kb5O/POf/6Tl//zP/8gYhk7t5Zdf1iKZR48e3b59u0uXLjQCu3v37vjx4zn+T3/6k/Gk2A4PHTpk3zqznyEcH5bSEFbkxA41e2No7CiLmtzaYrHIJHZuSeHCDrOlAJBnwA6BeWnePHOuWmLatGlKipXs2yFbjrDboTr+MNrhypUrZaqRTp06ybALO9ywYYMepbBjh3VmGUYbHf7nf/6nluR0dEjLQoWcv7+id+/ePPPLgAEDXnjhBY4sWTJzzhEV4+iwR48eHDDa4UsvvSRjVLJphx999NGbb77J4YYNG/75z3+WSatXr5Zhok0b64STAOQ9sENgXsaMGSPDQUGZrzGSuLVDGiQJ23eH8gkq22FISAiv0lhK2qHqLjK/kbAw62SJRNeuXV3Y4WuvvcYBp98dkjdwwMV3h2SZycnJFDhx4gTHSKTTly9f3jHFihxN/uUvf+nQoQOHz549y3tTMX532LRpUw4Y7bBevXoyRiWbdkg2L4+WSkz+rJSRfZHo6Gj5wBmAPAZ2CAKTPPj5ZeAxevRoPQqApwbYIQhMYIcekNVXkgA8DcAOQWACO8w+JUqUyJ8/v3xSCsDTCewQAAAAgB0CAAAAsEMAAABAwA4BAAAAATsEAAAABOwQAAAAELBDAAAAQMAOAQAAAJEdO6QMN2/dTrp+A4IgCIICT9dv3Lx9J5nMzpUdksguU+7eS065C0EQBEEBKRoaktlpXqjb4UPrGPEh5YMgCIKggJRxXOjcDiEIgiDoKRTsEIIgCIJghxAEQRD0x6P/D7KtDZwhGTC5AAAAAElFTkSuQmCC" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Mining pool identification data needed to be manually updated now and then. I’ve &lt;a href="https://github.com/0xB10C/miningpool-observer/pull/77"&gt;changed&lt;/a&gt; this to automatically update if possible, and otherwise to fall back to older pool identification data. This means less ongoing maintenance work for me.&lt;/p&gt;
&lt;p&gt;My mining-pool observer project also found six OFAC-sanctioned transactions missing from blocks. I’ve analyzed them and found that four of these were likely filtered by F2Pool. I’ve published a &lt;a href="https://b10c.me/observations/08-missing-sanctioned-transactions/"&gt;blog post&lt;/a&gt; about this, which was well received. F2Pool admitted the filtering and claimed they’d stop censoring.&lt;/p&gt;
&lt;h3 id="peer-observer"&gt;peer-observer&lt;/h3&gt;
&lt;p&gt;I’ve got a request to check my mempool data to see if I’ve seen similar spikes in vByte/second being broadcast on the Bitcoin network as mempool.space did in September 2023. None of my nodes saw these spikes. However, it prompted me to &lt;a href="https://github.com/0xB10C/peer-observer/pull/11"&gt;add&lt;/a&gt; monitoring for it to my p2p monitoring nodes.&lt;/p&gt;
&lt;p&gt;&lt;img src="data:image/png;base64,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" alt=""&gt;&lt;/p&gt;
&lt;h3 id="bitcoin-data"&gt;bitcoin-data&lt;/h3&gt;
&lt;p&gt;My &lt;a href="https://bitcoinops.org/en/newsletters/2023/12/13/#call-for-community-block-arrival-timestamps"&gt;call&lt;/a&gt; for more &lt;a href="https://github.com/bitcoin-data/block-arrival-times"&gt;block-arrival timestamp data&lt;/a&gt; was mentioned in a Bitcoin Optech newsletter, and I received a few contributions adding more and &lt;a href="https://github.com/bitcoin-data/block-arrival-times/pull/14"&gt;older&lt;/a&gt; timestamps. I’ve also &lt;a href="https://github.com/bitcoin-data/stale-blocks/pull/3"&gt;updated&lt;/a&gt; the &lt;a href="https://github.com/bitcoin-data/stale-blocks"&gt;stale-block dataset&lt;/a&gt;. Along with the work on improving miner detection in fork-observer and miningpool-observer, I’ve also been updating the &lt;a href="https://github.com/bitcoin-data/mining-pools"&gt;mining-pools&lt;/a&gt; dataset, adding four new pools.&lt;/p&gt;
&lt;h3 id="bitcoin-core-tracing-support-for-macos-and-freebsd"&gt;Bitcoin Core tracing support for macOS and FreeBSD&lt;/h3&gt;
&lt;p&gt;The current Bitcoin Core tracing framework only supports Linux. I’ve revisited this and found a way of extending this framework to macOS and FreeBSD without requiring new dependencies or similar. I’ve bought an old Mac mini to test this and have a &lt;a href="https://github.com/0xB10C/bitcoin/tree/2023-11-macos-freebsd-tracing-support"&gt;branch&lt;/a&gt; where tracing on macOS is working. FreeBSD still needs more work. I plan to PR this against Bitcoin Core soon.&lt;/p&gt;
&lt;h3 id="find-non-standard-transactions"&gt;find-non-standard-transactions&lt;/h3&gt;
&lt;p&gt;I’ve got a request about non-standard transactions and MEV on Bitcoin. I had previously thought about a tool to find non-standard transactions being mined and took a shot at implementing a quick and dirty tool &lt;a href="https://github.com/0xB10C/find-non-standard-tx"&gt;https://github.com/0xB10C/find-non-standard-tx&lt;/a&gt;. I’ve used this to check the last 100k blocks and wrote &lt;a href="https://b10c.me/observations/09-non-standard-transactions/"&gt;a blog post about it&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="miscellaneous"&gt;Miscellaneous&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;for fork-observer and miningpool-observer I needed to have an automatically updating source of pool identifications. I modified my &lt;a href="https://github.com/0xB10C/rust-bitcoin-pool-identification"&gt;rust-bitcoin-pool-identification&lt;/a&gt; library to &lt;a href="https://github.com/0xB10C/rust-bitcoin-pool-identification/pull/6"&gt;support&lt;/a&gt; this.&lt;/li&gt;
&lt;li&gt;With the release of Bitcoin Core v26, I &lt;a href="https://github.com/bitcoin-core/guix.sigs/pull/1004"&gt;contributed&lt;/a&gt; my GUIX build signatures for v26, and I’ve helped to &lt;a href="https://github.com/NixOS/nixpkgs/pull/272548"&gt;review&lt;/a&gt; and &lt;a href="https://github.com/NixOS/nixpkgs/commit/330b00b103978fdb03adcb4554ae676b2f8ebdb6"&gt;fixed a bug&lt;/a&gt; in the Nix Bitcoin Core package. I’ve also reviewed an &lt;a href="https://github.com/NixOS/nixpkgs/pull/277902"&gt;update of the btcd nix package&lt;/a&gt;, which I maintain.&lt;/li&gt;
&lt;li&gt;I’ve decided to discontinue the &lt;a href="https://bitcoin-dev.blog"&gt;https://bitcoin-dev.blog&lt;/a&gt; and have written about it &lt;a href="https://bitcoin-dev.blog/blog/the-end/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I quickly hacked together an &lt;a href="https://0xb10c.github.io/ocean-xyz-templates/"&gt;Ocean.xyz mining pool template fee comparison tool&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Is anyone actually reading these updates? If yes, please send me an 🐘 emoji.&lt;/li&gt;
&lt;li&gt;Responsibly disclosed a critical security vulnerability to a company in the Bitcoin space. Will publish more details sometime in the future.&lt;/li&gt;
&lt;li&gt;I helped Fabian Jahr on his Bitcoin Core asmap work by running and reviewing the results of his new asmap tools: &lt;a href="https://github.com/fjahr/asmap-data/issues/4"&gt;https://github.com/fjahr/asmap-data/issues/4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;After the recent btcd vulnerability disclosure, I added up a few btcd nodes to my monitoring and detected that the &lt;a href="https://delvingbitcoin.org/t/disclosure-btcd-consensus-bugs-due-to-usage-of-signed-transaction-version/455/5?u=0xb10c"&gt;vulnerability had been exploited on testnet&lt;/a&gt; a day after being published.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>An overview of recent non-standard Bitcoin transactions</title><link>https://b10c.me/observations/09-non-standard-transactions/</link><pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/09-non-standard-transactions/</guid><description>&lt;p&gt;This blog post provides an overview of non-standard transactions that
mining pools included in the last 117000 Bitcoin blocks.&lt;/p&gt;
&lt;p&gt;Usually, before a Bitcoin transaction is included in the blockchain, it is
relayed between nodes on the Bitcoin P2P network and cached in their mempools.
To mitigate some transaction relay Denial-of-Service attacks, the Bitcoin Core node
limits what transactions can be relayed. Transactions deemed as &amp;ldquo;standard&amp;rdquo; are
relayed, while &lt;a href="https://github.com/bitcoin/bitcoin/blob/5fbcc8f0560cce36abafb8467339276b7c0d62b6/src/policy/policy.cpp#L94"&gt;&amp;ldquo;non-standard&amp;rdquo;&lt;/a&gt; transactions are not relayed. In Bitcoin Core, these
rules are known as the policy rules. Mining pools can receive non-standard
transactions out-of-band or loosen the relay rules to accept them over the P2P
network. Non-standard transactions can be, and frequently are, mined.&lt;/p&gt;
&lt;p&gt;This post provides an overview of recent non-standard transactions included by
mining pools. Frequent non-standard transactions might show policy rules that need
to be adapted or changed, while ensuring no Denial-of-Service attacks are made
possible. Having an overview of which non-standard transactions are mined and
who mines them can serve as basis for future policy design. Additionally,
vulnerabilities in Bitcoin software came to light, which require mining
non-standard transactions to be exploited. Some of these vulnerabilities have been
&lt;a href="https://github.com/btcsuite/btcd/issues/1906"&gt;exploited&lt;/a&gt;, while others have been &lt;a href="https://delvingbitcoin.org/t/disclosure-btcd-consensus-bugs-due-to-usage-of-signed-transaction-version/"&gt;responsibly disclosed&lt;/a&gt;.
Some mining pools try to extract more value from blocks than just the block reward
by including non-standard transaction. This is generally known as Miner Extractable
Value (MEV). A large mining pool might be able to extract more value by investing
heavily into their transaction selection software to out-compete smaller mining pools.
This causes further mining pool centralization.&lt;/p&gt;
&lt;h2 id="methodology"&gt;Methodology&lt;/h2&gt;
&lt;p&gt;To detect non-standard transactions, two nodes are used. A data-node and a
test-node. The data-node has access to the full blocks that should be checked&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.
The test-node is synced to a height just below the test-start-height. It can be a
pruned node. Now, beginning from the test-start-height, blocks are requested from the
data-node. For each transaction in a block, excluding the coinbase transaction, we test
if the transaction is accepted to the test-node&amp;rsquo;s mempool using the &lt;code&gt;testmempoolaccept&lt;/code&gt;
RPC call. If the transaction is rejected from the test-node&amp;rsquo;s mempool, then the
transaction is non-standard. The rejection reason is recorded. If the transaction is
accepted, it is submitted to the test-node with the &lt;code&gt;sendrawtransaction&lt;/code&gt; RPC call. This
makes sure child transactions aren&amp;rsquo;t rejected due to parents from the same block not being available.
Once all transactions have been tested, the block is submitted to the test-node
with the &lt;code&gt;submitblock&lt;/code&gt; RPC call. This clears the mempool. The next block is tested
until the chain tip is reached. This methodology is implemented in the quick and dirty tool
&lt;a href="https://github.com/0xB10C/find-non-standard-tx"&gt;github.com/0xB10C/find-non-standard-tx&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While this methodology reliably detects non-standard transactions, there are a few
limitations. For example, there is only one reject reason returned for a transaction
even if the transaction would be rejected for multiple reasons. Furthermore, descendants of
rejected transactions can&amp;rsquo;t be added to the mempool as their inputs are missing.
These have to be analyzed manually. The ordering in the block might not represent
the order in which transactions entered the mempool. Usually, this isn&amp;rsquo;t a problem.
However, for the &lt;a href="https://bitcoinops.org/en/topics/cpfp-carve-out/"&gt;CPFP carve-out rule&lt;/a&gt; to be applicable, the 25th descendant has to
be added last. This results in one transaction being rejected with an
&amp;ldquo;too-long-mempool-chain&amp;rdquo; when the CPFP carve-out rule was used (but also allows
detecting and measuring the use of CPFP carve-out).&lt;/p&gt;
&lt;h2 id="non-standard-transactions-by-rejection-reason"&gt;Non-standard transactions by rejection reason&lt;/h2&gt;
&lt;p&gt;Here, a start height of 710575 (2021-11-20) and an end height of 827622
(2024-01-27) was used, which results in about 117k blocks checked for
non-standard transactions. The start height was chosen to be shorty
after Taproot activation. A Bitcoin Core v26.0 node was used as test-node.
The following overview does not cover all 20k non-standard transaction
detected. Rather, it picks out a few interesting examples and tries to
highlight the reasoning behind these non-standard transactions, where possible.&lt;/p&gt;
&lt;h3 id="bad-txns-nonstandard-inputs"&gt;&amp;ldquo;bad-txns-nonstandard-inputs&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;In January 2023, between block 771609 and block 773957, &lt;em&gt;MaraPool&lt;/em&gt; included 16
non-standard transactions in their blocks. These were all rejected with the
reason: &lt;code&gt;bad-txns-nonstandard-inputs&lt;/code&gt;. In this case, the &lt;code&gt;redeemScript&lt;/code&gt;s in the
inputs have more sigops than allowed. This is related to Bitcoin Core PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/26348"&gt;#26348&lt;/a&gt;
by Sergio Demian Lerner, where he describes that the RSK sidechain migrated
to a non-standard P2SH redeemscript with now more than 3000 BTC stuck on there.
&lt;em&gt;MaraPool&lt;/em&gt; helped RSK by mining the non-standard transactions. Sergio noted, that
their tests on testnet passed. At the time, non-standard transactions were allowed
by default on testnet. This was later changed in the Bitcoin Core PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/28354"&gt;#28354&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="tx-size"&gt;&amp;ldquo;tx-size&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;As a protection against excessive resource usage, Bitcoin Core does not relay and rejects transactions
larger than 100 kvB (400 kWU; &lt;code&gt;MAX_STANDARD_TX_WEIGHT&lt;/code&gt;). These are rejected with the reason &amp;ldquo;tx-size&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Luxor&lt;/em&gt; pool mined the 985 kvB (3.94 MWU), zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0ae&lt;/span&gt;
&lt;a href="https://mempool.space/tx/0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0ae"&gt;0301e0480b&lt;/a&gt;
in block 774628 (2023-02-01). This transaction &lt;a href="https://ordinals.com/inscription/0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0aei0"&gt;inscribes&lt;/a&gt; an 1803 × 1803 pixel JPEG showing
a taproot wizard with a size of 3.9 MB and fills nearly the entire block.&lt;/p&gt;
&lt;p&gt;Block 776884, mined by &lt;em&gt;Terra Pool&lt;/em&gt;, includes the 850 kvB transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7f&lt;/span&gt;
&lt;a href="https://mempool.space/tx/b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7f"&gt;b5a7e05f28&lt;/a&gt;
,
paying nearly 0.5 BTC in fees. The transaction inscribes a 1-minute MP4 music video
showing a &lt;a href="https://ordinals.com/inscription/b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7fi0"&gt;frog holding a drink&lt;/a&gt;. In block 777945, the ‎975 kvB transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;79b91e594c03c8f06d70c44a288a88a413c540abca007829ca119686a7f979da&lt;/span&gt;
&lt;a href="https://mempool.space/tx/79b91e594c03c8f06d70c44a288a88a413c540abca007829ca119686a7f979da"&gt;79b91e594c&lt;/a&gt;
pays a fee of nearly 0.75 BTC to inscribe a 4000×5999 pixel WEBP image. The 992 kvB transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;4af9047d8b4b6ffffaa5c74ee36d0506a6741ba6fc6b39fe20e4e08df799cf99&lt;/span&gt;
&lt;a href="https://mempool.space/tx/4af9047d8b4b6ffffaa5c74ee36d0506a6741ba6fc6b39fe20e4e08df799cf99"&gt;4af9047d8b&lt;/a&gt;
, mined in block 786501,
pays nearly 0.5 BTC in fees to inscribe a 2040×2653 pixel JPEG image of a Bitcoin Magazine cover
with the &lt;a href="https://ordinals.com/inscription/4af9047d8b4b6ffffaa5c74ee36d0506a6741ba6fc6b39fe20e4e08df799cf99i0"&gt;face of Julian Assange&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F2Pool&lt;/em&gt; frequently includes larger-than-standard transaction in their blocks. These are mainly
zero-fee mining pool payout transactions with one input and more than
3000 outputs. Examples are
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;b460f758881bd1dd03da99ce6fb465637cbdcf6c6bb4664a4fedaf18d033e57f&lt;/span&gt;
&lt;a href="https://mempool.space/tx/b460f758881bd1dd03da99ce6fb465637cbdcf6c6bb4664a4fedaf18d033e57f"&gt;b460f75888&lt;/a&gt;
,
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;56c5b34eaca34967bc22dad74887339d94c620a6e33f3294e8ad2fbd24baa45e&lt;/span&gt;
&lt;a href="https://mempool.space/tx/56c5b34eaca34967bc22dad74887339d94c620a6e33f3294e8ad2fbd24baa45e"&gt;56c5b34eac&lt;/a&gt;
,
and &lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;8239fbb0da35b5f6bbbcff2a0c2b00d7b2f21efb47387bce6816b42e7fc3e61f&lt;/span&gt;
&lt;a href="https://mempool.space/tx/8239fbb0da35b5f6bbbcff2a0c2b00d7b2f21efb47387bce6816b42e7fc3e61f"&gt;8239fbb0da&lt;/a&gt;
.&lt;/p&gt;
&lt;p&gt;There are two &lt;em&gt;F2Pool&lt;/em&gt; transactions that are not pool payouts and stand out. The zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;7884712aae8685406c3b8f264fca151d76fc20ed3071d21275a6b6fc8dc2a82a&lt;/span&gt;
&lt;a href="https://mempool.space/tx/7884712aae8685406c3b8f264fca151d76fc20ed3071d21275a6b6fc8dc2a82a"&gt;7884712aae&lt;/a&gt;
has 800 inputs and 800 outputs, with a size just above 152 kvB. All inputs and
outputs are 546 sat. This transaction moves F2Pool&amp;rsquo;s &amp;ldquo;uncommon sats&amp;rdquo;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;
to a different address. Transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;73be398c4bdc43709db7398106609eea2a7841aaf3a4fa2000dc18184faa2a7e&lt;/span&gt;
&lt;a href="https://mempool.space/tx/73be398c4bdc43709db7398106609eea2a7841aaf3a4fa2000dc18184faa2a7e"&gt;73be398c4b&lt;/a&gt;
has a size of 125 kvB and pays the complete input amount of 0.03682719 BTC ($750 USD
at the time) as fees. The only output is an OP_RETURN output with the message
&amp;ldquo;&lt;code&gt;you'll run cln. and you'll be happy.&lt;/code&gt;&amp;rdquo;. This transaction exploited a &lt;a href="https://github.com/btcsuite/btcd/issues/1906"&gt;consensus
bug in btcd&lt;/a&gt; causing, for example, LND, to stop processing new blocks. This transaction
was constructed by &lt;a href="https://github.com/brqgoo"&gt;brqgoo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="non-mandatory-script-verify-flag"&gt;&amp;ldquo;non-mandatory-script-verify-flag&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;The transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;00c1af8f7d9e2bfe854d1718db078eb6740920f702bc00d4a9f205d3a084c40e&lt;/span&gt;
&lt;a href="https://mempool.space/tx/00c1af8f7d9e2bfe854d1718db078eb6740920f702bc00d4a9f205d3a084c40e"&gt;00c1af8f7d&lt;/a&gt;
is non-standard with the reason &amp;ldquo;non-mandatory-script-verify-flag (OP_SUCCESSx
reserved for soft-fork upgrades)&amp;rdquo;. This transaction has a size of 104 vB and pays a
fee of 5000 sat. It was also &lt;a href="https://x.com/brqgoo/status/1602366985442136101"&gt;constructed by brqgoo&lt;/a&gt; and, similar to the previous
transaction, also includes an &lt;code&gt;OP_SUCCESS&lt;/code&gt;. It was, however, mined before the transaction
that exploited the consensus bug in LND and might have been a test transaction. The
only output is an OP_RETURN with a 32 byte value, which could be a hash
commitment to the later exploited vulnerability.&lt;/p&gt;
&lt;p&gt;possible hash commitment: &lt;code&gt;5d86c2f206e5d9eb8c797035039ffac92ed94f794f9dab764be0ba40491dec47&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="scriptpubkey--multi-op-return"&gt;&amp;ldquo;scriptpubkey&amp;rdquo; &amp;amp; &amp;ldquo;multi-op-return&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;In block 826775, &lt;em&gt;F2Pool&lt;/em&gt; mined transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;c3dd9ae8b5e9811514ef15238e0de96db8cf2f17ea4bdc41942e8c591c961a25&lt;/span&gt;
&lt;a href="https://mempool.space/tx/c3dd9ae8b5e9811514ef15238e0de96db8cf2f17ea4bdc41942e8c591c961a25"&gt;c3dd9ae8b5&lt;/a&gt;
,
which included a 100 byte OP_RETURN message. Similarly, in block 826796, F2Pool mined transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;ee8bbff798c5b07cfdf0a7379fb8ef2e05d91fcfac2daacb21de30c1f007bf79&lt;/span&gt;
&lt;a href="https://mempool.space/tx/ee8bbff798c5b07cfdf0a7379fb8ef2e05d91fcfac2daacb21de30c1f007bf79"&gt;ee8bbff798&lt;/a&gt;
with a 686 byte OP_RETURN message, in block 827419
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;17ec5d70668578d7e572eb9f821741759d1fe4ee4d6ca82ce35cb051d54bcf20&lt;/span&gt;
&lt;a href="https://mempool.space/tx/17ec5d70668578d7e572eb9f821741759d1fe4ee4d6ca82ce35cb051d54bcf20"&gt;17ec5d7066&lt;/a&gt;
and
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;2c869583a8dc9109ac60760c9cfbfdf25e34416a0e4ad858f096a5a38e1eedda&lt;/span&gt;
&lt;a href="https://mempool.space/tx/2c869583a8dc9109ac60760c9cfbfdf25e34416a0e4ad858f096a5a38e1eedda"&gt;2c869583a8&lt;/a&gt;
with 111 byte and 400 byte OP_RETURN messages. OP_RETURN is, by default, limited to 80 bytes of data.
These transactions were rejected with the reason &amp;ldquo;scriptpubkey&amp;rdquo;. F2Pool also mined the transactions
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;cb22f12d777e95e426fd31840171af453c600390276d29d307ef34dbaeda4387&lt;/span&gt;
&lt;a href="https://mempool.space/tx/cb22f12d777e95e426fd31840171af453c600390276d29d307ef34dbaeda4387"&gt;cb22f12d77&lt;/a&gt;
and
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;6f660e9f1bbfcc8435593eb8ff70a501275ad6cdbaa536747bd9d55bdbeda65a&lt;/span&gt;
&lt;a href="https://mempool.space/tx/6f660e9f1bbfcc8435593eb8ff70a501275ad6cdbaa536747bd9d55bdbeda65a"&gt;6f660e9f1b&lt;/a&gt;
,
which have five and two OP_RETURN outputs. As, by default, only one OP_RETURN output is allowed, these are
non-standard with the reason &amp;ldquo;multi-op-return&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;While these transactions are non-standard when using Bitcoin Core, they are standard when using Peter Todd&amp;rsquo;s
&amp;ldquo;Libre Relay&amp;rdquo; patch. In particular, &lt;a href="https://github.com/petertodd/bitcoin/commit/537862ff0df30e61de422176f7131a97bd78b13f"&gt;these changes&lt;/a&gt; allow large OP_RETURN messages and multi-OP_RETURN
transactions. &lt;em&gt;F2Pool&lt;/em&gt; might be running the &amp;ldquo;Libre Relay&amp;rdquo; patches.&lt;/p&gt;
&lt;p&gt;While writing this blog post, more non-standard &amp;ldquo;scriptpubkey&amp;rdquo; transactions were mined by &lt;em&gt;F2Pool&lt;/em&gt;.
For example, the 3.22 kvB transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;8c9fe58186c199fa9f8d72c121a45270f70d3854e67238f3c7c319989a50921b&lt;/span&gt;
&lt;a href="https://mempool.space/tx/8c9fe58186c199fa9f8d72c121a45270f70d3854e67238f3c7c319989a50921b"&gt;8c9fe58186&lt;/a&gt;
encodes a PNG image and
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;33a271fd7782754e3a73fd1b791be920898317f73b1c77cf6b94d9d718bce701&lt;/span&gt;
&lt;a href="https://mempool.space/tx/33a271fd7782754e3a73fd1b791be920898317f73b1c77cf6b94d9d718bce701"&gt;33a271fd77&lt;/a&gt;
a GIF. For more transactions, see this &lt;a href="https://twitter.com/mononautical/status/1751303255403937806"&gt;tweet thread&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="min-relay-fee-not-met"&gt;&amp;ldquo;min relay fee not met&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;By default, Bitcoin Core has a minimum relay fee of 1 sat/vByte. Transactions
below this feerate are rejected and not relayed to other nodes.
These are often sent to the mining pool out-of-band. Some mining pools
might opt to mine their own zero-fee transactions. By creating zero-fee
transactions and using, for example, the &lt;code&gt;prioritisetransaction&lt;/code&gt; RPC to assign
a virtual fee, they avoid their transaction from being relayed but still can
get it accepted into their mempool. If they construct a transaction that pays
a fee, they risk that it&amp;rsquo;s relayed to another pool and pays a competing mining
pool. Additionally, depending on the mining pool&amp;rsquo;s payout and fee structure,
including their own zero-fee transactions might shift the transaction fee costs
to their miners. As the pool&amp;rsquo;s zero-fee transaction takes up block space, it
reduces the transaction fees earned by their miners. The miners might not be aware
of being exploited by the pool.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ECMD Pool&lt;/em&gt; mined
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;b1360f73a7a990d5210ac43482ec4de83b76c3932522702dca6b9bb5f6468261&lt;/span&gt;
&lt;a href="https://mempool.space/tx/b1360f73a7a990d5210ac43482ec4de83b76c3932522702dca6b9bb5f6468261"&gt;b1360f73a7&lt;/a&gt;
paying 7400 sat at 14 kvB resulting in a 0.52 sat/vB feerate. This transaction
consolidates 96 inputs to the sending address and sends 43k USDT (Omnilayer) to
Huobi&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; in a nearly-dust output. The consolidated inputs don&amp;rsquo;t
seem to stem from &lt;em&gt;EMCD Pool&lt;/em&gt; themself. It&amp;rsquo;s unclear why the transaction did not
pay more than the minimum relay fee of 1 sat/vByte. The output of the transaction was
spent in the same block with a feerate of 89.1 sat/vB&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. While
this results in an effective feerate of over 2 sat/vB through CPFP, the first transaction
would not have been relayed.&lt;/p&gt;
&lt;p&gt;Transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;9ffad0add040094e7d61dd021a2750eebd034042467b0ce0dbc3f4a4b3aac07d&lt;/span&gt;
&lt;a href="https://mempool.space/tx/9ffad0add040094e7d61dd021a2750eebd034042467b0ce0dbc3f4a4b3aac07d"&gt;9ffad0add0&lt;/a&gt;
,
paying a feerate of 0.43 sat/vB with a fee of 7701 sat and a size of 17 kvB,
was mined by &lt;em&gt;SBICrypto&lt;/em&gt;. It consolidates 100 inputs into a single output. The
addresses belong to the freebitco.in entity&lt;sup id="fnref1:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. Why freebitco.in
constructed a transaction paying below the minimum relay feerate is not known.&lt;/p&gt;
&lt;p&gt;The transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;4da9b2ab358a76c0dd9067b91f07078e25b77c82439fe58450ae206c3deb4464&lt;/span&gt;
&lt;a href="https://mempool.space/tx/4da9b2ab358a76c0dd9067b91f07078e25b77c82439fe58450ae206c3deb4464"&gt;4da9b2ab35&lt;/a&gt;
was mined by an unidentified pool&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. It does not pay fees
and script-path-spends a 2-of-2 multisig P2TR output. The parent transaction
creates an OP_RETURN output with a valid but unused bech32 address
&lt;code&gt;bc1qypz2ynlgy3dmpqxxvap2s30wjfqcp24tk46p83&lt;/code&gt;. It&amp;rsquo;s unknown why the
transaction does not pay fees.&lt;/p&gt;
&lt;p&gt;The mining pool &lt;em&gt;Terra Pool&lt;/em&gt; mined the zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;1a6466600e05ccf5b63ded29d4a1a067ce33b79cd1252754ead733f21fae1775&lt;/span&gt;
&lt;a href="https://mempool.space/tx/1a6466600e05ccf5b63ded29d4a1a067ce33b79cd1252754ead733f21fae1775"&gt;1a6466600e&lt;/a&gt;
in block 811775 creating a 10k sat output. The output was directly consumed by
the next, also a zero-fee, transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;f9aa65bb55baa3e2ba95deac89afc99d28934cdb613e72abe20b1f25a0c848ee&lt;/span&gt;
&lt;a href="https://mempool.space/tx/f9aa65bb55baa3e2ba95deac89afc99d28934cdb613e72abe20b1f25a0c848ee"&gt;f9aa65bb55&lt;/a&gt;
in the same block. It &lt;a href="https://ordinals.com/preview/f9aa65bb55baa3e2ba95deac89afc99d28934cdb613e72abe20b1f25a0c848eei0"&gt;inscribes&lt;/a&gt; a &amp;ldquo;Clean Bitcoin Certificate&amp;rdquo;.
In a block mined by a &amp;ldquo;Clean Incentive&amp;rdquo; pool, the transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;d2a769546e9182d8b23765850e3ef605eec48fe641822bcce86594417072c96b&lt;/span&gt;
&lt;a href="https://mempool.space/tx/d2a769546e9182d8b23765850e3ef605eec48fe641822bcce86594417072c96b"&gt;d2a769546e&lt;/a&gt;
creates an additional 10k sat output. This output, and the previously inscribed certificate,
are then consumed by another zero-fee transaction,
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;ed8819d17dbd6ab0d934b24d1e6dadd6bcd7b91866a9a235a081576421c68022&lt;/span&gt;
&lt;a href="https://mempool.space/tx/ed8819d17dbd6ab0d934b24d1e6dadd6bcd7b91866a9a235a081576421c68022"&gt;ed8819d17d&lt;/a&gt;
,
which inscribes an accompanying &lt;a href="https://ordinals.com/preview/ed8819d17dbd6ab0d934b24d1e6dadd6bcd7b91866a9a235a081576421c68022i0"&gt;JSON object&lt;/a&gt; with more information about the certificate.
This is later moved in yet another zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;7cc18418b814a1e9fe731e630e638201c36bf45e1f20aa084e0182a43cdc8174&lt;/span&gt;
&lt;a href="https://mempool.space/tx/7cc18418b814a1e9fe731e630e638201c36bf45e1f20aa084e0182a43cdc8174"&gt;7cc18418b8&lt;/a&gt;
,
where a collection of inscriptions is inscribed.&lt;/p&gt;
&lt;p&gt;The same pool also mined the zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;699e9a96320465223172d739e0fb8723c5951314e4aba31d9ed7d1703b5b07d0&lt;/span&gt;
&lt;a href="https://mempool.space/tx/699e9a96320465223172d739e0fb8723c5951314e4aba31d9ed7d1703b5b07d0"&gt;699e9a9632&lt;/a&gt;
,
which has one input and one output. It spends a 1-of-2 P2SH output created
in the same block. The 1-of-2 multisig is composed of two public keys with well-known
private keys. The private keys are &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;. The transaction creating
the multisig output has a feerate of 3.9 sat/vB and is the first transaction in
the block after the coinbase. As the remaining transactions in the block pay a
significantly higher feerate, this is an indication the transaction was manually
prioritized by the pool. The pool might run a tool that checking for UTXOs spendable
with well-known private keys, and tries to spend and mine them before someone else
does &lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;MaraPool&lt;/em&gt; mined the zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;0026faeccb25b9837ccf9d8e7b88f676669967263bcd8d5de1b55fedb08a328d&lt;/span&gt;
&lt;a href="https://mempool.space/tx/0026faeccb25b9837ccf9d8e7b88f676669967263bcd8d5de1b55fedb08a328d"&gt;0026faeccb&lt;/a&gt;
,
which script-path-spends a single P2TR output from a depth-2 merkle tree, revealing
a 1-of-1 multisig. The parent transaction has an OP_RETURN output with the data
&lt;code&gt;cc1qjdexdhgqu95zfkvw20vfn3uh9r6s563y86qpsd&lt;/code&gt;. It&amp;rsquo;s unclear why &lt;em&gt;MaraPool&lt;/em&gt; mined
this zero-fee transaction.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F2Pool&lt;/em&gt; frequently mines zero-fee transactions. These are mainly consolidations
of their coinbase outputs and pool payout transactions&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;. An
example for a coinbase consolidation is transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;11887bfc724d3486b7da3e70cf13cc992f16e25273b3a685a70a7890957310e5&lt;/span&gt;
&lt;a href="https://mempool.space/tx/11887bfc724d3486b7da3e70cf13cc992f16e25273b3a685a70a7890957310e5"&gt;11887bfc72&lt;/a&gt;
.
It consolidates 21 inputs from &lt;code&gt;1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY&lt;/code&gt;, the well-known
F2Pool coinbase output address, into two outputs paying no fees.&lt;/p&gt;
&lt;p&gt;However, it seems F2Pool is also using zero-fee transactions for payments to Huobi
(&lt;a href="https://oxt.me/transaction/44335aef14233becb8290bf6455e79de921075f84834e15ef978a130016b4db9"&gt;44335aef14&lt;/a&gt;) and Binance (&lt;a href="https://oxt.me/transaction/a5c062118a4132a8e3441be989de1fb6dea03fdcfaeac8a731b6eb9acca17a5c"&gt;a5c062118a&lt;/a&gt;). While these are small, one-input two-output
transaction and don&amp;rsquo;t take up much block space, they reduce the fees earned in the block
and the payout distributed to F2Pool&amp;rsquo;s miners.&lt;/p&gt;
&lt;p&gt;In block 730459, F2Pool mined the zero-fee, one-input one-output transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;26a9e3872fadcee3144aae6629e887500d33813217b5447d2fb8e5ae6e5e0329&lt;/span&gt;
&lt;a href="https://mempool.space/tx/26a9e3872fadcee3144aae6629e887500d33813217b5447d2fb8e5ae6e5e0329"&gt;26a9e3872f&lt;/a&gt;
,
which moved 3900 BTC from &lt;code&gt;1J1F3U7gHrCjsEsRimDJ3oYBiV24wA8FuV&lt;/code&gt; to the same address. While
this seems pointless at first, one explain could be that there existed an
alternative, (pre-) signed transaction spending this UTXO. To invalidate this transaction,
the UTXO was spent. The transactions
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;03e43f5e2877a48c253cd9e05be63805f2717c47bf1bc7c1b210e43309e6899c&lt;/span&gt;
&lt;a href="https://mempool.space/tx/03e43f5e2877a48c253cd9e05be63805f2717c47bf1bc7c1b210e43309e6899c"&gt;03e43f5e28&lt;/a&gt;
and
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;68e129eb0a00cf8d9717e33ba213c9a9849989b9ccfe57b6ea0275c38d2f8cf0&lt;/span&gt;
&lt;a href="https://mempool.space/tx/68e129eb0a00cf8d9717e33ba213c9a9849989b9ccfe57b6ea0275c38d2f8cf0"&gt;68e129eb0a&lt;/a&gt;
are similar occurrences.&lt;/p&gt;
&lt;p&gt;F2Pool also transferred it&amp;rsquo;s &amp;ldquo;uncommon sats&amp;rdquo;&lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; to another address in the‎38 kvB transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;9f62795a4766409d7e32e2ca6a9e3565284cdbaa31a25687249e40cd80671049&lt;/span&gt;
&lt;a href="https://mempool.space/tx/9f62795a4766409d7e32e2ca6a9e3565284cdbaa31a25687249e40cd80671049"&gt;9f62795a47&lt;/a&gt;
.
This transaction has 200 inputs and 200 outputs worth 546 sat each. &lt;em&gt;F2Pool&lt;/em&gt; &lt;a href="https://www.f2pool.com/user/auction/item-list"&gt;sells&lt;/a&gt;
&amp;ldquo;uncommon sats&amp;rdquo;&lt;sup id="fnref2:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, starting at 450 USD for a single 546 sat UTXO&lt;sup id="fnref1:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="dust"&gt;&amp;ldquo;dust&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Bitcoin Core treats low value outputs as non-standard or &amp;ldquo;dust&amp;rdquo;, when the output value is lower than
the cost of creating and spending it. Dust UTXOs aren&amp;rsquo;t economical to spend and pollutes the UTXO set.
&lt;em&gt;F2Pool&lt;/em&gt; is the mining pool that frequently mines transactions, creating &amp;ldquo;dust&amp;rdquo; outputs.&lt;/p&gt;
&lt;p&gt;In the &lt;a href="https://docs.stacks.co/docs/staks-academy/mining#probability-to-mine-next-block"&gt;Stacks 2.0 mining protocol&lt;/a&gt;, stacks-miners try to get their commit transaction into the next
block by paying a high fee. In their commit transaction, they include, as a proof-of-transfer, two
outputs to two other stack miners. The higher the (bitcoin) amount of these outputs, compared to the
other stack-miners competing for the same block, the higher the probability to mine the Stacks block
and be rewarded. There is no minimal proof-of-transfer amount besides the Bitcoin dust limit.
&lt;em&gt;F2Pool&lt;/em&gt; is using their position as a mining pool to their advantage&lt;sup id="fnref2:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;. They filter all
stacks block commits broadcast by other stacks-miners. This allows them to include only their own
commit transaction in their block. In this zero-fee transaction, they pay dust outputs to two other
stack-miners. This allows them to have a 100% probability of being rewarded while only having to spend
&lt;a href="https://twitter.com/blockworksres/status/1659228830350069761"&gt;a few sats&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F2Pool&lt;/em&gt; creates other dust outputs too. In the zero-fee transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;638d8cf6da03eef9ccfdd6505782be98bb7c3b909dc650eef32a910dd0d74c2f&lt;/span&gt;
&lt;a href="https://mempool.space/tx/638d8cf6da03eef9ccfdd6505782be98bb7c3b909dc650eef32a910dd0d74c2f"&gt;638d8cf6da&lt;/a&gt;
,
&lt;em&gt;F2Pool&lt;/em&gt; splits their &amp;ldquo;uncommon sats&amp;rdquo;&lt;sup id="fnref3:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; from some of their coinbase payout outputs into separate UTXOs.
While these separate UTXOs all have a value of 546 sat, there is one UTXO created with a value
of 330 sat, which makes it a dust output. On the output, according to the ordinal theory, a &lt;a href="https://ordinals.com/inscription/a79af50675535850068fd7ab5dcf38c9e886811b3edf9ad84e30e7177b99288ci0"&gt;portrait
of a frog in a suit&lt;/a&gt; is inscribed. This inscription was inscribed about 40k blocks before F2Pool
split these out. The inscription somehow made its way to one of F2Pool&amp;rsquo;s addresses.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F2Pool&lt;/em&gt; also mined the one-input two-output transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;d82e322d9dc3ccd7b69450cc29c42b9da2576aa4184e4d4620f45bb9e269ff27&lt;/span&gt;
&lt;a href="https://mempool.space/tx/d82e322d9dc3ccd7b69450cc29c42b9da2576aa4184e4d4620f45bb9e269ff27"&gt;d82e322d9d&lt;/a&gt;
,
which creates a 250 sat dust output. It&amp;rsquo;s unclear why this transaction was included by &lt;em&gt;F2Pool&lt;/em&gt;.
The transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;38086f6079c9eeb1e1a637600645e99982281f5f8ee23dd9680d879b9e7da204&lt;/span&gt;
&lt;a href="https://mempool.space/tx/38086f6079c9eeb1e1a637600645e99982281f5f8ee23dd9680d879b9e7da204"&gt;38086f6079&lt;/a&gt;
creates two dust outputs and was sent to &lt;em&gt;F2Pool&lt;/em&gt; out-of-band. The story behind this transaction can
be found on &lt;a href="https://twitter.com/francispouliot_/status/1743704660123308387"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F2Pool&lt;/em&gt; mined the one-input and one-output transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;2814d0a3c9e6dd5b88911a6280fc3899391f5c47072eb11593af2838160fad2f&lt;/span&gt;
&lt;a href="https://mempool.space/tx/2814d0a3c9e6dd5b88911a6280fc3899391f5c47072eb11593af2838160fad2f"&gt;2814d0a3c9&lt;/a&gt;
,
paying a fee of 10k sat. The output has a value of zero, which is below the dust limit.
This zero-value output is spent in transaction
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;c1e0db6368a43f5589352ed44aa1ff9af33410e4a9fd9be0f6ac42d9e4117151&lt;/span&gt;
&lt;a href="https://mempool.space/tx/c1e0db6368a43f5589352ed44aa1ff9af33410e4a9fd9be0f6ac42d9e4117151"&gt;c1e0db6368&lt;/a&gt;
,
which inscribes a text inscription &lt;code&gt;you will use soma and you will like it&lt;/code&gt; and creates another zero-output.
An inscription on a zero-value output &lt;a href="https://github.com/ordinals/ord/issues/2062"&gt;broke&lt;/a&gt; the ordinals index. These transactions
were created by &lt;a href="https://github.com/supertestnet"&gt;supertestnet&lt;/a&gt;, who later released his code in a tool called &lt;a href="https://github.com/supertestnet/breaker-of-jpegs"&gt;breaker-of-jpegs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are plenty of other cases where &lt;em&gt;F2Pool&lt;/em&gt; included transactions that create outputs below
the dust limit. This could indicate that they use a lower dust limit than what&amp;rsquo;s set by default.&lt;/p&gt;
&lt;h2 id="too-long-mempool-chain"&gt;&amp;ldquo;too-long-mempool-chain&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;By default, Bitcoin Core limits the number of in-mempool descendants a transaction can have
to less than 25. An exception is the &lt;a href="https://bitcoinops.org/en/topics/cpfp-carve-out/"&gt;CPFP carve out&lt;/a&gt; rule, which allows a single,
small child of the top most ancestor transaction. When these limits are exceeded, transactions
are rejected with the &amp;ldquo;too-long-mempool-chain&amp;rdquo; reason. These transactions are usually standard
on their own but aren&amp;rsquo;t accepted into the mempool at the same time.&lt;/p&gt;
&lt;p&gt;In block 788839, ViaBTC included multiple ancestry sets with more than 200 transactions in total.
These would have been rejected from a default mempool. These sets include the transactions
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;6cd3e204ae299952e30acf4a7c5c337a6a0310b5a58e081199b2d17c7aec504e&lt;/span&gt;
&lt;a href="https://mempool.space/tx/6cd3e204ae299952e30acf4a7c5c337a6a0310b5a58e081199b2d17c7aec504e"&gt;6cd3e204ae&lt;/a&gt;
and &lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;174f1695d537fd21455b3a195a3dac6c430c0060b986ecc8bea5fa8c6d32ef09&lt;/span&gt;
&lt;a href="https://mempool.space/tx/174f1695d537fd21455b3a195a3dac6c430c0060b986ecc8bea5fa8c6d32ef09"&gt;174f1695d5&lt;/a&gt;
.
Both ancestry sets are related to minting BRC-20 tokens.&lt;/p&gt;
&lt;p&gt;In block 789149, &lt;em&gt;AntPool&lt;/em&gt; mined an ancestor transaction &lt;em&gt;A&lt;/em&gt;
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;d4431ed437bce8de22c1c134c6cd9427f5eb08e7b401d7152be936fed58848d0&lt;/span&gt;
&lt;a href="https://mempool.space/tx/d4431ed437bce8de22c1c134c6cd9427f5eb08e7b401d7152be936fed58848d0"&gt;d4431ed437&lt;/a&gt;
with 28 descendants as part of an BRC-20 token mint. With default Bitcoin Core rules,
only 24 children would have been allowed.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/09-non-standard-tx/antpool-brc20-mint-30-tx.png'
alt='&amp;#39;A&amp;#39; is an ancestor transaction with 28 descendants. The arrow direction shows a &amp;#39;is-parent-of&amp;#39; relationship between the transactions.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&amp;lsquo;A&amp;rsquo; is an ancestor transaction with 28 descendants. The arrow direction shows a &amp;lsquo;is-parent-of&amp;rsquo; relationship between the transactions.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In block 802625, &lt;em&gt;F2Pool&lt;/em&gt; mined an ancestor &lt;em&gt;A&lt;/em&gt;
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;1a4fa7cadc072f1937da9857531950069776e2742c7b15b04f3ef9292a46d50b&lt;/span&gt;
&lt;a href="https://mempool.space/tx/1a4fa7cadc072f1937da9857531950069776e2742c7b15b04f3ef9292a46d50b"&gt;1a4fa7cadc&lt;/a&gt;
with two descendants. The children are rejected from a default mempool with the reason &amp;ldquo;too-long-mempool-chain&amp;rdquo;.
Here, the ancestry set is clearly under the limit of 25 transactions. However, the ancestor
&lt;em&gt;A&lt;/em&gt; has a size of 52 kvB, child &lt;em&gt;1&lt;/em&gt; a size of 60 kvB, and &lt;em&gt;2&lt;/em&gt; a size of‎70 kvB. The default
Bitcoin Core configuration only allows an ancestry set to have a maximum size of 101 kvB
(&lt;code&gt;DEFAULT_ANCESTOR_SIZE_LIMIT_KVB&lt;/code&gt;). Transaction &lt;em&gt;A&lt;/em&gt; and child &lt;em&gt;1&lt;/em&gt; together already exceed
this limit. These transactions seem related to the &lt;em&gt;KuCoin&lt;/em&gt; exchange&lt;sup id="fnref2:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/09-non-standard-tx/f2pool-to-large-chain.png'
alt='&amp;#39;A&amp;#39; is an ancestor transaction with two descendants. The arrow direction shows a &amp;#39;is-parent-of&amp;#39; relationship between the transactions.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&amp;lsquo;A&amp;rsquo; is an ancestor transaction with two descendants. The arrow direction shows a &amp;lsquo;is-parent-of&amp;rsquo; relationship between the transactions.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As mentioned in the methodology limitations, occurrences of CPFP carve out are
incorrectly rejected with &amp;ldquo;too-long-mempool-chain&amp;rdquo;. The ordering in the block
is different from the valid mempool submission order. An example is
&lt;span style="color: transparent;position: absolute;overflow: hidden;z-index: -100;"&gt;0b9900440d01600e0589a855fb9d9393860b65a3e0f76a8fc8915385d43ae3e9&lt;/span&gt;
&lt;a href="https://mempool.space/tx/0b9900440d01600e0589a855fb9d9393860b65a3e0f76a8fc8915385d43ae3e9"&gt;0b9900440d&lt;/a&gt;
mined by &lt;em&gt;Binance Pool&lt;/em&gt;. This ancestor transaction &lt;em&gt;A&lt;/em&gt; has a child transaction &lt;em&gt;1&lt;/em&gt;. Transaction &lt;em&gt;1&lt;/em&gt; has
transaction &lt;em&gt;2&lt;/em&gt; through &lt;em&gt;24&lt;/em&gt; as children. This is the maximum number of allowed children.
Transaction &lt;em&gt;25&lt;/em&gt; is a direct child of the ancestor &lt;em&gt;A&lt;/em&gt; and has a size smaller than
1000 vB, which makes it a valid CPFP carve out. Future work could closer analyze how the
CPFP carve out rule is being used on the network.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/09-non-standard-tx/cpfp-carve-out-example.png'
alt='&amp;#39;A&amp;#39; is an ancestor transaction with 25 descendants. Transaction &amp;#39;25&amp;#39; is an example of a CPFP carve out. Here, arrow direction shows a &amp;#39;is-parent-of&amp;#39; relationship between the transactions.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&amp;lsquo;A&amp;rsquo; is an ancestor transaction with 25 descendants. Transaction &amp;lsquo;25&amp;rsquo; is an example of a CPFP carve out. Here, arrow direction shows a &amp;lsquo;is-parent-of&amp;rsquo; relationship between the transactions.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Non-standard transactions are frequently included by mining pools. While some pools occasionally
help community members by, for example, mining UTXOs stuck on non-standard scripts, other
pools use their position as pool to extract more value from blocks. The &lt;em&gt;F2Pool&lt;/em&gt; mining
pool is clearly the pool having the least strict standardness rules and, from time to time,
they accept to mine transactions that exploit vulnerabilities in Bitcoin related software.
The recent non-standard transactions don&amp;rsquo;t immediately show standardness rules that
need to be changed.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;An alternative to a data node could be a block explorer API serving the full blocks.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://docs.ordinals.com/overview.html"&gt;https://docs.ordinals.com/overview.html&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref3:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;According to OXT.me.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;And it tries to double spent the 43k USDT. &lt;a href="https://omniexplorer.info/search/05abfed93a56683ac909e1e0142c0f90f1180e04ba42b18841abcc7c820809d0"&gt;https://omniexplorer.info/search/05abfed93a56683ac909e1e0142c0f90f1180e04ba42b18841abcc7c820809d0&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin-data/mining-pools/issues/102"&gt;https://github.com/bitcoin-data/mining-pools/issues/102&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;The private key &lt;code&gt;1&lt;/code&gt; corresponds to the address &lt;code&gt;1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH&lt;/code&gt;
and the private key &lt;code&gt;2&lt;/code&gt; to the address &lt;code&gt;1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP&lt;/code&gt;. The particular P2SH address
used here is &lt;code&gt;38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE&lt;/code&gt;. This is a 1-of-2 and a 1-&lt;strong&gt;or&lt;/strong&gt;-2 multisig!&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;This is MEV.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;These are usually too large and listed under the &lt;code&gt;tx-size&lt;/code&gt; section.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>CPU usage of Bitcoin Core peers</title><link>https://b10c.me/projects/023-cpu-usage-of-peers/</link><pubDate>Wed, 29 Nov 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/023-cpu-usage-of-peers/</guid><description>&lt;p&gt;To help improve partition resistance, a medium-term goal is to increase the
number block relay connections a Bitcoin Core node has
(see &lt;a href="https://github.com/bitcoin/bitcoin/issues/28462"&gt;#28462&lt;/a&gt;).
However, how much resources do block-relay connections use? Surely they are
cheaper than full-relay connections? This blog-post focuses on the CPU usage
of Bitcoin Core peers.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve looked into this for &lt;a href="https://delvingbitcoin.org/t/cpu-usage-of-peers/196"&gt;CPU usage of peers -
delvingbitcoin.org&lt;/a&gt; and PR
&lt;a href="https://github.com/bitcoin/bitcoin/pull/28463"&gt;#28463&lt;/a&gt;. This post is
cross-posted from &lt;a href="https://delvingbitcoin.org/t/cpu-usage-of-peers/196/2"&gt;my answer on
delvingbitcoin.org&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="methodology"&gt;Methodology&lt;/h4&gt;
&lt;p&gt;To measure CPU usage of peers in Bitcoin Core, I&amp;rsquo;ve opted to measure the time we
spent in &lt;code&gt;ProcessMessages()&lt;/code&gt; and &lt;code&gt;SendMessages()&lt;/code&gt; per peer. I&amp;rsquo;ve added a
tracepoint to the end of both functions and pass, for example, the peer id, the
connection type, and function duration. I hook into these tracepoints with a
simple &lt;code&gt;bpftrace&lt;/code&gt; script that prints each &lt;code&gt;SendMessages()&lt;/code&gt; and
&lt;code&gt;ProcessMessages()&lt;/code&gt; function call as CSV-formatted row. See this &lt;a href="https://github.com/0xB10C/bitcoin/commit/59c0fe438a3e62d27eda237ae20c062608e047b3"&gt;commit&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="setup"&gt;Setup&lt;/h4&gt;
&lt;p&gt;The underlying node is hosted on a VPS of a large cloud provider and has access
to four AMD EPYC 7R32 cores (the message handling thread of Bitcoin Core is
single-threaded and only uses one of these). The IP address of the node is
well-known in the network. The node is pruned, which means no one was doing an
IBD from the node. Other than pruning and debug logging enabled, the node is
running with default parameters. The timing measurements posted here were all
taken with all inbound slots filled (the per-peer measurements with about
half-full inbound slots were similar). I&amp;rsquo;ve only looked at connections that that
ended up being connected for more than a minute. This means, the data doesn&amp;rsquo;t
cover short-lived feeler connections we made or received, and it doesn&amp;rsquo;t cover
short-lived spy node connections that were evicted after a few seconds.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve repeated these measurements on weekdays and a weekend in early November
2023. The resulting numbers differ slightly. This is likely related to, for
example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;transaction broadcast rate on the network ⇾ with more transactions being
broadcast, we spent more time validating transactions, which is &lt;em&gt;expensive&lt;/em&gt;
(see below)&lt;/li&gt;
&lt;li&gt;number of &lt;code&gt;inbound full-relay&lt;/code&gt; vs &lt;code&gt;inbound block-relay-only&lt;/code&gt; connections ⇾
&lt;code&gt;inbound full-relay&lt;/code&gt; are more expensive than &lt;code&gt;inbound block-relay-only&lt;/code&gt;
connections (see below)&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="total-time-spent-sending-and-processing-messages"&gt;Total time spent sending and processing messages&lt;/h4&gt;
&lt;p&gt;To start, this is the &lt;strong&gt;time spent per second&lt;/strong&gt; in &lt;code&gt;SendMessages()&lt;/code&gt; and
&lt;code&gt;ProcessMessages()&lt;/code&gt; summed up for all peers.&lt;/p&gt;
&lt;p&gt;On November 4th and 5th, the weekend before, it averaged at around 56ms per second.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/023-peer-cpu-usage/037ef74a035b38699c2e39c3c9b9cb981602f8ba.jpeg'
alt='Weekend: Total time spent (by all connections) sending and processing messages per second?'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Weekend: Total time spent (by all connections) sending and processing messages per second?&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On November 7th, a Tuesday, this averaged at about 32ms per second with 125
connections. This is on average about 17ms per second processing messages and
15ms per second sending messages.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/023-peer-cpu-usage/5cfaf11a8d32bbf1cfa2d429a0e000f27129dad7.jpeg'
alt='November 7th: Total time spent (by all connections) sending and processing messages per second?'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;November 7th: Total time spent (by all connections) sending and processing messages per second?&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There were short periods with the total time per second reaching nearly 1000ms
per second. This equals to 100% usage of one CPU core.&lt;/p&gt;
&lt;h4 id="per-peer-time-spent-sending-and-processing-messages"&gt;Per-peer time spent sending and processing messages&lt;/h4&gt;
&lt;p&gt;Looking at individual peers by connection direction and connection type shows
which connections are cheaper and which are more expensive. I assume that an
inbound connection sending me a version message with the &lt;code&gt;f_realy&lt;/code&gt; flag set to
false is an outbound block-relay-only connection by the peer. While
&lt;code&gt;-blocksonly&lt;/code&gt; nodes have the same fingerprint (&lt;a href="https://bitcoin.stackexchange.com/a/114082/63817"&gt;link&lt;/a&gt;), I assume that these are rare and only marginally affect the
numbers.&lt;/p&gt;
&lt;p&gt;November 4th and 5th:
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/023-peer-cpu-usage/41c44898b7f5cc254441e9d74d9c04a8f5336c78.jpeg'
alt='Weekend: On average, how long does a single connection spent in Send- and ProcessMessages() per second by connection and relay type?'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Weekend: On average, how long does a single connection spent in Send- and ProcessMessages() per second by connection and relay type?&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;November 7th:
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/023-peer-cpu-usage/20a18f2fb607df745cd2c794cc64ed8e1a52d102.jpeg'
alt='November 7th: On average, how long does a single connection spent in Send- and ProcessMessages() per second by connection and relay type?'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Weekend: On average, how long does a single connection spent in Send- and ProcessMessages() per second by connection and relay type?&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;connection type&lt;/th&gt;
&lt;th&gt;mean 4th+5th ㅤㅤㅤ&lt;/th&gt;
&lt;th&gt;mean 7th ㅤㅤㅤㅤ&lt;/th&gt;
&lt;th&gt;stdev 4th+5th ㅤㅤㅤ&lt;/th&gt;
&lt;th&gt;stdev 7th&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;outbound full-relay&lt;/td&gt;
&lt;td&gt;661.77µs&lt;/td&gt;
&lt;td&gt;611.63µs&lt;/td&gt;
&lt;td&gt;1378.43µs&lt;/td&gt;
&lt;td&gt;2596.95µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inbound full-relay&lt;/td&gt;
&lt;td&gt;457.81µs&lt;/td&gt;
&lt;td&gt;271.72µs&lt;/td&gt;
&lt;td&gt;880.94µs&lt;/td&gt;
&lt;td&gt;1061.78µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;outbound block-relay-onlyㅤ&lt;/td&gt;
&lt;td&gt;94.62µs&lt;/td&gt;
&lt;td&gt;86.14µs&lt;/td&gt;
&lt;td&gt;24.67µs&lt;/td&gt;
&lt;td&gt;158.18µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inbound block-relay-only&lt;/td&gt;
&lt;td&gt;96.84µs&lt;/td&gt;
&lt;td&gt;84.34µs&lt;/td&gt;
&lt;td&gt;77.94µs&lt;/td&gt;
&lt;td&gt;76.31µs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ㅤ&lt;/p&gt;
&lt;p&gt;The connections spend slightly less time on average on the 7th, but had a higher
standard deviation. Likely related to differences in messages relayed on the
network, however, I haven&amp;rsquo;t looked deeper into it.&lt;/p&gt;
&lt;p&gt;Outbound full-relay connections are the most expensive connections here, taking
more than 600µs per second on average. We currently only ever make 8 of these at
a time. Inbound full-relay connections are cheaper than outbound full-relay
connections, but still expensive compared to block-relay connections. However,
we accept up to 114 inbound full-relay connections (typically about 91 due to
some being block-relay-only, see also &lt;a href="https://github.com/bitcoin/bitcoin/pull/28463#issuecomment-1810591988"&gt;#28463
(comment)&lt;/a&gt;).
Inbound and outbound block-relay-only connections spent just under 100µs sending
and processing messages per second on average. These are the cheapest
connections.&lt;/p&gt;
&lt;h4 id="time-spent-processing-messages-by-relay-type-and-connection-direction"&gt;Time spent processing messages by relay type and connection direction&lt;/h4&gt;
&lt;p&gt;Since &lt;code&gt;ProcessMessages()&lt;/code&gt; only ever processes one message at a time, we can
measure the processing time by received message. &lt;code&gt;SendMessages()&lt;/code&gt; might send
zero, one, or multiple messages when called, which makes the same measurement
harder.&lt;/p&gt;
&lt;p&gt;November 7th:&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/023-peer-cpu-usage/5aa77688d2cfad866e763fe526efb2f568f25d06.png'
alt='November 7th: Boxen plot of time taken to process a received message by relay-type.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;November 7th: Boxen plot of time taken to process a received message by relay-type.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;tx&lt;/code&gt;, &lt;code&gt;addr&lt;/code&gt;, and &lt;code&gt;addrv2&lt;/code&gt; messages are only received and processed by
full-relay peers. Especially &lt;code&gt;tx&lt;/code&gt; messages are expensive with close to 0.5ms in
median. While I received a few &lt;code&gt;inv&lt;/code&gt; and &lt;code&gt;getdata&lt;/code&gt; messages from
block-relay-only peers, the majority stems from full-relay peers. Additionally,
during this time-frame, all &lt;code&gt;cmpctblock&lt;/code&gt; messages were received by full-relay
peers.&lt;/p&gt;
&lt;p&gt;Inbound &lt;code&gt;version&lt;/code&gt; messages take slightly shorter to process for block-relay-only
connections than for full-relay. @amiti suggested this might be related to
initializing the data structures for transaction relay?&lt;/p&gt;
&lt;h4 id="learnings"&gt;Learnings&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;On a modern server CPU core (AMD EPYC 7R32), Bitcoin Core usually spends less
than 100ms (10%) of the time in the (single-thread) message handling thread
with full inbound slots.&lt;/li&gt;
&lt;li&gt;Very roughly, an &lt;code&gt;outbound full-relay&lt;/code&gt; connection (~600µs per second) has
about 6x the &lt;em&gt;CPU usage&lt;/em&gt; of an &lt;code&gt;outbound block-relay-only&lt;/code&gt; connection (~100µs
per second). An &lt;code&gt;inbound full-relay&lt;/code&gt; connection (~300µs per second) has 3x the
&lt;em&gt;CPU usage&lt;/em&gt; of an &lt;code&gt;inbound block-relay-only&lt;/code&gt; connection (~100µs per second).&lt;/li&gt;
&lt;li&gt;As to be expected: Time spent processing and sending messages per second is
lower for &lt;code&gt;block-relay-only&lt;/code&gt; connections compared to &lt;code&gt;full-relay&lt;/code&gt;. The
&lt;code&gt;block-relay-only&lt;/code&gt; connections don&amp;rsquo;t process and send transactions. On the
processing side, transaction relay is more expensive than address relay.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="increasing-number-of-block-relay-only-slots-and-connections"&gt;Increasing number of block-relay-only slots and connections&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin/bitcoin/pull/28463"&gt;#28463&lt;/a&gt; proposes to increase the
number of inbound connection slots for block-relay-only connections. Currently,
a node has about 91 full-relay and 23 block-relay-only inbound connections (80%
and 20% of 114). As currently proposed, the PR increases this to about 113
full-relay and 75 block-relay-only connections (60% and 40% of 189 = 200 - 2 - 1 - 8).&lt;/p&gt;
&lt;p&gt;Assuming 600µs for a &lt;code&gt;outbound full-relay&lt;/code&gt; connection, 300µs for an &lt;code&gt;inbound full-relay&lt;/code&gt; and 100µs for a &lt;code&gt;block-relay&lt;/code&gt; connection, currently we are at 34.6ms
per second and will be at 46.4ms (increase of 34%) with the proposed change.
6.6ms more due to the new full-relay connections and 5.2ms due to the
block-relay-only connections. While spending 46.4ms per second in the message
handling thread is probably fine, a more conservative change might be to leave
the number of full-relay inbound slots largely untouched. Here, RAM and
bandwidth usage should be considered too.&lt;/p&gt;
&lt;p&gt;currently: $$ 8 \times 600µs + 2 \times 100µs + 91 \times 300µs + 23 \times 100µs = 34600µs = 34.6ms $$
as proposed: $$ 8 \times 600µs + 2 \times 100µs + 113 \times 300µs + 75 \times 100µs = 46400µs = 46.4ms $$&lt;/p&gt;
&lt;p&gt;Since later increasing from 2 outbound block-relay connections to 8 as proposed
in &lt;a href="https://github.com/bitcoin/bitcoin/issues/28462"&gt;#28462&lt;/a&gt; is only an increase
of $ 6 \times 100µs $, I don&amp;rsquo;t see a problem with this from the &lt;em&gt;CPU usage&lt;/em&gt; side.&lt;/p&gt;
&lt;h4 id="some-notes"&gt;Some notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;It would also be useful to have these measurements for Erlay. Maybe Erlay
makes transaction relay a lot cheaper (or a lot more expensive?).&lt;/li&gt;
&lt;li&gt;These measurements were made on a server CPU core, where a 34% increase is
only about 11ms per second. However, the numbers might look very different on
a &lt;a href="https://www.cpubenchmark.net/compare/3894vs4297/AMD-EPYC-7R32-vs-BCM2711"&gt;nearly 70% slower&lt;/a&gt;
Raspberry Pi 4 BCM2711 Core (&lt;a href="https://delvingbitcoin.org/uploads/default/original/1X/425ccc585b55907ea9b26b9a3bd1a7f1e796eef3.png"&gt;see this comparison&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Since my node is pruned, this does not include IBD data, which might raise the
average time spent in &lt;code&gt;SendMessages()&lt;/code&gt; (for both full-relay and block-relay
connections).&lt;/li&gt;
&lt;li&gt;Time spent in function isn&amp;rsquo;t a perfect measurement of CPU usage. For example,
when sending requested blocks, a big chunk of the time might be spent waiting
on disk IO.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Thanks to the &lt;a href="https://dci.mit.edu/"&gt;MIT DCI&lt;/a&gt; for sponsoring the node I&amp;rsquo;ve used
to measure this (and five more for related purposes!).&lt;/p&gt;</description></item><item><title>Six OFAC-sanctioned transactions missing</title><link>https://b10c.me/observations/08-missing-sanctioned-transactions/</link><pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/08-missing-sanctioned-transactions/</guid><description>&lt;p&gt;My project, &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt;, aims to detect when Bitcoin mining pools
are not mining transactions they could have been mining. Over the past few weeks,
it detected six missing transactions spending from OFAC-sanctioned addresses.
This post examines whether these transactions were intentionally filtered because
they spent from OFAC-sanctioned addresses or if there are other possible
explanations for these transactions to be missing from blocks. I conclude
that four out of six transactions were likely filtered.&lt;/p&gt;
&lt;p&gt;In September and October 2023, the &lt;a href="https://miningpool.observer/faq#sanctioned-tx-utxos"&gt;RSS feed&lt;/a&gt; of my &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt;
instance reported six blocks missing an OFAC-sanctioned transaction.
One block was mined by the ViaBTC mining pool, another by the Foundry USA pool,
and four by F2Pool. An OFAC-sanctioned transaction is a transaction
spending from or paying to an address sanctioned by the US Department of the
Treasury&amp;rsquo;s Office of Foreign Assets Control. I maintain a &lt;a href="https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses"&gt;tool&lt;/a&gt; to extract
a list of OFAC-sanctioned addresses from the &lt;em&gt;Specially Designated Nationals
(SDN)&lt;/em&gt; list published by the OFAC.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://miningpool.observer/faq#missing-tx-reasons"&gt;Several reasons&lt;/a&gt; could explain why a transaction might be absent from a block.
Generally, transactions do not propagate equally through the network, and there
is no global mempool to pick transactions from. Each node has its own set of
valid transactions. A pool might also prioritize transactions for which it
received an out-of-band payment. However, it might also deprioritize or filter
transactions.&lt;/p&gt;
&lt;p&gt;The goal here is to determine if the mining pool filtered any of these six
OFAC-sanctioned transactions or if there are other possible explanations for
their absence from the block&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Note that mining pools are free to
choose which transactions to include and which to leave out. However, to analyze
Bitcoin&amp;rsquo;s censorship-resistant properties, it&amp;rsquo;s crucial to understand which pools
and how many of them are filtering transactions.&lt;/p&gt;
&lt;p&gt;I conclude that the reports from &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt; indicating sanctioned
transactions missing from blocks by ViaBTC and Foundry are likely false-positives
and not the result of filtering. The transactions missing from F2Pool&amp;rsquo;s blocks are,
however, likely filtered. All missing transactions have been picked up by other
miners &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="block-808660-by-viabtc"&gt;Block 808660 by ViaBTC&lt;/h2&gt;
&lt;p&gt;Block &lt;a href="https://miningpool.observer/template-and-block/000000000000000000017c18a76632d9e39e8c388ee1e4028ec75e50866c79c5"&gt;808660&lt;/a&gt; &lt;code&gt;..866c79c5&lt;/code&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, mined by ViaBTC on September 21, 2023,
did not include transaction &lt;code&gt;262025e7..&lt;/code&gt;&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. This transaction
consolidates 100 inputs into one output. One of these inputs spends an output
paid to &lt;code&gt;1ECeZBxCVJ8Wm2JSN3Cyc6rge2gnvD3W5K&lt;/code&gt;. This address was &lt;a href="https://ofac.treasury.gov/recent-actions/20210921"&gt;added&lt;/a&gt;
to the OFAC&amp;rsquo;s SDN list on September 21st, 2021.&lt;/p&gt;
&lt;p&gt;The transaction has a size of 14.7 kvB and pays a feerate of 25.18 sat/vByte.
The output spent from the sanctioned address is 0.0002 BTC (20k sat) and was
only &lt;a href="https://blockstream.info/tx/262025e73812fc68b6514ea366abf463147176c7867e5853f117aded58c30e0e?output:84"&gt;created a day before&lt;/a&gt;. When ViaBTC mined block 808660, the transaction
had been in my node&amp;rsquo;s mempool for about 75 minutes. It did not have any
dependency on in-mempool transactions.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/viabtc-block-and-template-feerate-distribution.png'
alt='Feerate distribution of the block and the template for block 808660.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Feerate distribution of the block and the template for block 808660. Screenshot from miningpool.observer.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Examining the feerate distribution of block 808660 on &lt;a href="https://miningpool.observer/template-and-block/000000000000000000017c18a76632d9e39e8c388ee1e4028ec75e50866c79c5"&gt;miningpool.observer&lt;/a&gt;,
reveals that ViaBTC occupied about 1 MWU worth of block space, of
a total of 4 MWU, with prioritized transactions. These likely stem from the
&lt;a href="https://www.viabtc.com/tools/txaccelerator"&gt;ViaBTC Bitcoin Transaction Accelerator&lt;/a&gt;. Prioritizing some transactions means
that lower feerate transactions, such as the transaction spending from the
sanctioned address here, don&amp;rsquo;t make it into the block. For this ViaBTC block,
my miningpool-observer instance &lt;a href="https://miningpool.observer/template-and-block/000000000000000000017c18a76632d9e39e8c388ee1e4028ec75e50866c79c5"&gt;lists 24&lt;/a&gt; large consolidation transactions
at the end of the template that didn&amp;rsquo;t make it into this block.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/viabtc-missing-consolidations.png'
alt='List of large, missing consolidation transactions from block 808660'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;List of large, missing consolidation transactions from block 808660&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This leads to the conclusion that ViaBTC did not filter this transaction. It
got displaced by other, prioritized transactions. This is supported by the fact
that, three days later, ViaBTC mined a &lt;a href="https://blockstream.info/tx/cb9f2592f5fbadd4f5764654c08429b2ee6f79df0c63adb33e25f6d6c905c2af?output:84&amp;amp;expand"&gt;transaction&lt;/a&gt; &lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; spending an output
from the same sanctioned address in block 809181.&lt;/p&gt;
&lt;h2 id="block-813231-by-foundry-usa"&gt;Block 813231 by Foundry USA&lt;/h2&gt;
&lt;p&gt;Block &lt;a href="https://miningpool.observer/template-and-block/00000000000000000001740d5fbb8bbc0b93d4bf46ca2011f642e92a0a8528b6"&gt;813231&lt;/a&gt; &lt;code&gt;..0a8528b6&lt;/code&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;, mined by Foundry USA on October 21st,
2023, did not include the transaction &lt;code&gt;c9b57191..&lt;/code&gt;&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;. This transaction
consolidates 150 inputs into one output. One of the inputs spends an output
paid to &lt;code&gt;3PKiHs4GY4rFg8dpppNVPXGPqMX6K2cBML&lt;/code&gt;&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;. This address was
&lt;a href="https://ofac.treasury.gov/recent-actions/20230414"&gt;added&lt;/a&gt; to the OFAC’s SDN list on April 14th, 2023.&lt;/p&gt;
&lt;p&gt;As most of the 150 inputs are 2-of-3 multisig P2SH scripts, the missing
transaction is large at 43842 vByte. It pays a feerate of 5.09 sat/vByte
and had no dependencies on in-mempool transactions. This feerate was enough
to place it at position 161 out of 2215 transactions in the template
constructed by my Bitcoin Core node. However, this transaction, along with 18
other transactions, had only been in my mempool for around 30 seconds when I
learned about block 812331 by Foundry USA. This makes it likely that Foundry
didn&amp;rsquo;t have a chance to include the transaction in their block because they
didn&amp;rsquo;t know about it yet.&lt;/p&gt;
&lt;p&gt;It might take a few seconds for the transaction to propagate. Additionally, most
pools only push new block templates to miners every 30 seconds, which then
take a while to switch to the new job. Furthermore, the miningpool-observer
tool requests new block templates every few seconds and does best-effort
matching based on the minimum difference in missing and extra transactions
(see &lt;a href="https://miningpool.observer/faq#general-template"&gt;Methodology&lt;/a&gt; in the FAQ). This makes false-positives for young transactions,
probably up to around 60 seconds, possible.&lt;/p&gt;
&lt;p&gt;The mempool.space block explorer also keeps track of differences between block
templates and the final blocks broadcast by miners. They show that &lt;code&gt;c9b57191..&lt;/code&gt; is
included in their template but missing from the actual block. The transaction is
tagged &amp;ldquo;recently broadcasted&amp;rdquo; by them too.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/foundry-mempool-space-screenshot.png'
alt='Transaction missing from block 813231 by Foundry considered as _recently broadcast_ by mempool.space.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Transaction missing from block 813231 by Foundry considered as &amp;lsquo;recently broadcast&amp;rsquo; by mempool.space.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This leads to the conclusion that Foundry USA did not filter this transaction.
The transaction was broadcast too late to be included in the mining job that
ended up finding block 813231. In addition, Foundry USA also mined the next
block at height 813232 and included the sanctioned transaction there.&lt;/p&gt;
&lt;h2 id="blocks-810727-811791-811920-and-813357-by-f2pool"&gt;Blocks 810727, 811791, 811920, and 813357 by F2Pool&lt;/h2&gt;
&lt;p&gt;F2Pool mined the block &lt;a href="https://miningpool.observer/template-and-block/0000000000000000000350ae5ee08a4415146612af59a20021efeaf2ccda1498"&gt;810727&lt;/a&gt; &lt;code&gt;..ccda1498&lt;/code&gt; on October 5th, 2023, the blocks
&lt;a href="https://miningpool.observer/template-and-block/00000000000000000001631243b00b6c1019c0d833b6738e0c591dacaf4453d6"&gt;811791&lt;/a&gt; &lt;code&gt;..af4453d6&lt;/code&gt; and &lt;a href="https://miningpool.observer/template-and-block/00000000000000000002efd0fc8801b149f505b125308a35c584ed2600badf62"&gt;811920&lt;/a&gt; &lt;code&gt;..00badf62&lt;/code&gt; on October 12th, and the block
&lt;a href="https://miningpool.observer/template-and-block/00000000000000000000519c33dcdf5ca386524b2cbacb561f767e9663ac1669"&gt;813357&lt;/a&gt; &lt;code&gt;..63ac1669&lt;/code&gt; on October 22nd &lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;. Each block is missing one
sanctioned transaction&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;. Each of these transactions consolidates 150
2-of-3 multisig inputs into one output. For each transaction, one of the inputs
spends an output paid to &lt;code&gt;3PKiHs4GY4rFg8dpppNVPXGPqMX6K2cBML&lt;/code&gt;. This is the same
consolidation pattern and address as discussed in the previous section. None of
the missing transactions had a dependency on in-mempool transactions.&lt;/p&gt;
&lt;h3 id="block-810727"&gt;Block 810727&lt;/h3&gt;
&lt;p&gt;In block &lt;a href="https://miningpool.observer/template-and-block/0000000000000000000350ae5ee08a4415146612af59a20021efeaf2ccda1498"&gt;810727&lt;/a&gt;, F2Pool did not include the transaction &lt;code&gt;c6a66836..&lt;/code&gt;&lt;sup id="fnref1:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;,
which spends a sanctioned output. Due to the 150 2-of-3 multisig inputs, the
transaction is rather large at 44017 vBytes. It pays a fee of 446260 sat and
had been in my node&amp;rsquo;s mempool for nearly 4 hours when F2Pool mined block 810727.
Instead of &lt;code&gt;c6a66836..&lt;/code&gt;, F2Pool chose to include the transaction &lt;code&gt;907e1f45..&lt;/code&gt;&lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt;.
This transaction is also a consolidation transaction with 150 inputs and
one output, but does not spend from a sanctioned output. It pays the
same fee of 446260 sat but happens to be 3 vByte larger&lt;sup id="fnref:12"&gt;&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref"&gt;12&lt;/a&gt;&lt;/sup&gt;
at 44020 vByte. This means the missing transaction &lt;code&gt;c6a66836..&lt;/code&gt; has a
slightly higher feerate than &lt;code&gt;907e1f45..&lt;/code&gt;. When strictly sorting by feerate,
the missing transaction should have been included. However, in practice, it&amp;rsquo;s
unlikely that 3 vByte of additional block space will make a difference in the
total fees in the block.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-comparison-transactions.png'
alt='Comparison of the sanctioned transaction missing from F2Pool&amp;#39;s block 810727 to the extra transaction included. The extra transaction is 3 vBytes larger.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Comparison of the sanctioned transaction missing from F2Pool&amp;rsquo;s block 810727 to the extra transaction included. The extra transaction is 3 vBytes larger.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="block-811791"&gt;Block 811791&lt;/h3&gt;
&lt;p&gt;The sanctioned transaction &lt;code&gt;aa001ce6..&lt;/code&gt;&lt;sup id="fnref2:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt; is missing from F2Pools
block &lt;a href="https://miningpool.observer/template-and-block/00000000000000000001631243b00b6c1019c0d833b6738e0c591dacaf4453d6"&gt;811791&lt;/a&gt;. Similar to the previous consolidation transactions, this
transaction has a size of 42459 vBytes (169836 WU). With a fee of 446260 sat
it pays a feerate of 10.5 sat/vByte. When block 811791 arrived at the
miningpool-observer node, the transaction had been in its mempool for four
minutes.&lt;/p&gt;
&lt;p&gt;In this block, it&amp;rsquo;s notable that five transactions with OP_RETURN Stacks
block commitments are missing. F2Pool has, however, inserted their own Stacks
block commitment. This happens regularly and has been &lt;a href="https://twitter.com/SomsenRuben/status/1659486223025557504"&gt;reported before&lt;/a&gt;.
Additionally, F2Pool is including two large, zero-fee transactions in their
block. One consolidates previous F2Pool coinbase outputs, and the other is
a payout transaction to miners. This is common behavior for blocks mined by F2Pool.&lt;/p&gt;
&lt;p&gt;While these extra transactions take up more than 400 kWU of block space, there
would still have been enough space to include transaction &lt;code&gt;aa001ce6..&lt;/code&gt;. The block
includes 2.86 MWU of transactions below &lt;code&gt;aa001ce6..&lt;/code&gt;&amp;rsquo;s feerate of 10.5
sat/vByte. This transaction, with about 170 kWU, would have fit into the block.
On &lt;a href="https://mempool.space/block/00000000000000000001631243b00b6c1019c0d833b6738e0c591dacaf4453d6"&gt;mempool.space&lt;/a&gt;, this transaction is marked as &amp;ldquo;removed&amp;rdquo;,
which negatively affects their block health metric.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-feerate-distribution-block-811791.png'
alt='Feerate distribution by transaction packages in block 811791 including markers for the feerate and weight of the missing transaction.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Feerate distribution by transaction packages in block 811791 including markers for the feerate and weight of the missing transaction.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="block-811920"&gt;Block 811920&lt;/h3&gt;
&lt;p&gt;In block &lt;a href="https://miningpool.observer/template-and-block/00000000000000000002efd0fc8801b149f505b125308a35c584ed2600badf62"&gt;811920&lt;/a&gt;, F2Pool did not include transaction &lt;code&gt;1cb3d6bc..&lt;/code&gt;&lt;sup id="fnref3:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;,
which spends a sanctioned output. This transaction is a large consolidation
transaction as well. It has a size of 43630 vBytes (169836 WU) and, with a fee
of 44660 sat it pays a feerate of 10.23 sat/vByte. When block 811920
arrived at the miningpool-observer node, the transaction had been in the node&amp;rsquo;s
mempool for close to 2 minutes.&lt;/p&gt;
&lt;p&gt;In block 811920, 1.44 MWU of transactions pay less than 10.23 sat/vByte.
The 170 kWU transaction &lt;code&gt;1cb3d6bc..&lt;/code&gt; would have fit into the block. As
the transaction was in my node&amp;rsquo;s mempool for only close to two minutes, there
is a possibility that it didn&amp;rsquo;t yet propagate to F2Pool when they were
building their block template. The transaction is shown as &amp;ldquo;recently broadcast&amp;rdquo;
on &lt;a href="https://mempool.space/block/00000000000000000002efd0fc8801b149f505b125308a35c584ed2600badf62"&gt;mempool.space&lt;/a&gt;, too. Usually, mining pools
try to have good connectivity to the Bitcoin network. There is a high likelihood
that the transaction was in F2Pool&amp;rsquo;s mempool if it was in mempool.space&amp;rsquo;s and
miningpool.observer&amp;rsquo;s mempool.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-feerate-distribution-block-811920.png'
alt='Feerate distribution by transaction packages in block 811920 including markers for the feerate and weight of the missing transaction.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Feerate distribution by transaction packages in block 811920 including markers for the feerate and weight of the missing transaction.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="block-813357"&gt;Block 813357&lt;/h3&gt;
&lt;p&gt;In F2Pool&amp;rsquo;s block &lt;a href="https://miningpool.observer/template-and-block/00000000000000000000519c33dcdf5ca386524b2cbacb561f767e9663ac1669"&gt;813357&lt;/a&gt;, the transaction &lt;code&gt;e49cdb60..&lt;/code&gt;&lt;sup id="fnref4:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;,
which spends a sanctioned output, is missing. This consolidation transaction
has a size of 43053 vBytes (172209 WU). With a fee of 178504 sat it pays
a feerate of 4.15 sat/vByte. When block 813357 arrived at the
miningpool-observer node, the transaction had been in the node&amp;rsquo;s mempool for
more than 25 minutes.&lt;/p&gt;
&lt;p&gt;There are 684 kWU of transactions paying below 4.15 sat/vByte in block 813357.
The 172 kWU transaction &lt;code&gt;e49cdb60..&lt;/code&gt; would have fit into the block. As the
transaction was in my node&amp;rsquo;s mempool for more than 25 minutes, it&amp;rsquo;s unlikely that
the transaction didn&amp;rsquo;t propagate to one of F2Pools nodes yet. The transaction
was included in &lt;a href="https://mempool.space/block/00000000000000000000519c33dcdf5ca386524b2cbacb561f767e9663ac1669"&gt;mempool-space&lt;/a&gt;&amp;rsquo;s template for block 813357
too.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-feerate-distribution-block-813357.png'
alt='Feerate distribution by transaction packages in block 813357 including markers for the feerate and weight of the missing transaction.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Feerate distribution by transaction packages in block 813357 including markers for the feerate and weight of the missing transaction.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="conclusion-on-f2pools-blocks"&gt;Conclusion on F2Pools blocks&lt;/h3&gt;
&lt;p&gt;The sanctioned transaction missing from block 810727 had a slightly higher
feerate because it&amp;rsquo;s 3 vByte smaller than the included transaction.
While, in this case, these 3 vBytes of extra block space wouldn&amp;rsquo;t have made a
difference in total fees, the Bitcoin Core block templating algorithm would
have chosen the transaction with the higher feerate. The large extra
transactions included in block 811791 wouldn&amp;rsquo;t have had an effect on the
sanctioned transaction missing from block 811791. It has likely been filtered
from the block. The block audit on mempool.space agrees with this.
There is a chance that F2Pool didn&amp;rsquo;t yet know about the missing sanctioned
transaction from block 811920. However, 2 minutes should be enough for a large
pool to receive a transaction. Especially since mempool.space and miningpool.observer
knew about this transaction. It&amp;rsquo;s likely that this sanctioned transaction
is missing because F2Pool filtered it. Similar to the missing transaction
from block 811791, the missing transaction from block 813357 has likely been
filtered by F2Pool.&lt;/p&gt;
&lt;p&gt;These four missing sanctioned transactions lead to the conclusion that F2Pool
is currently filtering transactions. Since we&amp;rsquo;ve only seen transactions spending
from the single OFAC-sanctioned address &lt;code&gt;3PKiHs4GY4rFg8dpppNVPXGPqMX6K2cBML&lt;/code&gt;
missing, we can&amp;rsquo;t tell if F2Pool is filtering this single address or all OFAC-
sanctioned addresses.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This post discusses six Bitcoin transactions spending from OFAC-sanctioned
addresses that the &lt;a href="https://miningpool.observer"&gt;miningpool-observer&lt;/a&gt; tool detected as missing from blocks.
The two transactions missing from the ViaBTC and Foundry USA pool blocks are
false-positives and not filtered. The four OFAC-sanctioned transactions missing
from the F2Pool blocks are likely filtered. This raises the question of why
F2Pool, a pool with origins in Asia, is the first pool to filter transactions
based on US OFAC sanctions.&lt;/p&gt;
&lt;p&gt;The Bitcoin network, however, continues to work as normal. A single pool filtering
transactions does not affect the censorship resistance of the Bitcoin network as a
whole. Further monitoring of the transaction selection of mining pools allows
identifying when more pools start to filter transactions based on, for example,
OFAC sanctions. It also allows miners pointing their hashrate to these pools to
make an informed decision on switching to a different pool if they don&amp;rsquo;t agree
with a pool&amp;rsquo;s (unannounced) filtering policies.&lt;/p&gt;
&lt;h3 id="update-2023-11-23"&gt;Update 2023-11-23&lt;/h3&gt;
&lt;p&gt;After publishing this blog post, F2Pool Co-Founder &lt;a href="https://twitter.com/satofishi/"&gt;@satofishi&lt;/a&gt; tweeted that
F2Pool was indeed filtering these transactions. He followed up with a statement
that F2Pool would disable the filtering for now. Both tweets have since been
deleted. The second tweet is &lt;a href="https://archive.ph/UFP8l"&gt;archived&lt;/a&gt; on archive.ph. After deleting the tweet
that the transaction filtering patch is disabled for now, it is unclear if
F2Pool is still filtering transactions. &lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-satofishi-censoring-1.jpg'
alt='@satofishi justifying F2Pool filtering transactions. Note: No Bitcoin addresses by Vladimir Putin and Xi Jinping are sanctioned by OFAC.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;@satofishi justifying F2Pool filtering transactions. This tweet was deleted. Note: No Bitcoin addresses by Vladimir Putin and Xi Jinping are sanctioned by OFAC.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/08-missing-sanctioned-transactions/f2pool-satofishi-censoring-2.jpg'
alt='satofishi claiming he will disable the F2Pool transaction filtering patch for now. This tweet was deleted.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;satofishi claiming he will disable the F2Pool transaction filtering patch for now. This tweet was deleted.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;As all blocks with missing transactions didn&amp;rsquo;t come close to the
sigop limit of 80.000, these aren&amp;rsquo;t discussed here.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Ammended on 2023-11-23 after publishing.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;ViaBTC block 808660:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;000000000000000000017c18a76632d9e39e8c388ee1e4028ec75e50866c79c5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Transaction missing from block 808660:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;262025e73812fc68b6514ea366abf463147176c7867e5853f117aded58c30e0e&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;The transaction &lt;code&gt;cb9f2592..&lt;/code&gt; mined in block 809181 by ViaBTC is an
&lt;a href="https://omniexplorer.info/search/cb9f2592f5fbadd4f5764654c08429b2ee6f79df0c63adb33e25f6d6c905c2af"&gt;Omnilayer transaction&lt;/a&gt;
transferring 1528 USDT deposited to this address in September, 2020. The
output to the sanctioned address &lt;code&gt;1ECeZBxCVJ8Wm2JSN3Cyc6rge2gnvD3W5K&lt;/code&gt; for
this transaction was created similarly to the transaction &lt;code&gt;262025e7..&lt;/code&gt;
missing from block 808660 in &lt;a href="https://blockstream.info/tx/d11019a2a0308e01183cfd8db42a22d9d79c180575a4b0b42386d2621b4e3169?output:84"&gt;&lt;code&gt;d11019a2..&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I checked a few of these addresses and they all contained USDT balances on
OmniLayer which were swept in these transactions. While this is a guess,
it seems someone wanted to sweep the remaining USDT on a bunch of addresses,
sent 20k sats to each of them, and messed up the sweep by consolidating the
newly created outputs again in &lt;code&gt;262025e7..&lt;/code&gt;. They then retried with
&lt;code&gt;d11019a2..&lt;/code&gt; and successfully swept it with &lt;code&gt;cb9f2592..&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If that&amp;rsquo;s the case, then OFAC is likely missing a bunch of addresses by the
same entity on their list.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;Block 813231 mined by Foundry has the header hash:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;00000000000000000001740d5fbb8bbc0b93d4bf46ca2011f642e92a0a8528b6&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;Missing sanctioned transaction from block 813231 has the txid:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c9b5719131bfeac6378749243731c5e70f1ce56deabb7006a2b6539710866420&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;Based on &lt;a href="https://oxt.me/transaction/c9b5719131bfeac6378749243731c5e70f1ce56deabb7006a2b6539710866420"&gt;OXT.me data&lt;/a&gt;, this address belongs to an OKEX wallet.
The consolidation transaction &lt;code&gt;c9b57191..&lt;/code&gt; is OKEX consolidating deposits.
The output being consolidated is, &lt;a href="https://oxt.me/transaction/11057ba68132982d713a02ff614b1415c9e171fea4d0b84a5ab9387225414e56"&gt;according to OXT.me&lt;/a&gt;, a Hydra darknet
market payout. Additional information can be found &lt;a href="https://twitter.com/chainalysis/status/1646944900665974787"&gt;here&lt;/a&gt;.&lt;/p&gt;
&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;Block hashes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;810727: &lt;code&gt;0000000000000000000350ae5ee08a4415146612af59a20021efeaf2ccda1498&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;811791: &lt;code&gt;00000000000000000001631243b00b6c1019c0d833b6738e0c591dacaf4453d6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;811920: &lt;code&gt;00000000000000000002efd0fc8801b149f505b125308a35c584ed2600badf62&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;813357: &lt;code&gt;00000000000000000000519c33dcdf5ca386524b2cbacb561f767e9663ac1669&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;Missing, sanctioned transactions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;810727: &lt;code&gt;c6a668364f19df0f2977f8ad7d0a3a73c5e32b55b6a7c650cafa37a5ab4b19f2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;811791: &lt;code&gt;aa001ce6e262b8b9042645ecdec9c84e9e2ad06f56dff6dd5ae42005fdea8da9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;811920: &lt;code&gt;1cb3d6bcc650c2891b68e7b205d601bcf5158e30e1926d0fd0c8385cb456b37b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;813357: &lt;code&gt;e49cdb6075c49b8fc37b3e922038e2a3205d75a9a1fb4b69f3568707594c2d3e&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref3:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref4:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:11"&gt;
&lt;p&gt;Slightly larger and thus lower feerate transaction F2Pool picked for block 810727:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;907e1f45334652dd344cf846639f3f9a2ee11b5489e2ffc2660ea543881b1bce&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id="fn:12"&gt;
&lt;p&gt;Likely because it has fewer low-r nonces in the signatures which makes
the signatures larger.&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>FFM BTC Meetup: New features in Bitcoin Core v26.0</title><link>https://b10c.me/talks/020-btcffm-bitcoin-core-26-news/</link><pubDate>Thu, 19 Oct 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/020-btcffm-bitcoin-core-26-news/</guid><description>&lt;p&gt;The upcoming Bitcoin Core v26.0 release will include new experimental features
like &lt;a href="https://github.com/jamesob/assumeutxo-docs/tree/master/proposal"&gt;AssumeUTXO&lt;/a&gt;
and &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki"&gt;P2P transport v2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the &lt;a href="https://t.me/btcffm"&gt;Frankfurt Bitcoin Meetup (German)&lt;/a&gt;, I explained the Bitcoin Core development process,
that for this release cycle contributors focused their review on specific projects,
and when to expect the new release (after testing, where everyone can participate!).
Then dove into the problems that AssumeUTXO and P2P transport provide solutions for
and what these solutions are. This was followed by a small discussion.&lt;/p&gt;
&lt;p&gt;No slides.&lt;/p&gt;</description></item><item><title>Spiral Work-Log Q3 2023</title><link>https://b10c.me/funding/2023-sprial-report-q3/</link><pubDate>Mon, 16 Oct 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2023-sprial-report-q3/</guid><description>&lt;p&gt;This is a copy of the Q3 2023 work-log I sent to &lt;a href="https://spiral.xyz"&gt;Spiral&lt;/a&gt; for &lt;a href="https://b10c.me/funding/2023-sprial-grant/"&gt;my grant&lt;/a&gt;.&lt;/p&gt;
&lt;span&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: Some information that is not (or not yet) meant to be published may have been redacted.&lt;/span&gt;
&lt;hr&gt;
&lt;p&gt;In Q3, I finished my GitHub repository backup and mirroring project, helped with the fork-observer integration in Warnet, continued my work on the MIT DCI peer-observer setup, attended CoreDev and the BTC Azores unconference, implemented a hidden getrawaddrman RPC in Bitcoin Core, started working addrman-observer, wrote about the invalid Marathon block at height 809478, and worked on a NixOS modules workshop for bitcoin++ in Berlin.&lt;/p&gt;
&lt;p&gt;In chronological order:&lt;/p&gt;
&lt;h3 id="github-metadata-backup-and-mirror"&gt;&lt;a href="https://b10c.me/projects/021-github-backups-mirror/"&gt;GitHub Metadata Backup and Mirror&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This year, the Bitcoin Core project will have its 13th anniversary being hosted on GitHub. 13 years of issues and pull requests with critical design decisions and nuanced discussions hosted with a US-based company known for shutting down open-source software repositories when needing to follow DMCA and OFAC requests. While the medium-to-long term plan is to move off of GitHub, I’ve written &lt;a href="https://github.com/0xb10c/github-metadata-backup"&gt;a tool for incremental GitHub metadata backups&lt;/a&gt; as a short-to-medium-term alternative. To use and test the &lt;a href="https://github.com/bitcoin-data/"&gt;backups&lt;/a&gt;, I’ve set up a &lt;a href="https://mirror.b10c.me/"&gt;read-only metadata mirror generated&lt;/a&gt; from the backups (with &lt;a href="https://github.com/0xb10c/github-metadata-mirror"&gt;github.com/0xb10c/github-metadata-mirror&lt;/a&gt;): e.g. &lt;a href="https://mirror.b10c.me/bitcoin-bitcoin/"&gt;https://mirror.b10c.me/bitcoin-bitcoin/&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="warnet--fork-observer"&gt;Warnet + fork-observer&lt;/h3&gt;
&lt;p&gt;While I consider my &lt;a href="https://github.com/0xb10c/fork-observer"&gt;fork-observer&lt;/a&gt; project to be work-in-progress, I &lt;a href="https://github.com/bitcoin-dev-project/warnet/pull/87"&gt;helped&lt;/a&gt; the &lt;a href="https://github.com/bitcoin-dev-project/warnet"&gt;warnet&lt;/a&gt; Bitcoin network simulation project in setting up a local fork-observer instance and made sure attaching more than a 100 Bitcoin Core nodes works as intended.&lt;/p&gt;
&lt;h3 id="mit-dci-machines-for-peer-observer"&gt;MIT DCI machines for peer-observer&lt;/h3&gt;
&lt;p&gt;The MIT DCI generously provided me with access to six machines to run my work-in-progress Bitcoin P2P anomaly and attack detection tooling, &lt;a href="https://github.com/0xb10c/peer-observer"&gt;peer-observer&lt;/a&gt;, on. I deployed Bitcoin Core nodes and related software on these machines.&lt;/p&gt;
&lt;h3 id="coredev-and-btc-azores"&gt;CoreDev and BTC Azores&lt;/h3&gt;
&lt;p&gt;I flew to Terceira to attend the Bitcoin Core &lt;a href="https://coredev.tech/"&gt;CoreDev meeting&lt;/a&gt; and the &lt;a href="https://btcazores.com/"&gt;BTC Azores unconference&lt;/a&gt;. At CoreDev, I led a discussion session on (undisclosed) Bitcoin P2P network problems we’ve observed earlier this year. At the unconference, I enjoyed the discourse with the wider Bitcoin developer community.&lt;/p&gt;
&lt;h3 id="getrawaddrman-rpc--addrman-observer"&gt;&lt;code&gt;getrawaddrman&lt;/code&gt; RPC + addrman-observer&lt;/h3&gt;
&lt;p&gt;During Bitcoin Core PR review and discussions at CoreDev, the idea for a getrawaddrman RPC call came up. I added this RPC in PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/28523"&gt;#28523&lt;/a&gt;, which was merged recently.This RPC enables introspection into the Bitcoin Core IP address manager. To visualize the output, I created a work-in-progress tool called &lt;a href="https://github.com/0xB10C/addrman-observer"&gt;addrman-observer&lt;/a&gt;. Bitcoin Core developers interested in the addrman provided me with good feedback, the warnet project would like to use it, and I want it for my peer-observer setup.&lt;/p&gt;
&lt;h3 id="marathon-pool-invalid-block"&gt;Marathon Pool invalid block&lt;/h3&gt;
&lt;p&gt;I wrote down my notes on the Marathon pool invalid block at height 809478 in &lt;a href="https://b10c.me/observations/07-invalid-block-809478/"&gt;Invalid MARAPool block 809478&lt;/a&gt;. In turn, I received good feedback from people in the mining industry on my fork-observer project.&lt;/p&gt;
&lt;h3 id="bitcoin23-workshop-writing-a-nixos-module-for-your_app"&gt;bitcoin++23 Workshop: Writing a NixOS Module for your_app&lt;/h3&gt;
&lt;p&gt;For the &lt;a href="https://btcpp.dev/berlin23"&gt;nix+bitcoin focused edition of bitcoin++&lt;/a&gt;, I’ve prepared a 90 minute workshop enabling Bitcoin developers to write NixOS modules for their projects. The workshop can be found &lt;a href="https://github.com/0xB10C/btcpp23-nixos-modules-workshop"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>bitcoin++23 Workshop: Writing a NixOS Module for your_app</title><link>https://b10c.me/projects/022-bpp23-nixos-moduls-workshop/</link><pubDate>Sat, 07 Oct 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/022-bpp23-nixos-moduls-workshop/</guid><description>&lt;p&gt;Instructions for my &lt;em&gt;Writing a NixOS Module for your_app&lt;/em&gt; workshop.
Slides can be found &lt;a href="https://b10c.me/talks/019-bpp23-nixos-modules.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The workshop tries to convey the basics of writing a NixOS module. In the end,
participants should be able to write a basic NixOS module including a systemd
service for their project, be able to define and declare NixOS options, and be
familiar with basic systemd hardening and running containers on NixOS.&lt;/p&gt;
&lt;p&gt;A 360p recording of me giving the workshop can be found &lt;a href="https://www.youtube.com/watch?v=AijA3EpwgKM&amp;amp;t=300"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/AijA3EpwgKM?start=300?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="Live from bitcoin&amp;#43;&amp;#43; 23: Writing a NixOS Module for your_app"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id="task-0---setup-and-starting-the-vm"&gt;Task 0 - Setup and starting the VM&lt;/h2&gt;
&lt;p&gt;This workshop makes use of &lt;a href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; to spin up a personal environment
to work in. GitHub currently offers 120 hours of free CPU time per month for
Codespaces. All you need is a GitHub account. You can find the code for this
workshop in &lt;a href="https://github.com/0xB10C/btcpp23-nixos-modules-workshop"&gt;0xb10c/btcpp23-nixos-modules-workshop&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://codespaces.new/0xB10C/btcpp23-nixos-modules-workshop?quickstart=1"&gt;&lt;img src="https://github.com/codespaces/badge.svg" alt="Open in GitHub Codespaces"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This will open a new tab with a VSCode interface. The codespace automatically
starts a NixOS virtual machine. This might take a couple of minutes. It will let
you know when the VM is ready.&lt;/p&gt;
&lt;p&gt;A downside of using &lt;a href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; is that we don&amp;rsquo;t have KVM support for
our NixOS VM and have to fall back to emulation, which is considerably slower
than a KVM VM would be. Running &lt;code&gt;nixos-rebuild switch&lt;/code&gt; inside the NixOS VM is
quite slow and not recommended for this workshop. Rather, run
&lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt; on the host, which deploys the configuration to the VM.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve marked commands with &lt;code&gt;host$ &amp;lt;command&amp;gt;&lt;/code&gt; when you should run them from the
GitHub Codespaces shell. Commands marked with &lt;code&gt;vm$ &amp;lt;command&amp;gt;&lt;/code&gt; should be run from
the VM shell. You can use &lt;code&gt;ssh vm&lt;/code&gt; to log in to the VM.&lt;/p&gt;
&lt;h3 id="your_app"&gt;your_app&lt;/h3&gt;
&lt;p&gt;For the workshop, I&amp;rsquo;ve written a very basic Rust program called &lt;code&gt;your_app&lt;/code&gt;.
Imagine this is a project you&amp;rsquo;ve been working on, and you now want to write a
NixOS module for it. &lt;code&gt;your_app&lt;/code&gt; starts a web server on a user-defined port and
responds to requests. To show how easy it is to interact with other NixOS
modules and services, &lt;code&gt;your_app&lt;/code&gt; communicates with the RPC interface of a
Bitcoin Core node.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ your_app --help
your_app 0.1.0
USAGE:
your_app --rpc-host &amp;lt;RPC_HOST&amp;gt; --rpc-port &amp;lt;RPC_PORT&amp;gt; --rpc-user &amp;lt;RPC_USER&amp;gt;
--rpc-password &amp;lt;RPC_PASSWORD&amp;gt; &amp;lt;SUBCOMMAND&amp;gt;
OPTIONS:
-h, --help
Print help information
--rpc-host &amp;lt;RPC_HOST&amp;gt;
The host of the Bitcoin Core RPC server
--rpc-password &amp;lt;RPC_PASSWORD&amp;gt;
A password for authentication with the Bitcoin Core RPC server
--rpc-port &amp;lt;RPC_PORT&amp;gt;
The port of the Bitcoin Core RPC server
--rpc-user &amp;lt;RPC_USER&amp;gt;
A user for authentication with the Bitcoin Core RPC server
-V, --version
Print version information
SUBCOMMANDS:
help Print this message or the help of the given subcommand(s)
server Run the app with a web server
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="task-1---first-steps"&gt;Task 1 - First steps&lt;/h2&gt;
&lt;p&gt;In this task, we enable the Bitcoin Core service that ships with NixOS and
learn how to inspect a systemd service.&lt;/p&gt;
&lt;h3 id="11-enable-the-regtest-bitcoin-core-node"&gt;1.1: Enable the regtest Bitcoin Core node&lt;/h3&gt;
&lt;p&gt;In the &lt;code&gt;configuration.nix&lt;/code&gt; file you&amp;rsquo;ll find a &lt;code&gt;bitcoind&lt;/code&gt; service called
&lt;code&gt;regtest&lt;/code&gt;. This service is defined in the &lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-23.05/nixos/modules/services/networking/bitcoind.nix"&gt;&lt;code&gt;services/networking/bitcoind.nix&lt;/code&gt;&lt;/a&gt;
module&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Searching for &lt;code&gt;services.bitcoind&lt;/code&gt; on &lt;a href="https://search.nixos.org/options?query=services.bitcoind"&gt;search.nixos.org&lt;/a&gt; shows the
options that can be set. For this workshop, I&amp;rsquo;ve configured a Bitcoin Core
node on a local &lt;code&gt;regtest&lt;/code&gt; test network with an RPC server listening on port
18444.&lt;/p&gt;
&lt;p&gt;To enable the Bitcoin Core node, change the &lt;code&gt;enable = false;&lt;/code&gt; into &lt;code&gt;enable = true;&lt;/code&gt;.
For the changes to take effect, run &lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt; from the host to rebuild
the VM. NixOS will automatically generate, enable, and start the systemd service
defined for this node in the NixOS &lt;code&gt;bitcoind.nix&lt;/code&gt; module.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;host$ sh nixos-rebuild-vm.sh
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="12-using-systemd-tools"&gt;1.2: Using systemd tools&lt;/h3&gt;
&lt;p&gt;Once the system is rebuilt, you can inspect the service with the &lt;code&gt;systemctl status&lt;/code&gt;
command. The &lt;code&gt;status&lt;/code&gt; command only shows the last few log lines. If you want
to see more lines, use the &lt;code&gt;journalctl&lt;/code&gt; tool.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm $ systemctl status bitcoind-regtest.service
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ journalctl --pager-end --follow --unit bitcoind-regtest
vm$ journalctl -efu bitcoind-regtest
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="questions"&gt;Questions:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;How many log lines does &lt;code&gt;systemctl status bitcoind-regtest&lt;/code&gt; show?&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;systemctl status bitcoind-regtest&lt;/code&gt; also shows the generated &lt;code&gt;*.service&lt;/code&gt; file. Can you find the &lt;code&gt;-datadir&lt;/code&gt; parameter passed to Bitcoin Core? Where is the datadir?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="task-2---defining-and-declaring-options"&gt;Task 2 - defining and declaring options&lt;/h2&gt;
&lt;p&gt;The NixOS module for &lt;code&gt;your_app&lt;/code&gt; is located in &lt;code&gt;modules/your_app/default.nix&lt;/code&gt;.
I&amp;rsquo;ve already defined a &lt;code&gt;your_app_server&lt;/code&gt; and a &lt;code&gt;your_app_backup&lt;/code&gt; systemd
service in the &lt;code&gt;config&lt;/code&gt; section of the NixOS module. I&amp;rsquo;ve left a few comments
on the options that already exist. The places where you&amp;rsquo;ll need to fill in
something are marked with &lt;code&gt;# FIXME: Task X.X&lt;/code&gt;. You&amp;rsquo;ve successfully completed
the task when you can reach the &lt;code&gt;your_app server&lt;/code&gt; web server from the host machine
(from outside the VM).&lt;/p&gt;
&lt;h3 id="21-declare-options-for-your_app_server"&gt;2.1: Declare options for &lt;code&gt;your_app_server&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When running &lt;code&gt;your_app server&lt;/code&gt;, it expects the following command line arguments
from us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--rpc-host&lt;/code&gt; and &lt;code&gt;--rpc-port&lt;/code&gt; for the location of the Bitcoin Core RPC server to connect to&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--rpc-user&lt;/code&gt; and &lt;code&gt;--rpc-password&lt;/code&gt; for authenticating with the Bitcoin Core RPC server&lt;/li&gt;
&lt;li&gt;and a &lt;code&gt;port&lt;/code&gt; on which the web server will start to listen on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your task is to declare options for these command line arguments in
&lt;code&gt;options.services.your_app&lt;/code&gt; in &lt;code&gt;modules/your_app/default.nix&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use the &lt;code&gt;mkOption&lt;/code&gt; function - documentation can be found in the &lt;a href="https://nixos.org/manual/nixos/stable/#sec-option-declarations"&gt;NixOS manual on &lt;code&gt;mkOption&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;there&amp;rsquo;s a list of &lt;code&gt;types&lt;/code&gt; you can use in the NixOS manual &lt;a href="https://nixos.org/manual/nixos/stable/#sec-option-types"&gt;Option Types&lt;/a&gt; section&lt;/li&gt;
&lt;li&gt;think about reasonable defaults for the options. Defaulting to &lt;code&gt;null&lt;/code&gt; helps NixOS complain when an option is not set by the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="22-using-the-declared-options"&gt;2.2: Using the declared options&lt;/h3&gt;
&lt;p&gt;We can use the values from the options declared in 2.1 to define options from
NixOS modules such as, for example, the systemd services module. I&amp;rsquo;ve prepared
a systemd service in &lt;code&gt;systemd.services.your_app_server&lt;/code&gt; and have already
defined a few options. It&amp;rsquo;s your task to fill in the command line arguments
(marked with &lt;code&gt;FIXME: 2.2&lt;/code&gt;) in &lt;code&gt;serviceConfig.ExecStart&lt;/code&gt; with the options you
defined in 2.1.&lt;/p&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
Hint 1: Help, where do I start?
&lt;/summary&gt;
&lt;code&gt;ExecStart&lt;/code&gt; is defined with a multi-line string. Each line contains a new
argument. You can insert Nix expressions into strings with
&lt;code&gt;${ &amp;lt;nix expression&amp;gt; }&lt;/code&gt;. If you have defined an option called &lt;code&gt;username&lt;/code&gt; in
2.1., you could access it with &lt;code&gt;${ cfg.username }&lt;/code&gt;. Here, &lt;code&gt;cfg&lt;/code&gt; is short for
&lt;code&gt;config.services.your_app&lt;/code&gt; (see the &lt;code&gt;let .. in&lt;/code&gt; at the top of the file).
&lt;/details&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
Hint 2: &lt;code&gt;error: cannot coerce an integer to a string&lt;/code&gt;
&lt;/summary&gt;
Integers and strings don&amp;rsquo;t mix well in Nix. You can, however, convert an
integer to a string with a function provided by NixOS. See the &lt;a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-toString"&gt;&lt;code&gt;toString&lt;/code&gt;&lt;/a&gt;
function.
&lt;/details&gt;
&lt;h3 id="23-enable-the-your_app-service-in-configurationnix"&gt;2.3 Enable the your_app service in &lt;code&gt;configuration.nix&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;your_app&lt;/code&gt; module is imported in &lt;code&gt;configuration.nix&lt;/code&gt;. We can now enable the
&lt;code&gt;services.your_app.enable&lt;/code&gt; option by setting it to &lt;code&gt;true&lt;/code&gt;. We also need to define
the options we declared in 2.1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set the port for the web server to &lt;code&gt;4242&lt;/code&gt; (this is important, otherwise the port forwarding to the VM won&amp;rsquo;t work)&lt;/li&gt;
&lt;li&gt;the Bitcoin Core RPC server listens on &lt;code&gt;localhost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;you can set the Bitcoin Core RPC server port to &lt;code&gt;config.services.bitcoind.&amp;quot;regtest&amp;quot;.rpc.port&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use the RPC user &lt;code&gt;workshop&lt;/code&gt; and the password &lt;code&gt;btcpp23berlin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="24-open-the-firewall"&gt;2.4: Open the firewall&lt;/h3&gt;
&lt;p&gt;By default, NixOS has a firewall that blocks incoming packets. To be able to
reach the web server from the host, you&amp;rsquo;ll need to open this port in the
firewall. See the &lt;a href="https://search.nixos.org/options?channel=23.05&amp;amp;show=networking.firewall.allowedTCPPorts&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=allowedTCPPorts"&gt;&lt;code&gt;allowedTCPPorts&lt;/code&gt;&lt;/a&gt; option of the NixOS firewall for more
information. Similar to accessing the Bitcoin Core RPC port, you can access the
port from the &lt;code&gt;your_app server&lt;/code&gt; service.&lt;/p&gt;
&lt;h3 id="25-sh-nixos-rebuild-vmsh"&gt;2.5: sh nixos-rebuild-vm.sh&lt;/h3&gt;
&lt;p&gt;To rebuild and apply the configuration, run &lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt;. NixOS might
complain about errors in your configuration or module. Try to fix them, or ask
someone next to you for help. Looking at the logs of the &lt;code&gt;your_app_server.service&lt;/code&gt;
systemd service might help.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve managed to switch to your new configuration, try accessing the
&lt;code&gt;your_app&lt;/code&gt; web server from your host system.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ journalctl -efu your_app_server
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;host$ curl localhost:4242
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="task-3---secrets-security-and-hardening"&gt;Task 3 - Secrets, security, and hardening&lt;/h2&gt;
&lt;p&gt;This task covers basic security and systemd hardening. However, this is likely
not enough for a production setup.&lt;/p&gt;
&lt;h3 id="31-rpc-password-is-world-readable"&gt;3.1: RPC password is world-readable!&lt;/h3&gt;
&lt;p&gt;Your NixOS system configuration is world-readable by everyone with access to the
&lt;code&gt;nix/store/&lt;/code&gt;. Additionally, the systemd service configurations are world-readable too.
This means the RPC password set in 2.3 is now world-readable, too. Can you find
it?&lt;/p&gt;
&lt;h4 id="questions-1"&gt;Questions:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Where did you find the RPC password?&lt;/li&gt;
&lt;li&gt;How can this be avoided?&lt;/li&gt;
&lt;/ol&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
Hint for question 2
&lt;/summary&gt;
Search for &lt;code&gt;passwordFile&lt;/code&gt; on &lt;a href="https://search.nixos.org/options"&gt;search.nixos.org/options&lt;/a&gt;.
&lt;/details&gt;
&lt;h3 id="32-systemd-analyze-security"&gt;3.2: systemd-analyze security&lt;/h3&gt;
&lt;p&gt;Under the &amp;ldquo;Principle of Least Privilege&amp;rdquo;, our newly set up systemd service
should only have the minimum needed privileges. Systemd offers a bunch of
sandboxing and hardening features that we can use to reduce the privileges
of the &lt;code&gt;your_app&lt;/code&gt; service.&lt;/p&gt;
&lt;p&gt;The default systemd service options are quite lax. You can use&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ systemd-analyze security
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;to let systemd list you an &amp;ldquo;exposure&amp;rdquo; score (good = 0 - 10 = bad) for all loaded
services. A high &amp;ldquo;exposure&amp;rdquo; score does not mean that the service isn&amp;rsquo;t
sandboxed. It also does not mean that the service is vulnerable to attacks.
It indicates that there is likely room for improvement by applying additional
hardening settings to the service. Likewise, a perfect score doesn&amp;rsquo;t mean
the service is completely secure. A better score indicates that the service
has fewer privileges.&lt;/p&gt;
&lt;h4 id="questions-2"&gt;Questions:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;What exposure score is shown for &lt;code&gt;bitcoind-regest.service&lt;/code&gt; and &lt;code&gt;your_app_server.service&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Which service has the lowest score?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="33-your_app-hardening"&gt;3.3: your_app hardening&lt;/h3&gt;
&lt;p&gt;There is room for improvement in the &amp;ldquo;exposure&amp;rdquo; score of &lt;code&gt;your_app&lt;/code&gt;. You can
use&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ systemd-analyze security your_app_server.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;to list hardening options that can be enabled to improve the score. For
inspiration, take a look at the nix-bitcoin &lt;a href="https://github.com/fort-nix/nix-bitcoin/blob/9f28720e45577574251a71ea835581a69abb3cf7/pkgs/lib.nix#L9-L38"&gt;&lt;code&gt;defaultHardening&lt;/code&gt;&lt;/a&gt; options.&lt;/p&gt;
&lt;p&gt;Set the hardening options in &lt;code&gt;systemd.services.your_app_server.serviceConfig&lt;/code&gt; in
&lt;code&gt;modules/your_app/default.nix&lt;/code&gt;. You need to do a &lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt;
for the changes to be applied. This will also restart the &lt;code&gt;your_app_server.service&lt;/code&gt;.
Check if it still starts and the web server is still reachable from the host. If
not, you might have removed too many privileges.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;host$ curl localhost:4242
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="task-4---containers"&gt;Task 4 - Containers&lt;/h2&gt;
&lt;p&gt;Running software in containers is also possible on NixOS. NixOS supports
declarative oci-containers (i.e., Docker containers) but also allows running
imperative and declarative NixOS containers. OCI-containers can be useful if
there&amp;rsquo;s software not (yet) packaged for Nix. NixOS containers might be useful
if you want to run multiple instances of the same service on the same machine
or need a place for a quick experiment.&lt;/p&gt;
&lt;h3 id="41-oci-containers"&gt;4.1: OCI-Containers&lt;/h3&gt;
&lt;p&gt;To demonstrate running an OCI-container, we can use the
&lt;a href="https://hub.docker.com/r/nginxdemos/hello/"&gt;&lt;code&gt;nginxdemos/hello:plain-text&lt;/code&gt;&lt;/a&gt; image. In &lt;code&gt;configuration.nix&lt;/code&gt; you&amp;rsquo;ll find a
commented &lt;code&gt;plaintext-hello&lt;/code&gt; definition under &lt;code&gt;virtualisation.oci-containers.containers&lt;/code&gt;.
Uncomment it and set the &lt;code&gt;image&lt;/code&gt; (just use the image name above) and &lt;code&gt;ports&lt;/code&gt;
values (use &lt;code&gt;&amp;quot;8000:80&amp;quot;&lt;/code&gt;). More options can be found &lt;a href="https://search.nixos.org/options?query=virtualisation.oci-containers"&gt;here&lt;/a&gt;.
You will need to do a &lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt; to start the OCI container.&lt;/p&gt;
&lt;p&gt;Test that the web server in the container is reachable with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ curl localhost:8000
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="questions-3"&gt;Questions&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Which backend is being used by default to run oci-containers? Docker or podman?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="42-nixos-containers"&gt;4.2: NixOS containers&lt;/h3&gt;
&lt;p&gt;NixOS containers are lightweight &lt;a href="https://wiki.archlinux.org/title/systemd-nspawn"&gt;systemd-nspawn&lt;/a&gt; containers running NixOS.
These can be defined &lt;a href="https://nixos.org/manual/nixos/stable/#sec-imperative-containers"&gt;imperatively&lt;/a&gt; and &lt;a href="https://nixos.org/manual/nixos/stable/#sec-declarative-containers"&gt;declaratively&lt;/a&gt;. Imperative containers
are great for short-to-medium term experimental setups, while declarative
containers can be used for long-running container setups.&lt;/p&gt;
&lt;h4 id="imperative-nixos-container"&gt;Imperative NixOS container&lt;/h4&gt;
&lt;p&gt;To imperatively create and start a NixOS container named &lt;code&gt;btcpp23&lt;/code&gt; use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ nixos-container create btcpp23
vm$ nixos-container start btcpp23
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see the container logs and login as root with the following
commands. See &lt;a href="https://nixos.org/manual/nixos/stable/#sec-imperative-containers"&gt;Imperative NixOS Containers&lt;/a&gt; for more.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vm$ systemctl status container@btcpp23
vm$ nixos-container root-login btcpp23
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="declarative-nixos-container"&gt;Declarative NixOS container&lt;/h4&gt;
&lt;p&gt;An empty, auto-starting, declarative NixOS container might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;autoStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;privateNetwork&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# An emtpy NixOS container.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stateVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;23.05&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Feel free to copy and paste this container into the &lt;code&gt;configuration.nix&lt;/code&gt; file
and rebuild the VM. You should be able to login with &lt;code&gt;nixos-container root-login&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
local install (not recommended)
&lt;/summary&gt;
&lt;h3 id="01-install-nixos-shell"&gt;0.1: Install &lt;code&gt;nixos-shell&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;To utillize NixOS modules, we need a running NixOS system. In this workshop,
we&amp;rsquo;ll start a NixOS qemu virtual machine with the &lt;a href="https://github.com/Mic92/nixos-shell"&gt;&lt;code&gt;nixos-shell&lt;/code&gt;&lt;/a&gt; tool. Don&amp;rsquo;t
confuse this with the &lt;code&gt;nix-shell&lt;/code&gt; command, which allows us to temporary bring
Nix packages into our environment. We can however use &lt;code&gt;nix-shell&lt;/code&gt; to install
&lt;code&gt;nixos-shell&lt;/code&gt; as it&amp;rsquo;s &lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-23.05/pkgs/tools/virtualization/nixos-shell/default.nix"&gt;packaged in nixpkgs&lt;/a&gt;. This assumes you have Nix &lt;a href="https://nixos.org/download"&gt;installed&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nix-shell -p nixos-shell
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="02-clone-the-workshop-repository"&gt;0.2: Clone the workshop repository&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ git clone https://github.com/0xB10C/btcpp23-nixos-modules-workshop.git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can find the configuration for the &lt;code&gt;nixos-shell&lt;/code&gt; VM in &lt;a href="https://github.com/0xB10C/btcpp23-nixos-modules-workshop.git"&gt;this repository&lt;/a&gt;.
The &lt;a href="https://github.com/0xB10C/btcpp23-nixos-modules-workshop/blob/master/vm.nix"&gt;&lt;code&gt;vm.nix&lt;/code&gt;&lt;/a&gt; file defines qemu VM parameters such as the number of CPU cores to
use, the amount of RAM to reserve, and the size of the VM&amp;rsquo;s disk. Feel free to
leave all files as they are. You&amp;rsquo;ll only need to modify &lt;code&gt;configuration.nix&lt;/code&gt; and the
&lt;code&gt;modules/your_app/default.nix&lt;/code&gt; module during the workshop.&lt;/p&gt;
&lt;h3 id="03-starting-the-vm-and-logging-in"&gt;0.3: Starting the VM and logging in&lt;/h3&gt;
&lt;p&gt;Inside the &lt;code&gt;btcpp23-nixos-modules-workshop&lt;/code&gt; folder, start the VM by running &lt;code&gt;nixos-shell&lt;/code&gt;.
While initial VM setup might take a minute or two, all following starts should be faster.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll be greeted with a message explaining how to login and how to quit the VM.
Use the &lt;code&gt;root&lt;/code&gt; user without a password to log in. To exit the VM, either
use &lt;code&gt;shutdown now&lt;/code&gt; to shut it down or &lt;code&gt;Ctrl+a c&lt;/code&gt; and type &lt;code&gt;quit&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="04-rebulding-the-system-with-sh-nixos-rebuild-vmsh-optional"&gt;0.4: Rebulding the system with &lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt; (optional)&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Skip this step if you plan to directly continue with &lt;strong&gt;Task 1&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once logged in, you can rebuild the NixOS system from the configuration. Changes
can be made to the &lt;code&gt;configuration.nix&lt;/code&gt; from your favorite editor on the host.
If you are setting up the VM up before the workshop. feel free to run
&lt;code&gt;sh nixos-rebuild-vm.sh&lt;/code&gt; once to rebuild the system.&lt;/p&gt;
&lt;/details&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This module allows running multiple Bitcoin Core instances at the same time,
which makes it a bit harder to reason about as a NixOS module beginner.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Workshop: Writing a NixOS module for YOUR_APP</title><link>https://b10c.me/talks/019-bpp23-nixos-modules/</link><pubDate>Sat, 07 Oct 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/019-bpp23-nixos-modules/</guid><description>&lt;p&gt;I held a workshop about &amp;ldquo;Writing a NixOS module for YOUR_APP&amp;rdquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://b10c.me/projects/022-bpp23-nixos-moduls-workshop/"&gt;Workshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/talks/019-bpp23-nixos-modules/019-bpp23-nixos-modules.html"&gt;Slides (reveal-js)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AijA3EpwgKM&amp;amp;t=300"&gt;Stream (360p)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Invalid MARAPool block 809478</title><link>https://b10c.me/observations/07-invalid-block-809478/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/07-invalid-block-809478/</guid><description>&lt;p&gt;Notes on the invalid Bitcoin mainnet block at height 809478 mined by
experimental, in-house MARAPool mining pool software on September 27, 2023.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/07-invalid-block-809478/testnet-invalid-blocks.png'
alt='invalid blocks on the Bitcoin testnet on the 26th September, 2023'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On September 26, 2023, I noticed that a testnet node attached to a
&lt;a href="https://github.com/0xB10C/fork-observer"&gt;fork-observer&lt;/a&gt; instance reported frequent invalid blocks. The nodes &lt;code&gt;debug.log&lt;/code&gt;
showed multiple messages similar to the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: ConnectBlock: Consensus::CheckTxInputs: aca785e8.., bad-txns-inputs-missingorspent, CheckTxInputs: inputs missing/spent
InvalidChainFound: invalid block=00000000.. height=2505274 log2_work=75.527022 date=2023-09-26T15:20:05Z
InvalidChainFound: current best=00000000.. height=2505273 log2_work=75.527015 date=2023-09-26T15:13:45Z
ERROR: ConnectTip: ConnectBlock 00000000.. failed, bad-txns-inputs-missingorspent, CheckTxInputs: inputs missing/spent
InvalidChainFound: invalid block=00000000.. height=2505274 log2_work=75.527022 date=2023-09-26T15:20:05Z
InvalidChainFound: current best=00000000.. height=2505273 log2_work=75.527015 date=2023-09-26T15:13:45Z
ERROR: AcceptBlockHeader: block 00000000.. is marked invalid
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This isn&amp;rsquo;t concerning by itself, as the Bitcoin testnet can be weird at times
due to its low mining difficulty. No one was losing money, as testnet coins do
not have any value. Someone could be testing a new block template algorithm and
generating invalid blocks. Since this stopped after a few blocks, I didn&amp;rsquo;t
investigate deeper after &lt;a href="https://twitter.com/0xB10C/status/1706695449598402868"&gt;posting&lt;/a&gt; about it on Twitter.&lt;/p&gt;
&lt;p&gt;On September 27, fork-observer notified me about the &lt;a href="https://twitter.com/0xB10C/status/1706937041739530556"&gt;invalid Bitcoin mainnet
block&lt;/a&gt; &lt;code&gt;000000000000000000006840568a01091022093a176d12a1e8e5e261e4f11853&lt;/code&gt; at
height 809478. Invalid blocks on mainnet are rather uncommon. Someone is losing
money when they use time and energy to grind the block header of an invalid
block. I checked a few monitoring nodes, and all of them saw the block and
considered it invalid. BitMex Research&amp;rsquo;s forkmonitor also &lt;a href="https://forkmonitor.info/feeds/btc/blocks/invalid.rss"&gt;lists the block as
invalid&lt;/a&gt; and attributes the block to MARAPool.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/07-invalid-block-809478/mainnet-invalid-block.png'
alt='invalid block on the Bitcoin mainnet on the 27th September, 2023'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While validating this block, Bitcoin Core reported:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: ConnectBlock: Consensus::CheckTxInputs: 66dfefcdc3eeec2745c11f511f6068d62f34c34c767ba0feed47f63f01ccc2d8,
bad-txns-inputs-missingorspent, CheckTxInputs: inputs missing/spent
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means, there was a problem validating the transaction &lt;code&gt;66dfefcd[..]&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.
Specificially, a previous output refferenced in an input wasn&amp;rsquo;t found in the
UTXO-set during block verification. This is usually either caused by a
transaction ordering problem (output is being spent before it&amp;rsquo;s created) or a
corrupt UTXO set. Problems like this always raise the question where the problem
originates from and who else might be affected. Is this a bug only on the mining
pool side? Or is this a bug in some version of Bitcoin Core possibly affecting
the whole network?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://sprovoost.nl/"&gt;Sjors Provoost&lt;/a&gt; pointed out that &lt;code&gt;66dfefcd[..]&lt;/code&gt;, which is
simple a one-input and one-output transaction, did end up being mined by Foundry
USA in the competing, valid &lt;a href="https://mempool.space/tx/66dfefcdc3eeec2745c11f511f6068d62f34c34c767ba0feed47f63f01ccc2d8"&gt;block 809484&lt;/a&gt;.
The input of &lt;code&gt;66dfefcd[..]&lt;/code&gt; spends the first output of &lt;code&gt;7d18f0ee[..]&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Looking at
the invalid block with&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;bitcoin-cli getblock 000000000000000000006840568a01091022093a176d12a1e8e5e261e4f11853
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(&lt;a href="https://gist.github.com/0xB10C/70f35371a538ead2d6cf7353740d83b4"&gt;output&lt;/a&gt;)
shows that &lt;code&gt;66dfefcd[..]&lt;/code&gt; is the 6th transaction while &lt;code&gt;7d18f0ee[..]&lt;/code&gt; is the 1454th.
In a valid block, &lt;code&gt;7d18f0ee[..]&lt;/code&gt; should have been included before &lt;code&gt;66dfefcd[..]&lt;/code&gt;.
This shows a transaction ordering problem. Bitcoin Core checks created block
templates for validity before responding to a block template request. This check
would have caught transaction ordering problems before the template was sent to
miners.&lt;/p&gt;
&lt;p&gt;Upon closer inspection, it&amp;rsquo;s noticeable that the coinbase transaction of the
invalid block includes the string &lt;code&gt;/MARA Pool (v092623)/&lt;/code&gt;. The &lt;code&gt;v092623&lt;/code&gt; part
looks like a version number and could indicate software built on September 26,
2023 (MM-DD-YY), the day the invalid blocks on testnet first appeared. Checking
the invalid blocks on testnet shows a similar but differently formatted string:
&lt;code&gt;/MARA Pool (v230812AUG)/&lt;/code&gt;. This could be August 12, 2023, when read as
YY-MM-DD. While the date formats don&amp;rsquo;t match, the testnet and mainnet activity
seem related.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/mononautical"&gt;@mononaut&lt;/a&gt; further inspected the invalid block and found 145 transactions that
were included before their parent. They, too, came to the &lt;a href="https://twitter.com/mononautical/status/1707060734620807172"&gt;conclusion&lt;/a&gt; that this
likely isn&amp;rsquo;t related to Bitcoin Core&amp;rsquo;s block template algorithm. Moreover, the
transactions in the block were &lt;a href="https://twitter.com/mononautical/status/1707075749755134163"&gt;sorted by fee&lt;/a&gt; before mining, which breaks
Bitcoin&amp;rsquo;s consensus rules if there are parent-child relationships in the
transactions.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;This is what MARA&amp;#39;s invalid block at 809478 looks like:&lt;br&gt;&lt;br&gt; - pink transactions no longer exist in the main chain&lt;br&gt; - blue transactions are invalid due to ordering (they spend an output from a transaction included later in the block) &lt;a href="https://t.co/SJI1azOB5Z"&gt;https://t.co/SJI1azOB5Z&lt;/a&gt; &lt;a href="https://t.co/5gY9TRA2eG"&gt;pic.twitter.com/5gY9TRA2eG&lt;/a&gt;&lt;/p&gt;&amp;mdash; mononaut (@mononautical) &lt;a href="https://twitter.com/mononautical/status/1707055190971457698?ref_src=twsrc%5Etfw"&gt;September 27, 2023&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Marathon Digital Holdings later confirmed that they mined an invalid block and
clarified that they are using a small portion of their hash rate to experiment
with a new, internal mining pool software in a development environment. This
software included a bug that caused it to produce an invalid block. They also
made sure to clarify that this isn&amp;rsquo;t a problem with Bitcoin Core in any way.
No other mining pool should be affected.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
We can confirm that Marathon did mine an invalid block. We utilize a small
portion of our hash rate to experiment with our development pool and research
potential methods to optimize our operations. The error was the result of an
unanticipated bug that came from one of our experiments. In no way was this
experiment an attempt to alter Bitcoin Core in any way. Our team noticed the
invalid block around the same time as the rest of the world, and we immediately
corrected the error. This incident, while unintended, underscores the robust
security of the Bitcoin network, which rejected and rectified the anomaly.
&lt;br&gt;&amp;horbar; &lt;a href="https://twitter.com/MarathonDH/status/1707067548661928108"&gt;@MarathonDH&lt;/a&gt;
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;They mention that their team noticed the invalid block at the same time as
everyone else. It&amp;rsquo;s, however, unclear to me why they didn&amp;rsquo;t notice the six
invalid blocks on testnet a day before switching to mining on mainnet.&lt;/p&gt;
&lt;p&gt;If a mining pool (or any other company) is interested, I&amp;rsquo;d be happy to help set
up an internal and private
&lt;a href="https://github.com/0xb10c/fork-observer"&gt;fork-observer&lt;/a&gt; (FOSS) instance that
can be used to monitor what your nodes consider to be the valid chain tip and
alert you on invalid blocks and reorgs.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
To clarify, this bug emanated from Marathon's own internal development
environment. It was not related to Marathon's production pool. It was also not
related to Bitcoin Core. Bitcoin functioned exactly as designed by excluding the
invalid block.
&lt;br&gt;&amp;horbar; &lt;a href="https://twitter.com/MarathonDH/status/1707082061620580816"&gt;@MarathonDH&lt;/a&gt;
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;Marathon ends their tweet with &amp;ldquo;Bitcoin functioned exactly as designed by
excluding the invalid block&amp;rdquo;. I agree - this was just a 6.43701991 BTC (USD
$170k at the time of writing) bug that could probably have been avoided.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;code&gt;66dfefcdc3eeec2745c11f511f6068d62f34c34c767ba0feed47f63f01ccc2d8&lt;/code&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;code&gt;7d18f0eefce0497b5d0c9b61fdf816b7744587c7e5e57acc53de71d1dae59725&lt;/code&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Support from MIT DCI for peer-observer</title><link>https://b10c.me/funding/2023-mit-dci-support/</link><pubDate>Fri, 01 Sep 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2023-mit-dci-support/</guid><description>&lt;p&gt;The &lt;a href="https://www.dci.mit.edu/"&gt;MIT DCI&lt;/a&gt; supported my &lt;a href="https://b10c.me/projects/024-peer-observer/"&gt;peer-observer&lt;/a&gt; project with six monitoring Bitcoin nodes for 1.5 years.&lt;/p&gt;</description></item><item><title>GitHub Metadata Backup and Mirror</title><link>https://b10c.me/projects/021-github-backups-mirror/</link><pubDate>Thu, 10 Aug 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/021-github-backups-mirror/</guid><description>&lt;p&gt;This year, the Bitcoin Core project will have its &lt;a href="https://github.com/bitcoin/bitcoin/issues/1"&gt;13th anniversary&lt;/a&gt; being
hosted on GitHub. 13 years of issues and pull requests with critical design
decisions and nuanced discussions hosted with a US-based company known for
shutting down open-source software repositories when needing to follow DMCA and
OFAC requests. While the medium-to-longterm plan is to move off of GitHub, I&amp;rsquo;ve
written a tool for incremental GitHub metadata backups as a short-to-medium-term
alternative. To use and test the backups, I&amp;rsquo;ve set up a read-only metadata mirror
generated from the backups.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;
&lt;a href='https://github.com/0xB10C/github-metadata-backup' class="btn btn-outline-danger my-2" role="button"&gt;github-metadata-backup&lt;/a&gt;
&lt;a href='https://github.com/0xB10C/github-metadata-mirror' class="btn btn-outline-danger my-2" role="button"&gt;github-metadata-mirror&lt;/a&gt;
&lt;a href='https://mirror.b10c.me/' class="btn btn-outline-danger my-2" role="button"&gt;Mirrors and backups&lt;/a&gt;
&lt;a href='http://e3y5vky4v7snefqyhbn6kcmyl5fo4cnk3a2irh2ttvwua46ww5ubl6qd.onion/' class="btn btn-outline-danger my-2" role="button"&gt;Mirrors and backups (Onion Service)&lt;/a&gt;
&lt;/p&gt;
&lt;h3 id="moving-off-of-github"&gt;Moving off of GitHub?&lt;/h3&gt;
&lt;p&gt;Moving the Bitcoin Core development process away from GitHub has &lt;a href="https://github.com/bitcoin-core/bitcoin-devwiki/wiki/GitHub-alternatives-for-Bitcoin-Core"&gt;repeatedly been
a topic&lt;/a&gt; among Bitcoin Core contributors. GitHub being a single point of failure,
its unreliability, and the spam combined with the lack of moderation tools have
been frequent topics. However, moving away from GitHub also means finding a better
alternative. Ideally, the alternative is decentralized or federated and easily
self-hostable to avoid moving to the next single point of failure. This also
raises questions about who will host and administer the platform. Who is a
trustworthy sysadmin to protect the alternative from DOS and other attacks? A
slow or unreachable platform does not help developer productivity. Undeniably,
GitHub has a significant network effect. Requiring users to sign up on another
platform to report an issue or submit a small patch might not work well. Good
code-review tools and stable CI integrations are high on the developer wishlist.&lt;/p&gt;
&lt;p&gt;While a perfect alternative might not exist, Bitcoin Core developer &lt;a href="https://github.com/fjahr"&gt;fjahr&lt;/a&gt;
currently experiments with a self-hosted GitLab instance that synchronizes
GitHub issues and pull requests in real-time. This is a &lt;em&gt;hot-spare&lt;/em&gt; alternative
to GitHub. It might not be the final medium-to-longterm alternative the project
seeks. Still, it can act as an interim alternative, allowing developers to
continue working in case of problems with GitHub.&lt;/p&gt;
&lt;h3 id="github-metadata-backups"&gt;GitHub metadata backups&lt;/h3&gt;
&lt;p&gt;Tangentially, having a standalone backup of the development history of the
Bitcoin Core project is vital for the project&amp;rsquo;s future. In the case
that GitHub de-platforms the project, a backup of issues and pull requests
with comments and code review allows reading up on design decisions and
discussions about smaller and more extensive changes. This is amplified by
long-time Bitcoin Core developers leaving and new developers starting to
contribute to the project. Some ideas and discussions of the last 13 years
would otherwise be lost. The backup can also be imported into a GitHub
alternative once the project agrees to move to a new platform.&lt;/p&gt;
&lt;p&gt;There are already tools to back up GitHub metadata, like issues and pull
requests, and a public GitHub repository containing a metadata backup. The
GitHub user &lt;a href="https://github.com/zw"&gt;zw&lt;/a&gt; has been running his &lt;a href="https://github.com/zw/ghrip"&gt;ghrip&lt;/a&gt; Perl script for the last nine years
and has pushed nearly 30.000 incremental backup commits to his &lt;a href="https://github.com/zw/bitcoin-gh-meta"&gt;bitcoin-gh-meta&lt;/a&gt;
backup repository. However, upon closer inspection, it turns out that the
backups are incomplete. &lt;a href="https://github.com/zw/bitcoin-gh-meta/pull/6"&gt;Pull-request reviews are missing&lt;/a&gt; from the backups.
This is likely due to a change in the GitHub API since the Perl script was
last touched nine years ago. Also, Bitcoin Core maintainer &lt;a href="https://github.com/achow101"&gt;achow101&lt;/a&gt; has a project
called &lt;a href="https://github.com/achow101/github-dl"&gt;github-dl&lt;/a&gt;, which downloads the full git repository, including source code,
release-assets, and a wiki, if present. The backups are, however, not incremental,
and a single backup takes more than a day to complete.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github.com/0xB10C/github-metadata-backup"&gt;github-metadata-backup&lt;/a&gt; tool makes incremental metadata backups by
writing a state file after the first full backup and then only re-requests the
changed issues and pull requests on subsequent runs. While source code, release-
assets, and the wiki are out-of-scope, the backup contains everything displayed
in an issue or pull request on GitHub. The backup is written to disk as one JSON
file per issue or pull request. These JSON files can be tracked in Git and
periodically pushed to remote repositories (for example, hosted by GitHub - duh).&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/0xB10C/github-metadata-backup"&gt;github-metadata-backup&lt;/a&gt; tool is written in Rust and uses &lt;a href="https://github.com/XAMPPRocky"&gt;XAMPPRocky&amp;rsquo;s&lt;/a&gt;
&lt;a href="https://github.com/XAMPPRocky/octocrab"&gt;octocrab&lt;/a&gt; library. Next to the endpoints for issues and pull requests, the
GitHub REST API &lt;a href="https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28"&gt;timeline&lt;/a&gt; endpoint is used to fetch events in issues and
pull requests. I had to &lt;a href="https://github.com/XAMPPRocky/octocrab/pulls?q=author%3A0xB10C"&gt;add&lt;/a&gt; the timeline API endpoints to the &lt;a href="https://github.com/XAMPPRocky/octocrab"&gt;octocrab&lt;/a&gt;
library, as they weren&amp;rsquo;t implemented before. A GitHub access token is required
to run the backup tool, as unauthenticated API requests are heavily rate-limited
(60 requests per hour). The tool detects rate-limiting when authenticated and
waits until the token isn&amp;rsquo;t rate-limited anymore. The initial backup takes a
while as requests are frequently rate-limitied, but the following incremental
backups are pretty fast and normally only take a few seconds.&lt;/p&gt;
&lt;h3 id="mirroring-issues-and-pull-requests"&gt;Mirroring issues and pull requests&lt;/h3&gt;
&lt;p&gt;Having metadata backups of GitHub repositories is great. However, using
and testing the backups is required to ensure they are up-to-date and complete.
I&amp;rsquo;ve set up a script that transforms the JSON files into Markdown that the &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;
static-site generator can use to generate a read-only mirror of the repository
metadata. Using the &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; CSS framework, a GitHub-like look can be archived.
Reusing the GitHub IDs for issues and pull request comments allows linking from
the mirror directly to comments on GitHub. On the mirror, URLs to other issues
and pull requests in the same repository are rewritten to the mirror. This allows
to open linked issues and pull requests, even if GitHub is down or the repository
has been removed.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m backing up and mirroring the &lt;a href="https://github.com/bitcon/bitcoin"&gt;bitcoin/bitcoin&lt;/a&gt;, the &lt;a href="https://github.com/bitcoin/bips"&gt;bitcoin/bips&lt;/a&gt;,
the &lt;a href="https://github.com/bitcoin-core/secp256k1"&gt;bitcoin-core/secp256k1&lt;/a&gt;, and the &lt;a href="https://github.com/bitcoin-core/gui"&gt;bitcoin-core/gui&lt;/a&gt; repositories. Let me
know if I should consider adding other repositories, too. I&amp;rsquo;m focusing on GitHub
repositories with comments on issues and pull requests that are a vital
part of the Bitcoin and Bitcoin Core development history.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://mirror.b10c.me/bitcoin-bitcoin/' class="btn btn-outline-danger my-2" role="button"&gt;Bitcoin Core mirror&lt;/a&gt;
&lt;a href='https://mirror.b10c.me/bitcoin-bips/' class="btn btn-outline-danger my-2" role="button"&gt;Bitcoin BIPs mirror&lt;/a&gt;
&lt;a href='https://mirror.b10c.me/bitcoin-core-secp256k1/' class="btn btn-outline-danger my-2" role="button"&gt;secp256k1 mirror&lt;/a&gt;
&lt;a href='https://mirror.b10c.me/bitcoin-core-gui/' class="btn btn-outline-danger my-2" role="button"&gt;Bitcoin Core GUI mirror&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also offering compressed archives of the backups for download. Feel free to
download backups occasionally and store them on one of your disks. The mirror is
also available via an onion service for the people who want or need to use it.
GitHub itself doesn&amp;rsquo;t offer an onion service to access its site.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://mirror.b10c.me/' class="btn btn-outline-danger my-2" role="button"&gt;Mirrors and backups&lt;/a&gt;
&lt;a href='http://e3y5vky4v7snefqyhbn6kcmyl5fo4cnk3a2irh2ttvwua46ww5ubl6qd.onion/' class="btn btn-outline-danger my-2" role="button"&gt;Mirrors and backups (Onion Service)&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;While I will host the backups and mirrors for a while, I&amp;rsquo;d welcome it if others
put up backups and mirrors, too. The backup tool can easily be run on low-power
hardware. The mirroring tool uses Hugo, which loads the complete JSON files into
memory before generating the static pages. Processing large repositories like
&lt;code&gt;bitcoin/bitcoin&lt;/code&gt; uses quite a bit of RAM. I&amp;rsquo;d be happy to help anyone trying
to set this up. There are also public &lt;a href="https://github.com/0xb10c/nix"&gt;Nix packages and NixOS modules&lt;/a&gt; I use.
This includes automatic runs via systemd-timers, a commit after each backup
run, and automated pushes to one or more git remotes. I am happy to share my
configuration if someone wants to run this on NixOS.&lt;/p&gt;
&lt;p&gt;The backups can also be used for data analysis and data mining&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Number of new
contributors, comments per contributor, busiest times, most-active contributor,
and so on. Also, the comments can be used as training data for a language model.
I won&amp;rsquo;t have the time to play around with the data for the next few months, but
let me know if you do something with the data.&lt;/p&gt;
&lt;p&gt;&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/021-github-backups-mirror/overview.png'
alt='Start page of the bitcoin/bitcoin mirror'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Start page of the bitcoin/bitcoin mirror&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/021-github-backups-mirror/pr-28053.png'
alt='Pull-request #28053 on the bitcoin/bitcoin mirror'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Pull-request #28053 on the bitcoin/bitcoin mirror&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/021-github-backups-mirror/pr-28053-2.png'
alt='Pull-request #28053 merge on the bitcoin/bitcoin mirror'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Pull-request #28053 merge on the bitcoin/bitcoin mirror&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/021-github-backups-mirror/issue-20227.png'
alt='Issue #20227 on the bitcoin/bitcoin mirror'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Issue #20227 on the bitcoin/bitcoin mirror&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Reminds me of &lt;a href="https://youtu.be/bYviBstTUwo?t=877"&gt;SpiegelMining – Reverse Engineering von Spiegel-Online (33c3) - english translation&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Grant from Human Rights Foundation</title><link>https://b10c.me/funding/2023-hrf-grant/</link><pubDate>Tue, 09 May 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2023-hrf-grant/</guid><description>&lt;p&gt;The &lt;a href="https://hrf.org"&gt;Human Rights Foundation&lt;/a&gt; supported my work on Bitcoin with a developer grant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://x.com/gladstein/status/1655939990029996035"&gt;Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
"[Grant to @0xB10C] for their work on Bitcoin Core tracepoints, P2P monitoring, fork observer, mining pool observer, and Bitcoin data.
&lt;br&gt;&lt;br&gt;
Funding supports 0xB10C's efforts to monitor the Bitcoin network for anomalies, improving network security and resiliency 🦾"
&lt;/span&gt;
&lt;/blockquote&gt;</description></item><item><title>LinkingLion: An entity linking Bitcoin transactions to IPs?</title><link>https://b10c.me/observations/06-linkinglion/</link><pubDate>Tue, 28 Mar 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/06-linkinglion/</guid><description>&lt;p&gt;This post describes and discusses the behavior of an entity I call LinkingLion.
The entity opens connections to many Bitcoin nodes using four IP address ranges
and listens to transaction announcements. This might allow the entity to link
newly broadcast transactions to node IP addresses. The entity has been active in
some capacity since 2018 and is also active on the Monero network using the same
IP address ranges. The entity might be a blockchain analysis company collecting
data to improve its products.&lt;/p&gt;
&lt;p&gt;I previously observed an entity making multiple, short-lived connections per
second to many nodes on the Bitcoin P2P network. I called this entity an
&amp;ldquo;Inbound Connection Flooder&amp;rdquo; and wrote about my initial observation in this
&lt;a href="https://b10c.me/observations/05-inbound-connection-flooder-down/"&gt;post&lt;/a&gt;. However, after closer inspection of the entity&amp;rsquo;s behavior, I think these
short-lived connections are only a symptom of the primary goal. I suspect
this entity is likely tracking transaction propagation to attempt to determine
which node broadcasts which transaction to link transactions to IP addresses.&lt;/p&gt;
&lt;p&gt;The entity uses IP addresses from three IPv4 /24 ranges and one IPv6 /32
range to connect to listening nodes on the Bitcoin network. These IP address
ranges are all &lt;a href="https://archive.is/yMjSl"&gt;announced&lt;/a&gt; by AS54098, LionLink Networks. However, the ranges
belong to different companies based on ARIN and RIPE registry information.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;162.218.65.0/24&lt;/code&gt;: Fork Networking, LLC (forked.net) - &lt;a href="https://search.arin.net/rdap/?query=162.218.65.0%2F24"&gt;ARIN Whois&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;209.222.252.0/24&lt;/code&gt;: Castle VPN, LLC (castlevpn.com) - &lt;a href="https://search.arin.net/rdap/?query=209.222.252.0%2F24"&gt;ARIN Whois&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;91.198.115.0/24&lt;/code&gt;: Linama UAB (?) - &lt;a href="https://apps.db.ripe.net/db-web-ui/query?searchtext=91.198.115.0%2F24"&gt;RIPE Whois&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2604:d500::/32&lt;/code&gt;: Data Canopy (datacanopy.com) - &lt;a href="https://search.arin.net/rdap/?query=2604%3Ad500%3A%3A%2F32"&gt;ARIN Whois&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fork Networking and Castle VPN are US-based companies owned by the same person.
Fork Networking offers hosting and Colocation services, while Castle VPN is a
VPN provider. Linama UAB is a Lithuanian company with no web presence. Data
Canopy is a US-based company offering cloud and colocation data centers.
Since the connections from these IP ranges share very similar behavior, I
assume they are controlled or rented by the same entity. I&amp;rsquo;m calling the
entity &amp;ldquo;LinkingLion&amp;rdquo; as the AS &lt;em&gt;LionLink Networks&lt;/em&gt; is the common factor
for these IPs, and I assume the entity is trying to &lt;em&gt;link&lt;/em&gt; transactions to IP
addresses.&lt;/p&gt;
&lt;h2 id="behavior"&gt;Behavior&lt;/h2&gt;
&lt;p&gt;To analyze the behavior of LinkingLion, I recorded the network traffic between
my node and the entity&amp;rsquo;s IP ranges for about five days in the first half of
March 2023. In this timeframe, about 200.000 connections were opened to my node
from the entity. In the following section, I&amp;rsquo;ll walk through the observed
behavior.&lt;/p&gt;
&lt;h5 id="connection-establishment-and-handshake"&gt;Connection establishment and handshake&lt;/h5&gt;
&lt;p&gt;Out of the four IP ranges, the entity uses the following 812 addresses
(&lt;a href="https://b10c.me/data/observations/06-linkinglion/linkinglion-ip-addresses.txt"&gt;list&lt;/a&gt;) to open TCP connections to many listening Bitcoin nodes
on the network:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;162.218.65.11&lt;/code&gt; - &lt;code&gt;162.218.65.254&lt;/code&gt; (244 addresses)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;209.222.252.2&lt;/code&gt; - &lt;code&gt;209.222.252.254&lt;/code&gt; (253 addresses)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;91.198.115.3&lt;/code&gt; - &lt;code&gt;91.198.115.62&lt;/code&gt; (60 addresses) + &lt;code&gt;91.198.115.114&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2604:d500:4:1::2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2604:d500:4:1::3:2&lt;/code&gt; - &lt;code&gt;2604:d500:4:1::3:fe&lt;/code&gt; (253 addresses)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It uses the full range of ephemeral ports (1024-65535), which deviates from the
default behavior of many operating systems (most use a smaller subset). It can
be observed that the same IP address repeatedly connects, in some cases more
than 50 times, before the entity switches to another IP address in the same
address range.&lt;/p&gt;
&lt;p&gt;The entity establishes a TCP connection to our Bitcoin node and starts the
version handshake by sending a &lt;code&gt;version&lt;/code&gt; message. The version
messages have obscure user agents like, for example,
&lt;code&gt;/bitcoinj:0.14.3/Bitcoin Wallet:4.72/&lt;/code&gt;, &lt;code&gt;/Classic:1.3.4(EB8)/&lt;/code&gt;, or
&lt;code&gt;/Satoshi:0.13.2/&lt;/code&gt;. In total, &lt;a href="https://b10c.me/data/observations/06-linkinglion/linkinglion-user-agents.txt"&gt;118&lt;/a&gt; different user agents
are used. Nearly all of these appear in version messages with the same frequency,
which indicates that the user agents are picked from a list and are likely fake.
The entity uses 0 as the nonce for all connections and sets the transaction
relay flag to receive information about new transactions we know.&lt;/p&gt;
&lt;p&gt;The block height sent in the version message does not match the block height
known to the Bitcoin network. About 98% of the connections increment the block
height precisely every 10 minutes. Since the average time between blocks has been
less than 10 minutes over the last few months, the entity’s height lags behind
the network’s best height. In the observed connections, two different height
configurations can be identified as lagging by about 700 and 2100 blocks. I
estimated that the entity&amp;rsquo;s and the network&amp;rsquo;s height for the connections lagging
by about 700 blocks matched in late Q4 2022 or early Q1 2023, and the height
for connections lagging by 2100 matched in Q3 2022. I assume this is the time
the height was last configured. For about 2% of the connections, the height is
always set to block 658501. These connections all originate from &lt;code&gt;2604:d500:4:1::2&lt;/code&gt;,
&lt;code&gt;91.198.115.114&lt;/code&gt;, or &lt;code&gt;162.218.65.219&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My node responds with a &lt;code&gt;version&lt;/code&gt; and a &lt;code&gt;verack&lt;/code&gt; message acknowledging
that it understood the entity&amp;rsquo;s &lt;code&gt;version&lt;/code&gt; message. At this point, the entity is
expected to respond with a &lt;code&gt;verack&lt;/code&gt; to complete the handshake. However, the entity
closes about 82% of connections without sending a &lt;code&gt;verack&lt;/code&gt; message. These
connections are short-lived, with a connection duration of only a few seconds.
All IPv4 addresses besides &lt;code&gt;209.222.252.2&lt;/code&gt; open these connections. However, only
the IPv6 address &lt;code&gt;2604:d500:4:1::2&lt;/code&gt; opens short-lived connections, while the
other IPv6 addresses don&amp;rsquo;t.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/06-linkinglion/sequence-version-handshake.png'
alt='Sequence diagramm of the communication during the version handshake'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Opening a short-lived connection and closing it right after receiving the
&lt;code&gt;version&lt;/code&gt; message is typical when checking if a node is reachable on a given
address. The entity also learns metadata like which network services the node
offers, what version the node has, and what height it considers the blockchain
to be.&lt;/p&gt;
&lt;h5 id="communication"&gt;Communication&lt;/h5&gt;
&lt;p&gt;The remaining 18% of the opened connections receive a &lt;code&gt;verack&lt;/code&gt; and stay open
longer. After the handshake, a Bitcoin Core node sends a &lt;code&gt;sendcmpct&lt;/code&gt;
message indicating support for Compact Block Relay, a &lt;code&gt;ping&lt;/code&gt; message, a
&lt;code&gt;feefilter&lt;/code&gt; message with the minimum feerate we&amp;rsquo;re interested in, and a
&lt;code&gt;getheaders&lt;/code&gt; message requesting new headers the peer might know. The entity
responds with a &lt;code&gt;pong&lt;/code&gt; message and continues to respond for the duration of
the connection. It never initiates a &lt;code&gt;ping&lt;/code&gt; itself.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/06-linkinglion/sequence-after-verack.png'
alt='Sequence diagramm of the communication after the version handshake'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;From here on, two different behaviors can be observed. Either the entity listens
for &lt;code&gt;inv&lt;/code&gt; messages from us for up to 150 seconds (2 minutes and 30 seconds), or
it sends us a &lt;code&gt;getaddr&lt;/code&gt; and listens for &lt;code&gt;inv&lt;/code&gt; and &lt;code&gt;addr&lt;/code&gt; messages from us for
up to 600 seconds (10 minutes) before closing the connection. We send 15 inv
messages on average to the entity during the shorter, inv-only connections.
During the longer &lt;code&gt;inv&lt;/code&gt;-and-&lt;code&gt;addr&lt;/code&gt; connections, we send an average of six
&lt;code&gt;addr&lt;/code&gt;-messages and 104 &lt;code&gt;inv&lt;/code&gt;-messages. In the Bitcoin protocol, &lt;code&gt;inv&lt;/code&gt;
(inventory) messages are announcements that new blocks or transactions are
available. Upon receiving an &lt;code&gt;inv&lt;/code&gt;, a node might request the block or
transaction if it doesn&amp;rsquo;t know about it yet. The entity never requests blocks
or transactions.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;inv&lt;/code&gt;-only connection duration is similar for the three IPv4 address ranges.
Many connections are closed after either 90 seconds or 150 seconds. The
connections from the 253 addresses in the &lt;code&gt;2604:d500:4:1::3&lt;/code&gt; IPv6 range are
primarily closed after 150 seconds, while some are closed earlier, between 90
and 150 seconds. The connections from &lt;code&gt;2604:d500:4:1::2&lt;/code&gt; are closed nearly
uniformly between 0 and 90 seconds. Generally, there are no special IP addresses
used only for longer or shorter connections. The only outliner is &lt;code&gt;209.222.252.2&lt;/code&gt;,
which only makes the longer 150-second connections. The IP &lt;code&gt;162.218.65.219&lt;/code&gt; is
notable for making twice the number of connections than the other IPs in the
same IP range. The &lt;code&gt;inv&lt;/code&gt;-and-&lt;code&gt;addr&lt;/code&gt; connections request addresses with a
&lt;code&gt;getaddr&lt;/code&gt; message and only stem from &lt;code&gt;2604:d500:4:1::2&lt;/code&gt; and &lt;code&gt;91.198.115.114&lt;/code&gt;.
These are closed just after being open for 600 seconds.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/06-linkinglion/histogram-connection-duration-by-ip-range.png'
alt='Connection duration per IP range, stacked'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h5 id="mass-inbound-eviction"&gt;Mass inbound-eviction&lt;/h5&gt;
&lt;p&gt;A Bitcoin Core node has a limited number of inbound connection slots. A new
inbound connection might evict an existing one when all slots are full. Some
peers are protected from being evicted, for example, peers that send us blocks
or transactions we didn&amp;rsquo;t know about. Bitcoin Core&amp;rsquo;s eviction logic might choose
to evict a peer from the network group with the most connections out of the
unprotected peers. Bitcoin Core calculates network groups based on the /16
subnet for IPv4 and /32 subnet for IPv6.&lt;/p&gt;
&lt;p&gt;LinkingLion often has multiple open connections to a node simultaneously. Once
the inbound connection slots are full, a new inbound connection might cause one
of the connections by the entity to be evicted. The entity reacts by opening
another connection to the node, causing yet another connection to be evicted. I
described this as &amp;ldquo;Inbound Connection Flooder&amp;rdquo; due to the high frequency of
multiple hundred connections per minute. What I &lt;a href="https://b10c.me/observations/05-inbound-connection-flooder-down/"&gt;described&lt;/a&gt; only happens
when a node&amp;rsquo;s inbound connection slots are full.&lt;/p&gt;
&lt;h5 id="other-behavior"&gt;Other behavior&lt;/h5&gt;
&lt;p&gt;As reported in this &lt;a href="https://github.com/monero-project/monero/issues/8520"&gt;monero issue&lt;/a&gt;, the same IP ranges also open
connections to nodes on the Monero network. One user &lt;a href="https://github.com/monero-project/monero/issues/8520#issuecomment-1303251723"&gt;reports&lt;/a&gt;
that the entity also uses IP addresses like &lt;code&gt;91.198.115.74&lt;/code&gt;, while I only
observed connections using the IP addresses &lt;code&gt;91.198.115.3&lt;/code&gt; to &lt;code&gt;91.198.115.62&lt;/code&gt;.
The IP address ranges the entity uses have all been added to an IP
&lt;a href="https://gui.xmr.pm/files/block.txt"&gt;block list&lt;/a&gt; for Monero nodes.&lt;/p&gt;
&lt;!-- https://archive.ph/XDvCp --&gt;
&lt;p&gt;I&amp;rsquo;ve only seen connections from the &lt;code&gt;162.218.65.0/24&lt;/code&gt; IP range starting at
&lt;code&gt;162.218.65.11&lt;/code&gt;. However, there are publicly accessible logs, for
example, &lt;a href="https://archive.ph/q7k4W"&gt;[1]&lt;/a&gt;, &lt;a href="https://archive.ph/1sfcu"&gt;[2]&lt;/a&gt;, and &lt;a href="https://archive.ph/fU1yz#selection-49859.0-49879.14"&gt;[3]&lt;/a&gt; from Summer 2021 showing
requests to web servers from &lt;code&gt;162.218.65.10&lt;/code&gt; with a &lt;code&gt;Java/1.8.0_292&lt;/code&gt; user
agent. It&amp;rsquo;s unclear if these requests are related to the entity.&lt;/p&gt;
&lt;h2 id="discussion"&gt;Discussion&lt;/h2&gt;
&lt;p&gt;In the following section I discuss questions that came up after making the above
observations.&lt;/p&gt;
&lt;h5 id="are-the-connections-from-the-same-legal-entity"&gt;Are the connections from the same legal entity?&lt;/h5&gt;
&lt;p&gt;It&amp;rsquo;s unclear if the described &lt;em&gt;LinkingLion&lt;/em&gt; entity is a single entity or a group
of legal entities. The connections share patterns across the different IP
address ranges. For example, the connection durations for the &lt;code&gt;inv&lt;/code&gt;-only
and &lt;code&gt;inv&lt;/code&gt;-and-&lt;code&gt;addr&lt;/code&gt; connection types are similar across the IP address ranges.
Additionally, the IP address ranges all use the same fake user agents. While
this indicates that the same or similar software is used to open connections
through the same IP address ranges, it does not confirm that only one legal
entity is behind these connections. Furthermore, the three different height
configurations sent via the version message (static at height 658501, lagging by
about 2100 and 700 blocks) could indicate three different configurations or
versions of the software run by either one or multiple entities.&lt;/p&gt;
&lt;h5 id="are-the-connections-opened-through-a-vpn-service"&gt;Are the connections opened through a VPN service?&lt;/h5&gt;
&lt;p&gt;Based on ARIN registry information, the &lt;code&gt;209.222.252.0/24&lt;/code&gt; IP range belongs to
a company called CastleVPN. This could indicate that the connections are opened
through a VPN service. The other IP ranges could also be used as VPN endpoints,
which would explain why multiple software configurations share the same IP
addresses. However, this theory remains unconfirmed for now.&lt;/p&gt;
&lt;h5 id="what-information-does-the-entity-learn-about-a-node-it-targets"&gt;What information does the entity learn about a node it targets?&lt;/h5&gt;
&lt;p&gt;The information the entity learns from a node can be categorized into
&lt;em&gt;metadata&lt;/em&gt;, &lt;em&gt;inventory&lt;/em&gt;, and &lt;em&gt;addresses&lt;/em&gt;. All connections learn about node
metadata, which includes if and when a node is reachable or unreachable, which
software version runs on this specific node and when it upgrades, which
block height it considers the best and when it changes, and which services the
node offers. For example, if the node is pruned or serves bloom or compact block
filters.&lt;/p&gt;
&lt;p&gt;Connections that complete the version handshake and stay connected
learn about our node&amp;rsquo;s &lt;em&gt;inventory&lt;/em&gt;, like transactions and blocks. The timing
information, i.e., when a node announces its new inventory, is especially
relevant. The entity is likely to first learns about our new wallet transaction
from us. As the entity is connected to many listening nodes, it can use that information to link broadcast transactions to IP addresses.&lt;/p&gt;
&lt;p&gt;About 2% of the LinkingLion connections also ask our node to send
it the network addresses of other nodes on the network. The
entity likely uses these to find new targets to connect to and to keep
connections to all possible nodes open. There are known ways of trying to infer
the network topology, for example, how many connections a node has or who its
peers are, based on address propagation. Based on the small number of
connections that request and learn about other network addresses, this doesn&amp;rsquo;t
seem to be the goal of the &lt;code&gt;addr&lt;/code&gt; messages here. Though, it is also possible to
learn about the network topology by tracking transaction relay.&lt;/p&gt;
&lt;p&gt;However, why does the entity open multiple short-lived connections from multiple
IP ranges to a single node? Similar information could be extracted with less
effort and without opening and closing connections frequently. This would
have avoided much of the noise that caused me to look at this in detail.&lt;/p&gt;
&lt;h5 id="how-long-has-the-entity-been-active-for"&gt;How long has the entity been active for?&lt;/h5&gt;
&lt;p&gt;I personally first observed the entity in the Summer of 2022. However, the
entity has been active for longer. In August 2020, Bitcoin Core developer
&lt;a href="https://github.com/jonatack"&gt;@jonatack&lt;/a&gt; &lt;a href="https://github.com/bitcoin/bitcoin/pull/19643#issuecomment-671093420"&gt;posted a review comment&lt;/a&gt; on GitHub, which
included the peers currently connected to his node. Four inbound connections
from &lt;code&gt;2604:d500:4:1::2&lt;/code&gt; with fake user agents are visible. Similarly, a
screenshot in Bitcoin Core PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/18402#issue-585703721"&gt;#18402: gui: display mapped AS in peers info
window&lt;/a&gt; from March 2020 shows a connection from the same IP address
as peer 43.&lt;/p&gt;
&lt;!-- https://archive.ph/YFDYk --&gt;
&lt;!-- https://archive.ph/Hijfe --&gt;
&lt;p&gt;On an IP address banlist previously maintained by Greg Maxwell, now only accessible
via the Way Back Machine, the IP ranges &lt;code&gt;162.218.65.0/24&lt;/code&gt; and &lt;code&gt;2604:d500:4:1::2/128&lt;/code&gt;
can be found. They first appeared on the &lt;a href="https://web.archive.org/web/20180922235455/https://people.xiph.org/~greg/banlist.cli.txt"&gt;archived list in March 2019&lt;/a&gt;
and weren&amp;rsquo;t present in September 2018. The other two IP ranges are not on the
list. However, the IP range &lt;code&gt;23.92.36.0/24&lt;/code&gt;, also announced by AS54098 LionLink
Networks, can be found there since September 2018. There are &lt;code&gt;#bitcoin-core-dev&lt;/code&gt;
IRC logs from &lt;a href="https://www.erisian.com.au/bitcoin-core-dev/log-2018-02-06.html"&gt;February 18, 2018&lt;/a&gt;, discussing this IP range with
multiple users mentioning that they have multiple connections from that IP range.
A &lt;a href="https://github.com/bitcoin-core/gui/pull/298#issuecomment-828585637"&gt;screenshot&lt;/a&gt; shows two inbound connections (id 214 and 246)
from this IP range with the user agents &lt;code&gt;/Satoshi:0.10.2/&lt;/code&gt; and &lt;code&gt;/bitcoinj:0.14.3/&lt;/code&gt;.
It seems the entity has been active since early 2018 in some capacity. However,
it&amp;rsquo;s unknown whether the entity was active the whole time. The lagging block
heights, presumably set in Q3 2022 and Q1 2023, indicate that the entity is
still, at least to some degree, maintaining the data collection.&lt;/p&gt;
&lt;!--
https://web.archive.org/web/20211207180132/https://www.erisian.com.au/bitcoin-core-dev/log-2018-02-06.html
--&gt;
&lt;!--
additional discussion:
2019-03-18T22:10:26 &lt;luke-jr&gt; aside: 162.218.65.0/24 is actually all attackers?
https://www.erisian.com.au/bitcoin-core-dev/log-2019-03-18.html
https://archive.ph/HEKmH
--&gt;
&lt;h5 id="who-is-the-entity"&gt;Who is the entity?&lt;/h5&gt;
&lt;p&gt;Most Bitcoin P2P anomalies originate from individuals playing around with the
open network, companies with profit motives, for example, selling data to other
companies and law enforcement, or by (academic) researchers. In this case, it
seems unlikely that an individual would sustain this over multiple years. The
IP address ranges and servers cost money. An academic experiment is usually
shorter, too, as papers eventually need to be published. Academic researchers
might not use fake user agents. It makes sense for a company to pay for IP
address ranges and servers if they can sell the collected data or enhance an
existing product. This could be a company doing blockchain analysis.&lt;/p&gt;
&lt;h5 id="what-are-possible-preventions-and-solutions"&gt;What are possible preventions and solutions?&lt;/h5&gt;
&lt;p&gt;A short-term prevention might be to manually ban the IP address ranges used by
the entity from making inbound connections to nodes. I&amp;rsquo;ve published a
transparent and Open Source &lt;a href="https://github.com/0xB10C/banlist"&gt;banlist&lt;/a&gt; with the first entry being this
entity. Node operators that want to protect against the entity making
connections to their node can use this banlist. However, it&amp;rsquo;s important to
note that this banlist is entirely optional and centralized. Another
possibility is to contact the abuse contacts of the IP range owners
or AS54098 LionLink Networking.&lt;/p&gt;
&lt;p&gt;Both of these methods, however, don&amp;rsquo;t solve the root problem. The entity can
easily switch to new IP ranges or route traffic through a different AS. The root
problem is that transactions can to be linked to IP addresses. Fixing this
requires changes in the initial transaction broadcast and transaction rebroadcast
logic on the Bitcoin network and in Bitcoin Core. Transactions are transmitted
to peers with independent, exponential delays. An entity opening multiple
concurrent &lt;code&gt;inv&lt;/code&gt;-listening connections to a node can link transactions to the node&amp;rsquo;s IP address with a high success rate. A solution might be &lt;a href="https://bitcoinops.org/en/topics/dandelion/"&gt;Dandelion&lt;/a&gt;
(in particular, &lt;a href="https://dl.acm.org/doi/pdf/10.1145/3292040.3219620"&gt;Dandelion++&lt;/a&gt; or some modification of it), where transactions are
first transmitted to another node, which then broadcasts it. Dandelion++ is
beeing used in Monero since 2020. An &lt;a href="https://github.com/bitcoin/bitcoin/pull/13947#issuecomment-513858189"&gt;implementation attempt&lt;/a&gt; in Bitcoin Core
did not succeed primarily due to &lt;a href="https://bitcoin.stackexchange.com/questions/81503/what-is-the-tradeoff-between-privacy-and-implementation-complexity-of-dandelion"&gt;DoS and complexity concerns&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Transaction broadcast over privacy networks like Tor is not affected if done
correctly. A strategy is to broadcast a transaction to a node on the Tor network
using a fresh connection and then close the connection right after. Some Bitcoin
wallets with a strong focus on privacy implement similar features. It is
currently &lt;a href="https://github.com/bitcoin/bitcoin/issues/19042"&gt;not implemented&lt;/a&gt; in the Bitcoin Core wallet. Tools like
&lt;a href="https://github.com/laanwj/bitcoin-submittx"&gt;bitcoin-submittx&lt;/a&gt; might be helpful.&lt;/p&gt;
&lt;!-- https://github.com/bitcoin/bitcoin/pull/15668: p2p: Slightly more private initial tx relay --&gt;
&lt;!-- https://github.com/bitcoin/bitcoin/issues/19042: Tor-only transaction broadcast onlynet=onion alternative --&gt;
&lt;hr&gt;
&lt;p&gt;To summarize, an entity frequently opens connections from multiple IP ranges to
many nodes on the Bitcoin network. Some characteristics, like the fake
user agents and the block heights that increase precisely every 10 minutes,
confirm that the connections do not originate from some misconfigured Bitcoin
node but are custom clients. About 20% of the connections are used to listen to
transaction announcements, allowing the entity to link newly broadcast
transactions to IP addresses. The same IP addresses connect to nodes
on the Monero network too.&lt;/p&gt;
&lt;p&gt;Only a few details about the entity are known. The same IP ranges have been making
connections since 2018 in some capacity. It&amp;rsquo;s unclear if the IP ranges are maybe
endpoints of a VPN service. Similarly, if the entity is a single entity or a
group of legal entities is unknown. The behavior could indicate financial
motives. A possibility is a blockchain analysis company that wants to enrich
its product with additional data. A short-term solution might be a banlist or
reporting the entity&amp;rsquo;s behavior. Solving the root problem requires deeper
changes to the P2P logic in bitcoin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You can check if LinkingLion is connecting to your listening clearnet Bitcoin
Core node by grepping for the addresses in the &lt;code&gt;getpeerinfo&lt;/code&gt; output:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bitcoin-cli getpeerinfo | grep -E '162.218.65|209.222.240|91.198.115|2604:d500:4:1'&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Update 2024-03-28: One year after publishing this blog post, the LinkLion Networks AS issues a statement that they aren&amp;rsquo;t affiliated with LinkingLion besides announcing their IP addresses. On the same day, the LinkingLion activity significantly drops. I&amp;rsquo;ve written an update about it &lt;a href="https://b10c.me/blog/013-one-year-update-on-linkinglion/"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Grant from Spiral.xyz for 2023</title><link>https://b10c.me/funding/2023-sprial-grant/</link><pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2023-sprial-grant/</guid><description>&lt;p&gt;&lt;a href="https://spiral.xyz"&gt;Spiral.xyz&lt;/a&gt; supported my work on Bitcoin for the full year of 2023 with a developer grant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://x.com/spiralbtc/status/1636063897512824832"&gt;Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>2022 Review and 2023 Outlook</title><link>https://b10c.me/blog/011-2022-review-and-2023-outlook/</link><pubDate>Tue, 17 Jan 2023 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/011-2022-review-and-2023-outlook/</guid><description>&lt;p&gt;In this post, I revisit my plans and projects for 2022, and give an outlook for
2023. I plan to continue my current Bitcoin network monitoring efforts in 2023.
I briefly touch on potentially tight open-source developer funding in 2023.&lt;/p&gt;
&lt;h2 id="projects"&gt;Projects&lt;/h2&gt;
&lt;p&gt;In 2022, I pushed 582 commits to GitHub, opened 72 issues, proposed 68 pull
requests, &lt;a href="https://b10c.me/talks#p2p-monitoring"&gt;spoke&lt;/a&gt; &lt;a href="https://b10c.me/talks#monitoring-bitcoin-mining-pool-transaction-selection"&gt;at&lt;/a&gt; &lt;a href="https://b10c.me/talks#0xb10c-tracepoints-and-monitoring-the-bitcoin-network"&gt;four&lt;/a&gt; &lt;a href="https://btcazores.com/"&gt;(un)conferences&lt;/a&gt;, and appeared on one &lt;a href="https://b10c.me/talks#0xb10c-tracepoints-and-monitoring-the-bitcoin-network"&gt;podcast&lt;/a&gt;.
However, I don&amp;rsquo;t think these numbers express what has kept me busy in 2022. In
this post, I want to introduce some of my current projects, mention their status,
discuss recent progress, and lay out planned work for 2023.&lt;/p&gt;
&lt;p&gt;I was fortunate to be a &lt;a href="https://brink.dev/blog/2022/04/04/grantees/"&gt;Brink.dev grantee&lt;/a&gt; in 2022, which allowed me to focus
on my projects with very little administrational overhead and no need to worry
if and when I&amp;rsquo;ll receive my next grant payment. That&amp;rsquo;s how you should do
open-source developer grants! When Brink asked me to summarize the projects I
plan to work on in 2022, I replied with the following.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
[..] I plan to continue work on tracepoints in Bitcoin Core, including interface tests, review, and the addition of further tracepoints. Another goal is to launch a Signet with regular reorgs and a tool to explore reorgs visually. After observing flooding attacks on the Bitcoin P2P network in the summer of 2021, I set out to build a P2P network monitoring and anomaly detection tool in 2022. The goal is to feed the observations into Bitcoin and Bitcoin Core development, improving the P2P network robustness. I also plan to write up further Mempool Observations and start a similar series of blog posts for P2P network observations.
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;h3 id="tracepoints-for-bitcoin-core"&gt;Tracepoints for Bitcoin Core&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve been adding a tracing interface to Bitcoin Core. This allows for greater
observability of process internals during development and production use.
For example, we can attach to a tracepoint in the P2P network message processing
and learn in real-time about which peer sends us which network messages. This
opens up many new possibilities for debugging, monitoring, evaluating changes,
and detecting anomalies or other problems. We mainly use &lt;a href="https://ebpf.io"&gt;eBPF&lt;/a&gt; and
Userspace, Statically Defined Tracing (USDT) on Linux systems to hook into the
tracepoints. I&amp;rsquo;ve previously introduced this topic in my blog post &lt;a href="https://b10c.me/blog/008-bitcoin-core-usdt-support/"&gt;Userspace,
Statically Defined Tracing support for Bitcoin Core&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There has been much progress on the Bitcoin Core tracing framework in
2022. After we added the &lt;a href="https://github.com/bitcoin/bitcoin/pull/19866"&gt;tracing framework&lt;/a&gt; and followed up with the
&lt;a href="https://github.com/bitcoin/bitcoin/pull/22006"&gt;first tracepoints&lt;/a&gt; in 2021, I started 2022 by ensuring that the
&lt;a href="https://github.com/bitcoin/bitcoin/pull/23724"&gt;tracepoints are included in release builds&lt;/a&gt; and added &lt;a href="https://github.com/bitcoin/bitcoin/pull/24358"&gt;functional tests&lt;/a&gt; for
the tracepoints to provide a &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#semi-stable-api"&gt;semi-stable&lt;/a&gt; interface between releases. Getting
these tests to run in the &lt;a href="https://github.com/bitcoin/bitcoin/issues/24782"&gt;CI containers&lt;/a&gt; was more complex than expected, as the
CI&amp;rsquo;s container kernel headers often don&amp;rsquo;t match the host&amp;rsquo;s kernel headers. The
headers are needed to compile the eBPF bytecode when using the &lt;a href="https://github.com/iovisor/bcc"&gt;BPF compiler
collection&lt;/a&gt; (bcc). A solution was to &lt;a href="https://github.com/bitcoin/bitcoin/pull/25528"&gt;run the tests in a VM&lt;/a&gt;. I opened a PR to
add &lt;a href="https://github.com/bitcoin/bitcoin/pull/25832"&gt;tracepoints for opened, closed, evicted, and misbehaving connections&lt;/a&gt; and
helped with a PR for &lt;a href="https://github.com/bitcoin/bitcoin/pull/26531"&gt;mempool tracepoints&lt;/a&gt;. End of November 2022, I opened a PR
that would &lt;a href="https://github.com/bitcoin/bitcoin/pull/26593"&gt;eliminate the (minimal) overhead&lt;/a&gt; from tracepoint argument
computation for users not using the tracepoints. This would also allow expanding
the tracepoints to, for example, pass serialized transactions to tracing scripts.
Currently, we make sure to avoid potentially expensive computations just for the
tracepoints not to affect the majority of users not using the tracepoints.
While I think Bitcoin Core&amp;rsquo;s tracing framework is still experimental, it&amp;rsquo;s
moving in the right direction. I&amp;rsquo;m trying to keep this &lt;a href="https://github.com/users/0xB10C/projects/2/views/1"&gt;list of tracing-related&lt;/a&gt;
issues and pull requests up to date.&lt;/p&gt;
&lt;p&gt;I had the opportunity to &lt;a href="https://b10c.me/talks/#workshop-tracing-bitcoin-core-v23.0"&gt;hold&lt;/a&gt; a &lt;a href="https://b10c.me/projects/020-tracing-workshop-bpp22/"&gt;Tracing Bitcoin Core workshop&lt;/a&gt; at bitcoin++
2022 in Austin, Texas. Together with &lt;a href="https://jb55.com"&gt;jb55&lt;/a&gt;, who initially proposed a PR using
eBPF to trace Bitcoin Core, I did an impromptu talk on the tracing framework,
and its use cases at BTCAzores 2022.&lt;/p&gt;
&lt;p&gt;I plan to propose further tracepoints, for example, some which allow observing
changes to the IP address manager&amp;rsquo;s state. I&amp;rsquo;ve also looked into the kernels
&lt;a href="https://github.com/libbpf/libbpf"&gt;libbpf&lt;/a&gt;, which is an alternative to the current &lt;a href="https://github.com/iovisor/bpftrace"&gt;bpftrace&lt;/a&gt; and bcc tooling to
develop tracing scripts. There is the possibility to replace the current Python
and bcc tracepoint interface tests with concise and faster unit tests using
libbpf. Instead of spinning up a full node in the functional test, we could
directly test the function with the tracepoints. It might make sense to extract
the example tracing scripts from &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing"&gt;contrib/tracing/&lt;/a&gt; into a separate
&lt;code&gt;tracing-tools&lt;/code&gt; repo which allows us to, for example, add CI testing for the
scripts. Seemingly, with each new bpftrace version, one of the existing example
bpftrace scripts break. An option might be to drop the bpftrace example scripts
moving forward. I might do a video workshop or similar format that goes into
the details of eBPF, USDT, and the Bitcoin Core tracing framework to transfer
some of my knowledge about it.&lt;/p&gt;
&lt;h3 id="p2p-monitoring"&gt;P2P monitoring&lt;/h3&gt;
&lt;p&gt;As my most ambitious project of 2022, I&amp;rsquo;ve been working on a tool for passive P2P
monitoring using honey-pot nodes to detect anomalies and attacks on the Bitcoin
network. We want to be aware of these attacks, anomalies, potential bugs, and
other problems to react to them if needed. This is a reaction to the &lt;code&gt;addr&lt;/code&gt;
message spam during the Summer of 2021, which was only noticed by coincidence.
It would be good to have some monitoring for this.&lt;/p&gt;
&lt;p&gt;On a technical level, the monitoring utilizes the tracepoints mentioned above to
extract P2P events like receiving a message, a connection being opened, or a
misbehaving peer. These events are published into a message queue to which other
applications can subscribe. One example application is a Prometheus metrics
endpoint used in a Grafana dashboard. A demo version of this is running on
&lt;a href="https://public.peer.observer/"&gt;public.peer.observer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2023, I&amp;rsquo;m planning to deploy more instances of this tool to different
regions with different configurations: for example, a mix of networks (IPv4,
IPv6, Onion, I2P, &amp;hellip;), P2P features (block filter, bloom filter, P2Pv2, &amp;hellip;),
and bitcoind versions (releases, master, P2P PRs). I&amp;rsquo;m using NixOS for
declarative (infrastructure as code) and reproducible system deployments, which
reduces the infrastructure maintenance overhead to a minimum.&lt;/p&gt;
&lt;p&gt;I also plan to build out a P2P data archival tool. Next to disk-space efficient
storage of the P2P traffic, this should include functionality to read, replay,
and filter archival data. There&amp;rsquo;s also the idea of a &lt;em&gt;Grafana Live&lt;/em&gt; dashboard
with real-time metrics. The work on this ties in with proposing further
P2P-related tracepoints to Bitcoin Core.&lt;/p&gt;
&lt;p&gt;This project includes a &amp;ldquo;detect ➞ maybe analyze ➞ maybe react&amp;rdquo; part, which
happens ad-hoc when we notice an anomaly or problem. For example, I&amp;rsquo;ve looked
into and have written about an anomaly I&amp;rsquo;ve come across: &lt;a href="https://b10c.me/observations/05-inbound-connection-flooder-down/"&gt;Inbound connection
flooder down&lt;/a&gt;. As a reaction, there&amp;rsquo;s the possibility of publishing an
open-source banlist (similar to Greg Maxwell&amp;rsquo;s old banlist) with the IPs of the
inbound connection flooder. I have notes for a few anomalies that I haven&amp;rsquo;t
analyzed closely yet. I hope to get to them at some point in 2023.&lt;/p&gt;
&lt;h3 id="reorgs-on-signet-and-fork-observer"&gt;Reorgs on Signet and fork-observer&lt;/h3&gt;
&lt;p&gt;In 2021, I worked with a &lt;a href="https://summerofbitcoin.org"&gt;Summer of Bitcoin&lt;/a&gt; mentee on &amp;ldquo;Reorgs on Signet&amp;rdquo;.
Testnet can often be unreliable with &lt;a href="https://blog.lopp.net/the-block-storms-of-bitcoins-testnet/"&gt;block storms&lt;/a&gt; and frequent reorgs. With signet,
the idea is to have a network that can be reliably unreliable. A part of this is
to have, for example, automatic reorgs to test Bitcoin software behavior. Though,
something like this hasn&amp;rsquo;t been deployed yet.&lt;/p&gt;
&lt;p&gt;While I initially planned to rebase the old branch where we added functionality
for periodic reorgs to Bitcoin Core&amp;rsquo;s &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/signet"&gt;signet miner&lt;/a&gt; during the &lt;em&gt;Summer of
Bitcoin&lt;/em&gt;, I didn&amp;rsquo;t get to it as it didn&amp;rsquo;t seem like a priority. There hasn&amp;rsquo;t
been much interest from the broader community to use this. In many Bitcoin
software projects, the reorg behavior is probably either tested in a regtest
test suite or not tested at all due to large reorgs being very infrequent
nowadays on the Bitcoin mainnet. It&amp;rsquo;s unclear to me if a signet with scheduled
reorgs would currently see much use.&lt;/p&gt;
&lt;p&gt;One artifact from working on the signet miner script is a tool to visualize the
header tree with its forks and stale tips. This tool is now called fork-observer.
It collects chain tip information from Bitcoin Core and &lt;a href="https://github.com/btcsuite/btcd/pull/1918"&gt;btcd nodes&lt;/a&gt; via the
&lt;code&gt;getchaintips&lt;/code&gt; RPC and then builds a header tree. A single fork-observer instance
can handle multiple networks with multiple nodes.&lt;/p&gt;
&lt;p&gt;The fork-observer project is still work in progress. While a large chunk of it
is done, there are a few missing pieces, such as RSS feeds for stale and offline
nodes and some UI improvements. Next to the RSS feeds, bots posting events to,
e.g., nostr relays or Twitter are planned. Ideally, I want to combine the
tool&amp;rsquo;s release with a custom signet with reorgs to show its potential.&lt;/p&gt;
&lt;p&gt;The fork-observer tool only needs access to the &lt;code&gt;getchaintips&lt;/code&gt;,
&lt;code&gt;getblockheaders&lt;/code&gt;, and &lt;code&gt;getblockhash&lt;/code&gt; RPC calls. These can be whitelisted in
Bitcoin Core for a fork-observer-specific user. This allows connecting to many
external nodes via a private network like, for example, a WireGuard tunnel
to one fork-observer instance. Though, it&amp;rsquo;s certainly possible to run a
fork-observer instance on, for example, a &lt;a href="https://nixbitcoin.org/"&gt;nix-bitcoin&lt;/a&gt;, start9 Embassy, or
an Umbrel node connected to a single mainnet node.&lt;/p&gt;
&lt;p&gt;The tool is similar to BitMEX Research&amp;rsquo;s &lt;a href="https://forkmonitor.info/nodes/btc"&gt;forkmonitor.info&lt;/a&gt;. I think
fork-observer is easier to self-host and benefits from the header tree
visualization. However, forkmonitor.info has additional features, for
example, checking for supply mismatches and listing interesting lightning
transactions. It should be possible to add some of the header tree visualizations
to forkmonitor.info too, which I might attempt at some point.&lt;/p&gt;
&lt;p&gt;The tool also works well together with a bitcoin-data project I&amp;rsquo;ve started. The
goal is to collect historical stale block data with header information where
possible. I&amp;rsquo;m purposefully holding off on publishing this data until
a Bitcoin Core PR is merged and released. However, if you have a long-running
node (&amp;gt;5 years), feel free to get in touch with me if you want to provide data.
I&amp;rsquo;m using the fork-observer database to feed new data into the dataset.&lt;/p&gt;
&lt;p&gt;You can find a development version of fork-observer at &lt;a href="https://fork.observer"&gt;fork.observer&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="mininigpool-observer"&gt;mininigpool-observer&lt;/h3&gt;
&lt;p&gt;In 2021, I started working on a miningpool-observer project that provides
insights into mining pool transaction selection. This has been running on
&lt;a href="https://miningpool.observer"&gt;miningpool.observer&lt;/a&gt; since May 6th, 2021, when Marathon Pool mined their first
block, claiming not to include OFAC-sanctioned transactions. In &lt;a href="https://b10c.me/projects/016-miningpool-observer/"&gt;Observing
Bitcoin Mining Pools&lt;/a&gt;, I’ve written about the project in more detail.&lt;/p&gt;
&lt;p&gt;My work on miningpool-observer in 2022 has been mainly maintenance, smaller
features, and bug fixes. I don&amp;rsquo;t expect this to change in 2023. I&amp;rsquo;m thinking
about a few changes that allow for easier self-hosting and reduce the
maintenance burden going forward. There are a few dependencies, such as
the &lt;a href="https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses"&gt;ofac-sanctioned-digital-currency-addresses&lt;/a&gt; repository and the
&lt;a href="https://github.com/0xB10C/known-mining-pools"&gt;known-mining-pools&lt;/a&gt; repository, which I plan to work on further. We&amp;rsquo;re
currently &lt;a href="https://github.com/mempool/mining-pools/issues/25"&gt;combining the efforts&lt;/a&gt; of my &lt;a href="https://github.com/0xB10C/known-mining-pools"&gt;known-mining-pools&lt;/a&gt; and the
&lt;a href="https://github.com/mempool/mining-pools"&gt;mempool/mining-pools&lt;/a&gt; dataset. My goal is to have a high quallity dataset that
applications can use attribute blocks to mining pools like some block explorers
already do.&lt;/p&gt;
&lt;p&gt;Once Stratum v2 gains wider adoption, and job negotiation with the pools is
possible, it would be interesting to poke at some Stratum v2 mining pools with
non-standard transactions and transactions to sanctioned addresses. This could
be incorporated into the miningpool-observer at some later point, depending
on Stratum v2 adoption.&lt;/p&gt;
&lt;h3 id="bitcoin-data"&gt;bitcoin-data&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve set up the &lt;a href="https://github.com/bitcoin-data"&gt;@bitcoin-data&lt;/a&gt; organization on GitHub to archive some public
but ephemeral bitcoin data, which could be helpful for research purposes at some
point.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;a href="https://github.com/bitcoin-data/block-arrival-times"&gt;block-arrival-times&lt;/a&gt; repository that contains timestamps for when a
node first saw a block. These timestamps are ephemeral as the timestamps encoded
in the block headers almost always don&amp;rsquo;t match the time the block was broadcast
to the network. We can extract the block arrival times from, for example, Bitcoin
Core&amp;rsquo;s &lt;code&gt;debug.log&lt;/code&gt; files. For instance, I&amp;rsquo;ve needed block arrival times when
looking at which block height was active in &lt;a href="https://b10c.me/observations/01-locktime-stairs/"&gt;The stair-pattern in time-locked
Bitcoin transactions&lt;/a&gt;. There are other applications, too, like
measuring block propagation timings across the network. If you have an old
&lt;code&gt;debug.log&lt;/code&gt;, feel free to check out the &lt;a href="https://github.com/bitcoin-data/block-arrival-times#adding-data"&gt;adding data&lt;/a&gt; section. There are details
on how to parse and add data to the repository. Otherwise, just send it to me.&lt;/p&gt;
&lt;p&gt;Another repository is the &lt;a href="https://github.com/bitcoin-data/bitcoin-stats-archive"&gt;bitcoin-stats-archive&lt;/a&gt;. This currently fetches, and
archives [KIT DSN Bitcoin Monitoring] data and &lt;a href="https://luke.dashjr.org/programs/bitcoin/files/charts/historical.html"&gt;luke-jr&amp;rsquo;s Bitcoin node counts&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="other-smaller-projects-for-2023"&gt;Other smaller projects for 2023&lt;/h3&gt;
&lt;p&gt;I run a lot of my infrastructure, including many of the projects listed above,
on &lt;a href="https://nixos.org"&gt;NixOS&lt;/a&gt;. This allows me to build and package my projects reproducibly and
declaratively define what services run on which hosts. This has been very
reliable for me, and I&amp;rsquo;ll continue to use this setup. I plan to share the Nix
packages and modules for my projects, making it easier for others to self-host
them. I still have to figure out how to publish these and how to incorporate
this into my current infrastructure repository.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been running a mempool visualization website on &lt;a href="https://mempool.observer"&gt;mempool.observer&lt;/a&gt; for a
few years now, and I&amp;rsquo;ve recently set up a quick and dirty extension for full-RBF
monitoring on &lt;a href="https://fullrbf.mempool.observer"&gt;fullrbf.mempool.observer&lt;/a&gt;. I could extend the main site with
proper visualizations of, for example, RBF replacements, transaction packages in
the mempool, size- and time-based evictions, and more. Though, I&amp;rsquo;m not sure if
I&amp;rsquo;ll get to it. I&amp;rsquo;ve recently seen that there&amp;rsquo;s ongoing work to, for example,
incorporate [RBF replacement timelines] into the mempool.space project. My time
might be better spent helping to review this work rather than building something
similar from scratch.&lt;/p&gt;
&lt;p&gt;Over the past years, I&amp;rsquo;ve been able to use my monitoring tools and the data
I&amp;rsquo;ve collected to provide data-based feedback to proposed changes. For example,
I could &lt;a href="https://github.com/naumenkogs/txrelaysim/issues/8#issuecomment-903255752"&gt;reproduce&lt;/a&gt; the simulated bandwidth savings promised by Erlay, run
detailed IBD benchmarks for &lt;a href="https://github.com/bitcoin/bitcoin/pull/20827#issuecomment-1017603590"&gt;PR #20827&lt;/a&gt;, and &lt;a href="https://github.com/bitcoin/bitcoin/pull/26451#issuecomment-1313934927"&gt;visualize RBF replacement data&lt;/a&gt;
to provide insights into an RBF edge case. I plan to help out with similar
smaller projects wherever possible. Looking at network data can often reveal
possible improvements, as with &lt;a href="https://github.com/bitcoin/bitcoin/issues/26526"&gt;#26526&lt;/a&gt; and &lt;a href="https://github.com/bitcoin/bitcoin/issues/26527"&gt;#26527&lt;/a&gt;. There are also a few more
&lt;a href="https://b10c.me/observations/"&gt;Bitcoin Network Observations&lt;/a&gt; I&amp;rsquo;m planning to write.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve set up a technical, open-content Bitcoin developer blog as an experiment
in 2021. This experiment succeeds when people start (cross) posting their blog
posts to it on their own, and it fails once I don&amp;rsquo;t think it&amp;rsquo;s relevant anymore
and stop paying for the domain. For more information, see &lt;a href="https://bitcoin-dev.blog/"&gt;bitcoin-dev.blog&lt;/a&gt;.
While the blog didn’t see much activity in 2022, there’s a plan to revive it in
2023.&lt;/p&gt;
&lt;p&gt;I won’t be able to finish or even get to all projects I’ve listed here in 2023.
Similar to the last years, ad-hoc projects will pop up, and others will lose
their relevance for a while. I often finish the projects I’ve started but might
put them on ice for a while if I need time to focus on something I deem to be
more urgent.&lt;/p&gt;
&lt;h2 id="developer-funding-in-2023"&gt;Developer funding in 2023&lt;/h2&gt;
&lt;p&gt;Over the past years, there have often been more funding opportunities than
qualified&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; open-source Bitcoin developers. However, this has changed with big
donors affected by the current market situation. Among others, for example,
Gemini&amp;rsquo;s &lt;a href="https://www.superlunar.com/fund"&gt;superlunar fund&lt;/a&gt;, funding many important developers and projects,
recently shut down. There&amp;rsquo;s a chance that we&amp;rsquo;ll see a few grantors unable to
renew grants for existing developers resulting in an even tighter funding
situation with more qualified developers than funding. Though, at the moment,
there is still funding available. I recommend starting looking for alternatives
as early as possible if you think your grantor might not be able to renew your
grant.&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;ve found a replacement for my Brink grant with a Spiral developer
grant, which allows me to continue working on the projects mentioned above.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;0xB10C is a Bitcoin developer focusing on observability in the Bitcoin
network. He has built monitoring tools for mining pool transaction selection,
P2P network anomalies, transaction replacements, forks and reorgs, and
Bitcoin protocol statistics extracted from the blockchain. He aims to feed
insights gained from the monitoring tools and collected data into Bitcoin
development. Learn more about his projects on &lt;a href="https://b10c.me/projects"&gt;b10c.me/projects&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;However, there have been qualified developers unable to find funding in
some instances.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Inbound Connection Flooder Down (LinkingLion)</title><link>https://b10c.me/observations/05-inbound-connection-flooder-down/</link><pubDate>Wed, 16 Nov 2022 00:07:00 +0000</pubDate><guid>https://b10c.me/observations/05-inbound-connection-flooder-down/</guid><description>&lt;p&gt;Over the past few months, I&amp;rsquo;ve repeatedly observed very short-lived P2P
connections with fake user agents being made to my Bitcoin Core node in a high
succession. This morning around 7:00 am UTC, these abruptly stopped.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update 2023-03-28&lt;/strong&gt;: I&amp;rsquo;ve inspected the behavior of these connections in detail
and wrote about it in &lt;a href="https://b10c.me/observations/06-linkinglion/"&gt;LinkingLion: An entity linking Bitcoin transactions to IPs?&lt;/a&gt;. The frequent connections and evictions reported
here only happen when my inbound connection slots are full.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This morning, I noticed a sudden drop in inbound connections to one of my nodes
monitored by a P2P monitoring tool I&amp;rsquo;ve been working on. The number of inbound
connections dropped from 115 (all inbound connection slots used) to 85 in about
10 minutes. Drops in inbound connections could be caused by an incident at a
cloud service provider like AWS, Google, Hetzner, or similar. However, I didn&amp;rsquo;t
find any incident reports on relevant status pages.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/05-inbound-connection-flooder-down/connections-dropping.png'
alt='Showing a chart of number of open connection over time and Connections dropping around 7:00 am UTC.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Looking into it, I&amp;rsquo;ve also noticed a significant drop in new inbound connections
per minute from a relatively high number of 40 to a more normal level of about
three new inbound connections per minute. As my node&amp;rsquo;s connection slots are
limited to the default 125 connections and 10 are by default used as outgoing
connections, most of these 40 inbound connections per minute caused another
connection to be evicted&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Subsequently, the number of evicted and closed
connections dropped after 7:00 am UTC too. The number of inbound connections
per &lt;code&gt;net_group&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; reveals that the new connections primarily originated from
three &lt;code&gt;net_groups&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/05-inbound-connection-flooder-down/opened-connections-by-netgroup.png'
alt='Showing a chart with a dot per net_group per five minutes. There are three net_groups with more than 100 connections per 5 min.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;These newly opened inbound connections sent version message with rather uncommon
user agents (some listed below). The user agents all appear with about the same
frequency indicating that these are just randomly picked from a list. With
slightly more effort, the entity opening these connections could have picked out
of a more realistic distribution of currently used user agents. This would have
made them a bit less obvious. After 7:00 am UTC, the version messages mainly
contained the expected &lt;code&gt;/Satoshi:23.0.0/&lt;/code&gt;, &lt;code&gt;/Satoshi:22.0.0/&lt;/code&gt;, and an occasional
seed node scraper or bitnodes user agent.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/bitcoinj:0.14.3/Bitcoin Wallet:1.0.5/
/Satoshi:0.16.0/
/Satoshi:0.8.2.2/
/breadwallet:1.3.5/
/bitcoinj:0.14.3/Bitcoin Wallet:4.72/
/Satoshi:0.12.1(bitcore)/
/bitcoinj:0.14.4/Bitcoin Wallet:5.21-blackberry/
/Satoshi:0.10.0/
/Satoshi:0.15.0/
/bitcoinj:0.14.5/Bitcoin Wallet:5.38/
/Satoshi:0.11.1/
/breadwallet:0.6.2/
/bitcoinj:0.14.5/Bitcoin Wallet:5.35/
/bitcoinj:0.14.4/Bitcoin Wallet:5.22/
/Satoshi:0.14.2/UASF-Segwit:0.3(BIP148)/
/breadwallet:0.6.4/
/Satoshi:0.15.99/
/Satoshi:0.16.1/
/Satoshi:0.8.5/
/bitcoinj:0.13.3/MultiBitHD:0.4.1/
/Satoshi:0.12.1/
/Satoshi:0.14.2(UASF-SegWit-BIP148)/
/bitcoinj:0.14.4/Bitcoin Wallet:5.25/
/bitcoinj:0.14.3/Bitcoin Wallet:4.58.1-btcx/
/breadwallet:0.6.5/
/Bitcoin ABC:0.16.1(EB8.0)/
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;ve seen these connections by this entity as early as June this year. However,
it might have been active before. Back then, I briefly recorded the Bitcoin P2P
traffic between one of the IP addresses these connections were opened from and
my node. The IP addresses belong to a US-based VPN service called CastleVPN&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;
and a hosting provider called Fork Networking. Both are likely products by the
same company. It&amp;rsquo;s unclear to me if the connections are being passed through
the VPN or if then originate from a server hosted by Fork Networking. A typical
P2P message exchange between the entity (them) and my Bitcoin Core node (us)
looked as follows.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/05-inbound-connection-flooder-down/sequence-diagram.svg'
alt='A sequence diagram with the communication between my node and the entity.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the received version message, the protocol version of 70015 and the user
agent &lt;code&gt;/Satoshi:1.0.5/&lt;/code&gt; clearly mismatch. The nonce, typically used to detect if
we&amp;rsquo;re connecting to ourselves, is always 0, only the service bit for
&lt;code&gt;NODE_NETWORK&lt;/code&gt; is set, and the starting height is set ten blocks into the future.
I&amp;rsquo;ve observed both positive and negative deltas for the starting height. During
the communication, we don&amp;rsquo;t reveal a lot of information to the entity opening the
connection. However, by sending the &lt;code&gt;getheaders&lt;/code&gt; message, we inform the entity
about which headers we know and which block we consider as chain tip. I found it
interesting that they even respond to our ping. The connection stays open until
it reaches a timeout or we evict the peer.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know the goal of the entity opening the connections. They learn that
there&amp;rsquo;s a listening Bitcoin node behind this IP address. However, that&amp;rsquo;s not
information you&amp;rsquo;d need to query more than 40 times a minute. They also learn
about which chain tip we currently consider valid via the &lt;code&gt;getheaders&lt;/code&gt; message
we send them, which is interesting if you are someone monitoring the network for
reorgs. However, you would be fine with way fewer connections for this too.
Someone interested in monitoring block propagation would favor long-lived
connections to learn about the blocks by us notifying them.&lt;/p&gt;
&lt;p&gt;If your goal is to block inbound connection slots on listening Bitcoin Core
nodes, then you can reach this with this technique. My node&amp;rsquo;s 30 newly free
inbound connection slots have been filling up with (hopefully) good peers over
the day. Additionally, by constantly opening new inbound connections, you can
evict some good inbound connections before they can send you a block or
transaction that would protect them. Since the eviction logic also considers
other heuristics to protect our peers, an attacker can only trick us into
evicting only some of our peers.&lt;/p&gt;
&lt;p&gt;I wonder if the entity behind this is actually malicious. There is always the
possibility that anomalies stem from some misconfigured academic measurements.
The entity doesn&amp;rsquo;t seem too sophisticated given that it, for example, picks
uncommon user agents with the same frequency as commonly used user agents.&lt;/p&gt;
&lt;p&gt;A simple protection against this is banning these IP addresses from opening
connections with your Bitcoin Core node. There have been so-called banlists
containing known spy nodes, mass connectors, and other abusive nodes in the
past. While I don&amp;rsquo;t know about any currently maintained banlists, it should be
possible to create a new banlist based on data collected with my P2P monitoring
project.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to hear from other node operators if they observed a similar drop in
inbound connections this morning around 7:00 am UTC. You can also check what
peer ids are reported by &lt;code&gt;getpeerinfo&lt;/code&gt; RPC. My node has currently seen over a
million peers. Also, if you are interested in this topic, I&amp;rsquo;m currently
&lt;a href="https://github.com/bitcoin/bitcoin/pull/25832"&gt;proposing a change&lt;/a&gt; to Bitcoin
Core that adds the tracepoints I&amp;rsquo;m using for opened, closed, evicted, and
misbehaving connections. I&amp;rsquo;m always happy about more testing and review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2022-11-18:&lt;/strong&gt; I&amp;rsquo;ve observed &lt;a href="https://twitter.com/0xB10C/status/1593201457100378114"&gt;another&lt;/a&gt; drop in inbound connections at
my node. However, the inbound flooding seems to have started up again. Other
Bitcoin developers reported seeing many inbound connections being opened to
their node too. Some reported also seeing these connections from the same three
/24 IPv4 subnets owned by Fork Networking. Based on the leasing price of
&lt;a href="https://www.forked.net/ip-address-leasing"&gt;$256/m per /24 subnet&lt;/a&gt;, it can be
assumed that this costs the entity at least $768 per month not including server
hosting fees.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The Bitcoin Core connection eviction logic protects nodes that, for
example, provided useful information about blocks and transactions to us.
For more information about the eviction logic in Bitcoin Core, see, for
example, &lt;a href="https://bitcoincore.reviews/16756"&gt;https://bitcoincore.reviews/16756&lt;/a&gt; and
&lt;a href="https://bitcoincore.reviews/20477"&gt;https://bitcoincore.reviews/20477&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;In Bitcoin Core, a &lt;code&gt;net_group&lt;/code&gt; of an IPv4 address is currently defined
as the /16 subnet (first two tuples of an IPv4 address; netmask
255.255.0.0). This might be superseded with &lt;code&gt;asmap&lt;/code&gt; in the future.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;It seems their VPN is so secure that they don&amp;rsquo;t even see the need to use
TLS on their website&amp;hellip; &lt;a href="http://www.castlevpn.com/"&gt;http://www.castlevpn.com/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Are you a real Bitcoiner?</title><link>https://b10c.me/blog/010-are-you-real-bitcoiner/</link><pubDate>Sun, 18 Sep 2022 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/010-are-you-real-bitcoiner/</guid><description>&lt;p&gt;It&amp;rsquo;s well known that you are only a real Bitcoiner if you are a carnivore, eat
red meat at least three times a week but never consume seed oils, lift heavy
weights, are straight, white, unvaccinated, believe in the Christian god,
got into Bitcoin before 2015, are a maximalist, stack sats regularly, and
own at least 6.15 BTC and two or more guns with plenty of ammunition.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Say yes &lt;a href="https://twitter.com/hashtag/Bitcoin?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#Bitcoin&lt;/a&gt; Fundamentalism &lt;a href="https://t.co/DXwqH5JYsk"&gt;pic.twitter.com/DXwqH5JYsk&lt;/a&gt;&lt;/p&gt;&amp;mdash; Skyler Designer (@skyler_fs) &lt;a href="https://twitter.com/skyler_fs/status/1570969064750850048?ref_src=twsrc%5Etfw"&gt;September 17, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Obviously, that&amp;rsquo;s not the case. Anyone claiming this is a self-proclaimed
gatekeeper that hasn&amp;rsquo;t understood Bitcoin. There are no gates in Bitcoin.
On the technical side, anyone can join the Bitcoin network, and the other
peers don&amp;rsquo;t care what you bench, if or which god you believe in, and how
much bitcoin you own. On the social side, it&amp;rsquo;s similar - your dietary
preferences, religion, political affiliation, skin color, when you got
interested in Bitcoin, and your sexual orientation doesn&amp;rsquo;t matter.&lt;/p&gt;
&lt;p&gt;Bitcoin is for everyone. A Bitcoiner is someone interested in Bitcoin and
bitcoin as a tool, money, or technology. Someone who understood that Bitcoin
is open for everyone, even for people you don&amp;rsquo;t like. Everything else doesn&amp;rsquo;t
matter. Bitcoin doesn&amp;rsquo;t care. You are probably not a Bitcoiner if you don&amp;rsquo;t
get this. You are probably not a Bitcoiner if you tell others they are not a
real Bitcoiner because they like something you don&amp;rsquo;t like.&lt;/p&gt;
&lt;p&gt;If you consume only vegan products and are interested in Bitcoin or bitcoin,
you are probably a vegan Bitcoiner. Or, if you only eat meat, you are
probably a carnivore Bitcoiner. But don&amp;rsquo;t generalize and assume all Bitcoiners
must be vegan or, in the latter case, carnivores. Similarly, you can be a
Christian Bitcoiner, a gun-owner Bitcoiner, or an LGQBT Bitcoiner. Being a
Bitcoiner doesn&amp;rsquo;t depend on your other preferences and life decisions.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thanks @skyler_fs on twitter for the fitting, and provocative header image.&lt;/p&gt;</description></item><item><title>P2TR spending transactions missing from F2Pool and AntPool blocks (2021)</title><link>https://b10c.me/observations/04-p2tr-spends-not-mined/</link><pubDate>Wed, 24 Aug 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/observations/04-p2tr-spends-not-mined/</guid><description>&lt;p&gt;My &lt;a href="https://miningpool.observer/"&gt;miningpool-observer&lt;/a&gt; project aims to detect when mining pools don&amp;rsquo;t mine
transactions they could have mined. Right after taproot activation, it caught
that F2Pool and AntPool didn&amp;rsquo;t mine P2TR (Pay-to-Taproot) spending transactions.
This post is a write-up of this observation.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I&amp;rsquo;m documenting an observation from November 2021 here so I can link
to it. This is no longer an issue. I&amp;rsquo;ve also discussed this in
&lt;a href="https://b10c.me/projects/019-taproot-activation-monitoring/"&gt;Monitoring Taproot Activation&lt;/a&gt;, which might give a better overview of the
situation.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="during-taproot-activation"&gt;During Taproot Activation&lt;/h2&gt;
&lt;p&gt;The taproot soft-fork was activated with block 709632 mined on November 14th,
2021. Some Bitcoin developers had crafted P2TR spending transactions
containing, for example, &lt;code&gt;OP_RETURN&lt;/code&gt; outputs celebrating the soft-fork
activation. With block 709631 being mined, Bitcoin Core nodes began to accept
and relay these P2TR spending transactions. Developers broadcast their
transactions, hoping to make it into block 709632.&lt;/p&gt;
&lt;p&gt;F2Pool mined block 709632 about 18 minutes after 709631 was mined. This was
the first block with the taproot rules active. However, it didn&amp;rsquo;t contain
any purposefully crafted P2TR spending transaction, which should have been
included. While people were discussing what could have happened, AntPool mined
the two blocks 709633 and 709634. Both of which didn&amp;rsquo;t contain any P2TR spending
transactions either.&lt;/p&gt;
&lt;p&gt;Then, Foundry mined block 709635, which included all P2TR spending transactions
waiting in the mempool. Over the following blocks, other mining pools like, for
example, Poolin, SlushPool, BTC.com, and Luxor also included P2TR spending
transactions.&lt;/p&gt;
&lt;p&gt;However, it took a few days until F2Pool and AntPool mined their first P2TR
spending transactions. We only learned from F2Pool why they didn&amp;rsquo;t start mining
these transactions right after activation. We don&amp;rsquo;t know details about AntPools
problems but assume it was a similar issue. F2Pool had upgraded its nodes at
least a few weeks before activation. However, they were running Bitcoin Core
with custom patches. One of these patches disconnected peers, which reported a
version string starting with &lt;code&gt;/Satoshi:0.1&lt;/code&gt;. Only Bitcoin Core v0.21.1 and
v22.0&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; shipped with the taproot soft-fork logic. Both of these didn&amp;rsquo;t match
F2Pools version string filter and were disconnected. After fixing this, F2Pool
started mining P2TR spending transactions. AntPool might have fixed a similar
issue and started to include P2TR spends, too.&lt;/p&gt;
&lt;h2 id="missing-transactions"&gt;Missing transactions&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://miningpool.observer/"&gt;miningpool-observer&lt;/a&gt; project compares a just mined block to a block
template generated &lt;a href="https://miningpool.observer/faq#general-template"&gt;just before&lt;/a&gt; the block was found. The differences
between the two can show which transactions the pool left out and which were
included extra. Block templates and blocks often differ slightly as different
nodes know about other transactions. However, transactions not making it
into multiple blocks when they have been in multiple block templates could
indicate filtering or censorship by pools.&lt;/p&gt;
&lt;p&gt;This was the case with the P2TR spending transactions. They were included in
the block templates but not in the blocks by F2Pool and AntPool. The
&lt;a href="https://miningpool.observer/missing"&gt;miningpool.observer/missing&lt;/a&gt; page shows transactions missing from three or
more blocks. This is the case with F2Pools taproot activation block 709632,
and AntPools blocks 709633 and 709634. The following transactions were
included by Foundry in block 709635.&lt;/p&gt;
&lt;p&gt;For example, transaction &lt;a href="https://miningpool.observer/missing/0bf67b1f05326afbd613e11631a2b86466ac7e255499f6286e31b9d7d889cee7"&gt;&lt;code&gt;0bf67b..&lt;/code&gt;&lt;/a&gt; &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; was missing from these blocks. It
spent a 6490 sat P2TR output and created an OP_RETURN and a P2SH output. The
OP_RETURN output contains the message &amp;ldquo;&lt;code&gt;∞/21million. Thanks Satoshi! —BitGo&lt;/code&gt;&amp;rdquo;.
Similarly, the transaction &lt;a href="https://miningpool.observer/missing/905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530"&gt;&lt;code&gt;905ecd..&lt;/code&gt;&lt;/a&gt; &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; spent the first Taproot 2-of-2
multisig output and includes an OP_RETURN output with the message
&lt;code&gt;Thx Satoshi! ∞/21mil First Taproot multisig spend -BitGo&lt;/code&gt;. Andrew Chow&amp;rsquo;s
script path spend &lt;a href="https://miningpool.observer/missing/37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8"&gt;&lt;code&gt;37777d..&lt;/code&gt;&lt;/a&gt; &lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;, which ended up being the second P2TR
spend mined, was reported missing. Pieter Wuille&amp;rsquo;s vanity address taproot
party taptap transaction &lt;a href="https://miningpool.observer/missing/83c8e0289fecf93b5a284705396f5a652d9886cbd26236b0d647655ad8a37d82"&gt;&lt;code&gt;83c8e0..&lt;/code&gt;&lt;/a&gt; &lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;, too.&lt;/p&gt;
&lt;p&gt;This shows that the tool works when multiple pools do not include transactions.
This also shows that Bitcoin works as intended. If at least some pools are
mining P2TR spending transactions, the transactions will eventually be mined.&lt;/p&gt;
&lt;p&gt;More Taproot activation history is documented in &lt;a href="https://b10c.me/projects/019-taproot-activation-monitoring/"&gt;Monitoring Taproot Activation&lt;/a&gt;
and in this bitcoin-dev mailing list thread
&lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-November/019598.html"&gt;&amp;ldquo;Taproot activates - A time/block line&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Bitcoin Core decided to drop the zero in the beginning of the version
starting with version v0.22.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;txid &lt;code&gt;0bf67b1f05326afbd613e11631a2b86466ac7e255499f6286e31b9d7d889cee7&lt;/code&gt;,
&lt;a href="https://archive.ph/v9O73"&gt;miningpool.observer on archive.ph&lt;/a&gt;,
&lt;a href="https://blockstream.info/tx/0bf67b1f05326afbd613e11631a2b86466ac7e255499f6286e31b9d7d889cee7"&gt;blockstream.info&lt;/a&gt;,
&lt;a href="https://mempool.space/tx/0bf67b1f05326afbd613e11631a2b86466ac7e255499f6286e31b9d7d889cee7"&gt;mempool.space&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;txid &lt;code&gt;905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530&lt;/code&gt;,
&lt;a href="https://archive.ph/Uqsup"&gt;miningpool.observer on archive.ph&lt;/a&gt;,
&lt;a href="https://blockstream.info/tx/905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530"&gt;blockstream.info&lt;/a&gt;,
&lt;a href="https://mempool.space/tx/905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530"&gt;mempool.space&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;txid &lt;code&gt;37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8&lt;/code&gt;,
&lt;a href="https://archive.ph/Kq3fs"&gt;miningpool.observer on archive.ph&lt;/a&gt;,
&lt;a href="https://blockstream.info/tx/37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8"&gt;blockstream.info&lt;/a&gt;,
&lt;a href="https://mempool.space/tx/37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8"&gt;mempool.space&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;txid &lt;code&gt;83c8e0289fecf93b5a284705396f5a652d9886cbd26236b0d647655ad8a37d82&lt;/code&gt;,
&lt;a href="https://archive.ph/FXF8k"&gt;miningpool.observer on archive.ph&lt;/a&gt;,
&lt;a href="https://blockstream.info/tx/83c8e0289fecf93b5a284705396f5a652d9886cbd26236b0d647655ad8a37d82"&gt;blockstream.info&lt;/a&gt;,
&lt;a href="https://mempool.space/tx/83c8e0289fecf93b5a284705396f5a652d9886cbd26236b0d647655ad8a37d82"&gt;mempool.space&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>bitcoin++ workshop: Tracing Bitcoin Core v23.0</title><link>https://b10c.me/projects/020-tracing-workshop-bpp22/</link><pubDate>Wed, 08 Jun 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/020-tracing-workshop-bpp22/</guid><description>&lt;p&gt;These are the tasks of my &amp;ldquo;Tracing Bitcoin Core v23.0&amp;rdquo; workshop for
&lt;a href="https://www.btcplusplus.dev/"&gt;bitcoin++ 2022&lt;/a&gt;. They might not make much sense
on their own as participants used a pre-setup VPS. The workshop slides can be
found &lt;a href="https://b10c.me/talks/017-tracing-workshop-bpp.html"&gt;here&lt;/a&gt;. This &lt;a href="https://github.com/0xb10c/bitcoin/tree/2022-02-tracing-workshop"&gt;branch&lt;/a&gt; was used during the workshop.&lt;/p&gt;
&lt;h4 id="prerequisites"&gt;Prerequisites&lt;/h4&gt;
&lt;p&gt;You will only need an SSH client to participate in the workshop. I will provide
you with a pre-setup VPS to work on the tasks. I&amp;rsquo;ll send you the IP for
your VPS. However, I first need an SSH &lt;strong&gt;public key&lt;/strong&gt; from you. Please make
sure not to send the private key part of your SSH key! I&amp;rsquo;d recommend generating
a new, temporary key pair for this workshop. You should be able to run the
following command on Linux and macOS to generate a new ED25519 keypair.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ssh-keygen -t ed25519 -C &lt;span class="s2"&gt;&amp;#34;bpp22-workshop&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Generating public/private ed25519 key pair.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter file in which to save the key &lt;span class="o"&gt;(&lt;/span&gt;/home/your_user/.ssh/id_ed25519&lt;span class="o"&gt;)&lt;/span&gt;: /home/your_user/.ssh/id_bpp22workshop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for&lt;/span&gt; no passphrase&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter same passphrase again:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Your identification has been saved in /home/your_user/.ssh/id_bpp22workshop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Your public key has been saved in /home/your_user/.ssh/id_bpp22workshop.pub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;The key fingerprint is:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SHA256:FaK3FinGErPr1nTG63msKLoVSU9syTPpQSGGdsnpfvZM bpp22-workshop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;The keys randomart image is:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+--&lt;span class="o"&gt;[&lt;/span&gt;ED25519 256&lt;span class="o"&gt;]&lt;/span&gt;--+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ snip +
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+----&lt;span class="o"&gt;[&lt;/span&gt;SHA256&lt;span class="o"&gt;]&lt;/span&gt;-----+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Please send the &lt;code&gt;/home/your_user/.ssh/id_bpp22workshop.pub&lt;/code&gt; file to me via
e-mail, telegram, or similar. I&amp;rsquo;ll respond with your server name. Once the
VPSs are set up, I&amp;rsquo;ll publish the IPs below.&lt;/p&gt;
&lt;table class="table table-sm table-striped table-bordered"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;server name (BIP39 word)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IP&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;assigned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;satoshi&lt;/td&gt;
&lt;td&gt;XXX.X.X.X&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hedgehog&lt;/td&gt;
&lt;td&gt;X.XX.XXX.XX&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pottery&lt;/td&gt;
&lt;td&gt;X.XX.XX.XXX&lt;/td&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mango&lt;/td&gt;
&lt;td&gt;XX.XXX.XX.XXX&lt;/td&gt;
&lt;td&gt;SV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lock&lt;/td&gt;
&lt;td&gt;XX.XXX.XX.XXX&lt;/td&gt;
&lt;td&gt;Ja&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;senior&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XXX&lt;/td&gt;
&lt;td&gt;Ju&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;acid&lt;/td&gt;
&lt;td&gt;X.XX.XXX.XXX&lt;/td&gt;
&lt;td&gt;JD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rabbit&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XX&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vehicle&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XX&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;piano&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XX&lt;/td&gt;
&lt;td&gt;MS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;universe&lt;/td&gt;
&lt;td&gt;XX.XXX.XX.XXX&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;link&lt;/td&gt;
&lt;td&gt;XX.XX.X.XXX&lt;/td&gt;
&lt;td&gt;SP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gadget&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XXX&lt;/td&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pumpkin&lt;/td&gt;
&lt;td&gt;XX.XXX.XX.XX&lt;/td&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sunny&lt;/td&gt;
&lt;td&gt;XX.XXX.XXX.XX&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gorilla&lt;/td&gt;
&lt;td&gt;X.XX.XXX.XXX&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="task-0---login-and-get-familiar-with-the-system"&gt;Task 0 - Login and get familiar with the system&lt;/h4&gt;
&lt;p&gt;Once you&amp;rsquo;ve got your IP address, you should be able to SSH into the server via
the following command. You might need to specify the SSH private key file
(without the &lt;code&gt;.pub&lt;/code&gt; at the end).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ssh workshop@13.37.83.33 -i ~/.ssh/id_bpp22workshop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Additionally, you might need to set the correct permissions (only read+write
for your current user) on the SSH private key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; /home/your_user/.ssh/id_bpp22workshop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you&amp;rsquo;ve logged in to the system, it&amp;rsquo;s time to get familiar with it.
Feel free to navigate around, check out the specs and running processes with
&lt;code&gt;htop&lt;/code&gt;, and check the kernel and operating system information with &lt;code&gt;uname -a&lt;/code&gt;.
The kernel version is important for using a lot of the eBPF functionality.
We need at least a Linux kernel of version 4.8 to use hook into the tracepoints
in Bitcoin Core. The programs &lt;code&gt;vim&lt;/code&gt; and &lt;code&gt;nano&lt;/code&gt; are also installed.&lt;/p&gt;
&lt;p&gt;Note that you have password-less &lt;code&gt;sudo&lt;/code&gt; access on the system. We need a
privileged user to do BPF syscalls and to read from BPF maps. I expect you to
&lt;strong&gt;not&lt;/strong&gt; abuse this power (e.g. deleting system files). Treat this server as you
would treat a server you&amp;rsquo;ve rented. The server auto-shutdowns on excessive
resource usage. Additionally, your disk space is limited (please don&amp;rsquo;t try to
sync mainnet). I might not provision another server for you or restart your
instance. This would effectively end your participation in the workshop.&lt;/p&gt;
&lt;p&gt;For the following tasks, it could be helpful to take a look at the Bitcoin Core
tracing documentation and examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tracing documentation: &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md"&gt;doc/tracing.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tracing examples: &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing"&gt;contrib/tracing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Which Linux kernel version does the provided server have?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;How much memory does your server have?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Are you able to switch to the &lt;code&gt;root&lt;/code&gt; user with &lt;code&gt;sudo su&lt;/code&gt;?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-1---listing-available-tracepoints"&gt;Task 1 - Listing available tracepoints&lt;/h4&gt;
&lt;p&gt;There is a &lt;code&gt;bitcoin&lt;/code&gt; directory in your home directory. This contains a checkout
of the Bitcoin Core source code. I&amp;rsquo;ve also built Bitcoin Core
from source for you to save some time during the workshop. During the
&lt;code&gt;./configure&lt;/code&gt; step, it was detected that the SystemTap &lt;code&gt;sys/sdt.h&lt;/code&gt; header file
is present on the system. Bitcoin Core was built with tracepoints.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./configure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;..&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Options used to compile and link:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;multiprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with experimental syscall sandbox &lt;span class="nv"&gt;support&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with &lt;span class="nv"&gt;libs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt;..&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with &lt;span class="nv"&gt;zmq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with fuzz &lt;span class="nv"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with &lt;span class="nv"&gt;bench&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; use &lt;span class="nv"&gt;asm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; USDT &lt;span class="nv"&gt;tracing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes &lt;span class="c1"&gt;# &amp;lt;---- Tracepoints enabled!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;sanitizers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; debug &lt;span class="nv"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;..&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Your task is to list the available tracepoints in the &lt;code&gt;bitcoind&lt;/code&gt;
binary. The Bitcoin Core tracepoint documentation (linked above) contains three
methods to do so. Feel free to try them out. The &lt;code&gt;bitcoind&lt;/code&gt; binary is located
in &lt;code&gt;/home/workshop/bitcoin/src/bitcoind&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Try finding one or two tracepoints in the Bitcoin Core source code. I&amp;rsquo;d
recommend that you navigate to the &lt;code&gt;/home/workshop/bitcoin/src&lt;/code&gt; directory
and &lt;code&gt;grep&lt;/code&gt; for &lt;code&gt;TRACE&lt;/code&gt; in &lt;code&gt;*.cpp&lt;/code&gt; files from there (&lt;code&gt;grep TRACE *.cpp -R&lt;/code&gt;).
The ripgrep tool (&lt;code&gt;rg&lt;/code&gt;) is installed on the system too.&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Which tracepoints does the binary contain?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Which of the three methods shows you information about the arguments passed to the tracepoints?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Are all tracepoints documented in the tracing documentation?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Were you able to find a tracepoint in the Bitcoin Core source code?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-2---running-p2p_monitorpy"&gt;Task 2 - Running &lt;code&gt;p2p_monitor.py&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;p2p_monitor.py&lt;/code&gt; script is intended to demonstrate the possibilities the
tracepoints offer. It shows the inbound and outbound traffic between the Bitcoin
Core node you&amp;rsquo;re hooking into and it&amp;rsquo;s peers in real-time. Under the hood, it
uses the &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-netinbound_message"&gt;&lt;code&gt;net:inbound_message&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-netoutbound_message"&gt;&lt;code&gt;net:outbound_message&lt;/code&gt;&lt;/a&gt;
tracepoints, the Python BCC wrapper, and &lt;a href="https://en.wikipedia.org/wiki/Curses_(programming_library)"&gt;curses&lt;/a&gt; to render a TUI (Text User
Interface).&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll be hooking into a testnet Bitcoin Core node already running on the system.
To execute BPF syscalls and read from BPF maps, we&amp;rsquo;ll need a privileged
user. Here, we use the root user. You can become root by executing &lt;code&gt;sudo su&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As root, navigate into the &lt;code&gt;/home/workshop/bitcoin&lt;/code&gt; directory. From there, you
can run the following command to start the &lt;code&gt;p2p_monitor.py&lt;/code&gt; script. We pass the
path of the system-wide &lt;code&gt;bitcoind&lt;/code&gt; binary we want to hook into (not the one in
the &lt;code&gt;src/&lt;/code&gt; directory).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 contrib/tracing/p2p_monitor.py &lt;span class="k"&gt;$(&lt;/span&gt;which bitcoind&lt;span class="k"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You&amp;rsquo;ll be able to navigate with UP/DOWN and select a peer with SPACE to see
individual P2P messages being exchanged. It might take a few seconds for
peers to appear as there is generally a lot less traffic on testnet compared
to mainnet. The script only learns about a connected peer once we send or
receive a message from it. Keep the script running for a minute or two.&lt;/p&gt;
&lt;p&gt;This is also documented in &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing#p2p_monitorpy"&gt;contrib/tracing/README.md - p2p_monitor.py&lt;/a&gt;
if you want to revisit running this at a later point.&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;How many peers is your node connected to?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Does your node have any inbound peers? What are the connection types of your peers?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Are you able to observe, for example, a handshake with a peer or a ping-pong?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-3---tracing-a-unit-test"&gt;Task 3 - Tracing a Unit Test&lt;/h4&gt;
&lt;p&gt;When building a &lt;code&gt;bitcoind&lt;/code&gt; binary with tracepoints enabled, the Bitcoin Core
unit tests and benchmarks include these tracepoints, too, if they call the code
sections containing the tracepoints. When adding the tracepoints, we&amp;rsquo;ve made
sure to check that the tracepoints cause &lt;a href="https://github.com/bitcoin/bitcoin/pull/23724#issuecomment-996919963"&gt;only minimal overhead of a few CPU
instructions&lt;/a&gt; if we don&amp;rsquo;t hook into the tracepoint.&lt;/p&gt;
&lt;p&gt;eBPF is programmable. In the previous task, the tracing script sent the
data passed in tracepoint arguments back to userspace. However, for example,
we can further filter the data or use it in calculations. &lt;code&gt;bpftrace&lt;/code&gt; has
builtin functionality like, e.g., &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#3-sum-sum"&gt;&lt;code&gt;sum()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#2-count-count"&gt;&lt;code&gt;count()&lt;/code&gt;&lt;/a&gt;, and creating
histograms with &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#8-hist-log2-histogram"&gt;&lt;code&gt;hist()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bitcoin Core&amp;rsquo;s unit test suite has a test called
&lt;code&gt;coins_test/coins_cache_simulation_test&lt;/code&gt;. It runs the function
&lt;a href="https://github.com/bitcoin/bitcoin/blob/08bcfa27675da5c65e4c9eab7e7764eab0599298/src/test/coins_tests.cpp#L104-L118"&gt;&lt;code&gt;SimulationTest()&lt;/code&gt;&lt;/a&gt;. This is a large randomized insert/remove
simulation test for the in-memory UTXO cache of Bitcoin Core.
We can trace this test with the &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-utxocache"&gt;&lt;code&gt;utxocache&lt;/code&gt;&lt;/a&gt; tracepoints.&lt;/p&gt;
&lt;p&gt;Run the test from inside the &lt;code&gt;bitcoin&lt;/code&gt; directory (as &lt;code&gt;workshop&lt;/code&gt; user, &lt;code&gt;CTRL-D&lt;/code&gt; to exit from &lt;code&gt;sudo su&lt;/code&gt;) with the following command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./src/test/test_bitcoin --run_test&lt;span class="o"&gt;=&lt;/span&gt;coins_tests/coins_cache_simulation_test --log_level&lt;span class="o"&gt;=&lt;/span&gt;test_suite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Using the &lt;code&gt;bpftrace&lt;/code&gt; script &lt;code&gt;contrib/tracing/task-3-stub.bt&lt;/code&gt; as a
starting point, add functionallity to count how many UTXOs are added and removed
(spent) during the unit test. Additionally, create a histogram of the UTXO
values being added and removed.&lt;/p&gt;
&lt;details class="my-3"&gt;
&lt;summary&gt;
&lt;b&gt;Hint: Where do I start?&lt;/b&gt;
&lt;/summary&gt;
&lt;p&gt;
Take a look at the documation for the bpftrace functions &lt;code&gt;sum()&lt;/code&gt;, &lt;code&gt;count()&lt;/code&gt;, and &lt;code&gt;hist()&lt;/code&gt; linked above.
&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Start your tracing script in the &lt;code&gt;bitcoin&lt;/code&gt; directory as root user. In another
SSH session, run the unit test. Stop the tracing script once the test is done.
This should print the &lt;code&gt;bpftrace&lt;/code&gt; counters and histograms.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ bpftrace contrib/tracing/task-3-stub.bt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;How many UTXOs are added and spent during the test?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What are the most common values of UTXOs added and removed?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Which binary is the bpftrace script hooking into?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What data does the fourth argument (i.e. &lt;code&gt;arg3&lt;/code&gt;; zero-indexed) of the &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;spent&lt;/code&gt; tracepoints contain? (Hint: it&amp;rsquo;s documented)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-4---running-the-tracepoint-interface-tests"&gt;Task 4 - Running the tracepoint interface tests&lt;/h4&gt;
&lt;p&gt;We want the tracepoint API to be semi-stable. That means there shouldn&amp;rsquo;t
be unexpected breaking changes to the API, and tracepoint arguments should be
documented. However, as tracepoints expose internals, we might need to change
or drop the tracepoints if we refactor or drop Bitcoin Core internals. We test
them in the Bitcoin Core functional test suite written in Python to ensure that
the tracepoints don&amp;rsquo;t break. We use the BCC Python wrapper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Run the interface functional tests. There are two ways of running
the tests. You can either run them through &lt;code&gt;test/functional/test_runner.py&lt;/code&gt;
or execute the standalone Python scripts. I recommend running them as
standalone Python scripts first to see the eventual reasons the tests are
skipped. Once you&amp;rsquo;re sure they aren&amp;rsquo;t skipped (i.e., they pass), you can run
them through &lt;code&gt;test_runner.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Running the tests as standalone Python scripts:
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 test/functional/interface_usdt_net.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 test/functional/interface_usdt_utxocache.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 test/functional/interface_usdt_validation.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 test/functional/interface_usdt_coinselection.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Running the tests through &lt;code&gt;test_runner.py&lt;/code&gt;:
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python3 test/functional/test_runner.py --filter&lt;span class="o"&gt;=&lt;/span&gt;interface_usdt_*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Do the tests pass?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;We require permissions to do BPF syscalls and read BPF maps for the tests. What happens when you run the tests with the &lt;code&gt;workshop&lt;/code&gt; user?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Is blindly running Python scripts downloaded from the internet as root user on your own machine a good idea?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-5---adding-new-tracepoints"&gt;Task 5 - Adding new tracepoints&lt;/h4&gt;
&lt;p&gt;Here, the goal is to get familiar with adding a new tracepoint to Bitcoin Core.
You&amp;rsquo;ll find documentation on this in &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#adding-tracepoints-to-bitcoin-core"&gt;doc/tracing.md&lt;/a&gt;. This
also includes a few guidelines and best practices. Make sure to familiarize
yourself with the documentation and to ask questions if something is unclear!&lt;/p&gt;
&lt;p&gt;For long-running tracing tools like &lt;a href="https://bitcoind.observer"&gt;bitcoind-observer&lt;/a&gt; it&amp;rsquo;s useful to know
when the Bitcoin Core instance we&amp;rsquo;re hooking into is stopped and started.
This can, for example, be used to reset the statistic counters on a restart.
These tracepoints don&amp;rsquo;t need to pass specific data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Add two new tracepoints. The first should trigger when
Bitcoin Core is shut down, and the second should trigger when Bitcoin Core
is started. Think about a name for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt; (see docs on &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#adding-tracepoints-to-bitcoin-core"&gt;&amp;ldquo;Adding
tracepoints to Bitcoin Core&amp;rdquo;&lt;/a&gt;) that give a good description for a user not
familar with Bitcoin Core internals.&lt;/p&gt;
&lt;details class="my-3"&gt;
&lt;summary&gt;
&lt;b&gt;Hint: Where should I put the shutdown tracepoint?&lt;/b&gt;
&lt;/summary&gt;
&lt;p&gt;
The &lt;code&gt;src/init.cpp&lt;/code&gt; file contains a function called &lt;code&gt;Shutdown()&lt;/code&gt;. Take a look at this function.
&lt;/p&gt;
&lt;/details&gt;
&lt;details class="my-3"&gt;
&lt;summary&gt;
&lt;b&gt;Hint: Where should I put the start-up tracepoint?&lt;/b&gt;
&lt;/summary&gt;
&lt;p&gt;
The &lt;code&gt;src/init.cpp&lt;/code&gt; file contains a function called &lt;code&gt;AppInitMain()&lt;/code&gt;. Take a look at this function.
&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;You&amp;rsquo;ll need to recompile Bitcoin Core for your tracepoints to appear in the
&lt;code&gt;bitcoind&lt;/code&gt; binary. In the &lt;code&gt;/home/workshop/bitcoin&lt;/code&gt; directory, run the following
command as the &lt;code&gt;workshop&lt;/code&gt; user. This will bring the Bitcoin Core build
dependencies into scope.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nix-shell&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, you&amp;rsquo;ll only need to run &lt;code&gt;make&lt;/code&gt; to compile Bitcoin Core. We &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; have
to re-run the autogen and configure steps. Compiling Bitcoin Core might take a
minute or two.&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;bpftrace&lt;/code&gt; script in &lt;code&gt;contrib/tracing/task-5-stub.bt&lt;/code&gt; to test
your tracepoints. However, you&amp;rsquo;ll first need to fill in the names you&amp;rsquo;ve
choosen for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt;. Then, run the script with the following
command as root user (&lt;code&gt;sudo su&lt;/code&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ bpftrace contrib/tracing/task-5-stub.bt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open another SSH session to your VPS to start and stop Bitcoin Core. From the
&lt;code&gt;bitcoin&lt;/code&gt; directory, use the following command to start Bitcoin Core in regtest
mode. Wait a few seconds and then stop it with &lt;code&gt;CTRL-C&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./src/bitcoind -regtest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;What name did you choose for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt;?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Where did you choose to place your tracepoints in the functions resposible for startup and shutdown?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Is your shutdown tracepoint being triggered in case Bitcoin Core does &lt;strong&gt;not&lt;/strong&gt;
shutdown cleanly? If not, would a tracepoint for this make sense?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Workshop: Tracing Bitcoin Core v23.0</title><link>https://b10c.me/talks/017-tracing-workshop-bpp/</link><pubDate>Wed, 08 Jun 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/017-tracing-workshop-bpp/</guid><description>&lt;p&gt;I&amp;rsquo;ve held a workshop showcasing the new tracepoints and tracing functionallity included in Bitcoin Core release v23.0.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=XuRnL393pSk"&gt;video (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/talks/013-tracing-workshop/017-tracing-workshop-bpp.html"&gt;Slides (reveal-js)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>0xB10C – Tracepoints and monitoring the Bitcoin network</title><link>https://b10c.me/talks/016-chaincode-22/</link><pubDate>Mon, 06 Jun 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/016-chaincode-22/</guid><description>&lt;p&gt;&lt;a href="https://twitter.com/adamcjonas"&gt;Jonas&lt;/a&gt;, &lt;a href="https://twitter.com/murchandamus"&gt;Murch&lt;/a&gt;, and I chat about monitoring Bitcoin mining pools and tracing Bitcoin Core.&lt;/p&gt;
&lt;a href='https://anchor.fm/chaincode/episodes/0xB10C--Tracepoints-and-monitoring-the-Bitcoin-network-e1jipel' class="btn btn-outline-danger my-2" role="button"&gt;anchor.fm&lt;/a&gt;
&lt;a href='https://podcast.chaincode.com/2022/06/06/timo-tracepoints-monitoring.html' class="btn btn-outline-danger my-2" role="button"&gt;Podcast Page&lt;/a&gt;</description></item><item><title>Monitoring Bitcoin mining pool transaction selection</title><link>https://b10c.me/talks/015-miningpool-observer/</link><pubDate>Sun, 08 May 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/015-miningpool-observer/</guid><description>&lt;p&gt;Can we detect transaction censorship by mining pools on the Bitcoin network?&lt;/p&gt;
&lt;p&gt;I spoke at the &lt;a href="https://mitbitcoinexpo.org"&gt;MIT Bitcoin Expo&lt;/a&gt; 2022 about mining pool transaction selection,
my &lt;a href="https://github.com/0xb10c/miningpool-observer"&gt;miningpool-observer&lt;/a&gt; project, and observed extra, missing, and conflicting
transactions between my block templates and the blocks mined by pools. I
conclude that we can detect large scale transcation censorship by but haven&amp;rsquo;t
seen any concrete evidence for censorship attempts yet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/zDBH9pg78YQ"&gt;video (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/talks/015-miningpool-observer/015-miningpool-observer.html"&gt;slides (RevealJS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/talks/015-miningpool-observer/slides.pdf"&gt;slides (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Grant from Bitcoin Suisse (not accepted)</title><link>https://b10c.me/funding/2022-bitcoinsuisse-grant/</link><pubDate>Fri, 08 Apr 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2022-bitcoinsuisse-grant/</guid><description>&lt;p&gt;&lt;a href="https://www.bitcoinsuisse.com/"&gt;Bitcoin Suisse&lt;/a&gt; suprise-&lt;a href="https://www.bitcoinsuisse.com/news/bitcoin-suisse-launches-research-grant-program"&gt;announced&lt;/a&gt; that they had selected me for a Research Grant.&lt;/p&gt;
&lt;p&gt;However, due to a exclusivity agreement in the &lt;a href="https://b10c.me/transparency/2022-brink-grant/"&gt;Brink grant agreement&lt;/a&gt;, I wasn&amp;rsquo;t able to accept the grant.
To the best of my knowledge, the grant payment was paid out to Brink instead.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bitcoinsuisse.com/news/bitcoin-suisse-launches-research-grant-program"&gt;Announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Advancing Bitcoin Workshop: Tracing Bitcoin Core v23.0</title><link>https://b10c.me/projects/tracing-workshop-22/</link><pubDate>Wed, 02 Mar 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/tracing-workshop-22/</guid><description>&lt;p&gt;These are the tasks of my &amp;ldquo;Tracing Bitcoin Core v23.0&amp;rdquo; workshop I held at
&lt;a href="https://www.advancingbitcoin.com/"&gt;Advancing Bitcoin 2022&lt;/a&gt;. They might
not make much sense on their own.&lt;/p&gt;
&lt;p&gt;Slides from the workshop can be found &lt;a href="https://b10c.me/talks/013-tracing-workshop.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="prerequisites"&gt;Prerequisites&lt;/h4&gt;
&lt;p&gt;You will only need an SSH client to participate in the workshop. I will provide
you with a pre-setup VPS to work on the tasks. I&amp;rsquo;ll send you the IP for
your VPS. However, I first need an SSH &lt;strong&gt;public key&lt;/strong&gt; from you. Please make
sure not to send the private key part of your SSH key! I&amp;rsquo;d recommend to
generate a new, temporary key pair for this workshop. You should be able to
run the following command on Linux and macOS to generate a new ED25519 keypair.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ssh-keygen -t ed25519 -C &amp;#34;advbit22-workshop&amp;#34;
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/your_user/.ssh/id_ed25519): /home/your_user/.ssh/id_advbit22workshop
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/your_user/.ssh/id_advbit22workshop
Your public key has been saved in /home/your_user/.ssh/id_advbit22workshop.pub
The key fingerprint is:
SHA256:FaK3FinGErPr1nTG63msKLoVSU9syTPpQSGGdsnpfvZM advbit22-workshop
The key&amp;#39;s randomart image is:
+--[ED25519 256]--+
+ snip +
+----[SHA256]-----+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please send the &lt;code&gt;/home/your_user/.ssh/id_advbit22workshop.pub&lt;/code&gt; file to me. I&amp;rsquo;ll
display contact information on the screen. Once the VPS is set up, I&amp;rsquo;ll send you
the IP address. Feel free to take a break in the meantime. It might take a few
minutes until I send you the IP address of your server.&lt;/p&gt;
&lt;h4 id="task-0---login-and-get-familiar-with-the-system"&gt;Task 0 - Login and get familiar with the system&lt;/h4&gt;
&lt;p&gt;Once you&amp;rsquo;ve got your IP address, you should be able to SSH into the server via
the following command. You might need to specify the SSH private key file
(without the &lt;code&gt;.pub&lt;/code&gt; at the end).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ssh workshop@[your.ipv4.addr] -i /home/your_user/.ssh/id_advbit22workshop
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally, you might need to set the correct permissions (only read+write
for your current user) on the SSH private key.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;chmod 600 /home/your_user/.ssh/id_advbit22workshop
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you&amp;rsquo;ve logged in to the system, it&amp;rsquo;s time to get familiar with it.
Feel free to navigate around, check out the specs and running processes with
&lt;code&gt;htop&lt;/code&gt;, and check the kernel and operating system information with &lt;code&gt;uname -a&lt;/code&gt;.
The kernel version is important for using a lot of the eBPF functionality.
We need at least a Linux kernel of version 4.8 to use hook into the tracepoints
in Bitcoin Core. The programs &lt;code&gt;vim&lt;/code&gt; and &lt;code&gt;nano&lt;/code&gt; are also installed.&lt;/p&gt;
&lt;p&gt;Note that you&amp;rsquo;ll be having password-less &lt;code&gt;sudo&lt;/code&gt; access on the system. We need a
privileged user to do BPF syscalls and to read from BPF maps. I expect you to
&lt;strong&gt;not&lt;/strong&gt; abuse this power (e.g. deleting system files). Treat this server as you
would treat a server you own. The server auto-shutdowns on excessive
resource usage. Additionally, your disk space is limited (please don&amp;rsquo;t try to
sync mainnet). I might not provision another server for you or restart your
instance. This would effectively end your participation in the workshop.&lt;/p&gt;
&lt;p&gt;For the following tasks, it could be helpful to take a look at the Bitcoin Core
tracing documentation and examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tracing documentation: &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md"&gt;doc/tracing.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tracing examples: &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing"&gt;contrib/tracing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Which Linux kernel version does the provided server have?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Are you able to switch to the &lt;code&gt;root&lt;/code&gt; user with &lt;code&gt;sudo su&lt;/code&gt;?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;ll wait until everyone is able to login into their server before continuing
to the next task.&lt;/p&gt;
&lt;h4 id="task-1---listing-available-tracepoints"&gt;Task 1 - Listing available tracepoints&lt;/h4&gt;
&lt;p&gt;There is a &lt;code&gt;bitcoin&lt;/code&gt; directory in your home directory. This contains a checkout
of the Bitcoin Core source code. I&amp;rsquo;ve also built Bitcoin Core
from source for you to save some time during the workshop. During the
&lt;code&gt;./configure&lt;/code&gt; step, it was detected that the SystemTap &lt;code&gt;sys/sdt.h&lt;/code&gt; header file
is present on the system. Bitcoin Core was built with tracepoints.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ./configure
[..]
Options used to compile and link:
multiprocess = no
with experimental syscall sandbox support = yes
with libs = yes
[..]
with zmq = yes
with test = yes
with fuzz binary = yes
with bench = yes
use asm = yes
USDT tracing = yes # &amp;lt;---- Tracepoints enabled!
sanitizers =
debug enabled = no
[..]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Your task is to list the available tracepoints in the &lt;code&gt;bitcoind&lt;/code&gt;
binary. The Bitcoin Core tracepoint documentation (linked above) contains three
methods to do so. Feel free to try them out. The &lt;code&gt;bitcoind&lt;/code&gt; binary is located
in &lt;code&gt;/home/workshop/bitcoin/src/bitcoind&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Try finding one or two tracepoints in the Bitcoin Core source code. I&amp;rsquo;d
recommend that you navigate to the &lt;code&gt;/home/workshop/bitcoin/src&lt;/code&gt; directory
and &lt;code&gt;grep&lt;/code&gt; for &lt;code&gt;TRACE&lt;/code&gt; in &lt;code&gt;*.cpp&lt;/code&gt; files from there (&lt;code&gt;grep TRACE *.cpp -R&lt;/code&gt;).
The ripgrep tool (&lt;code&gt;rg&lt;/code&gt;) is installed on the system too.&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Which tracepoints does the binary contain?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Which of the three methods show you information about the arguments passed to the tracepoints?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Are all tracepoints documented in the tracing documentation?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Were you able to find a tracepoint in the Bitcoin Core source code?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-2---running-p2p_monitorpy"&gt;Task 2 - Running &lt;code&gt;p2p_monitor.py&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;p2p_monitor.py&lt;/code&gt; script is intended to demonstrate the possibilities the
tracepoints offer. It shows the inbound and outbound traffic between the Bitcoin
Core node you&amp;rsquo;re hooking into and it&amp;rsquo;s peers in real-time. Under the hood, it
uses the &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-netinbound_message"&gt;&lt;code&gt;net:inbound_message&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-netoutbound_message"&gt;&lt;code&gt;net:outbound_message&lt;/code&gt;&lt;/a&gt;
tracepoints, the Python BCC wrapper, and &lt;a href="https://en.wikipedia.org/wiki/Curses_(programming_library)"&gt;curses&lt;/a&gt; to render a TUI (Text User
Interface).&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll be hooking into a testnet Bitcoin Core node already running on the system.
To execute BPF syscalls and read from BPF maps, we&amp;rsquo;ll need a privileged
user. Here, we use the root user. You can become root by executing &lt;code&gt;sudo su&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As root, navigate into the &lt;code&gt;/home/workshop/bitcoin&lt;/code&gt; directory. From there, you
can run the following command to start the &lt;code&gt;p2p_monitor.py&lt;/code&gt; script. We pass the
path of the system-wide &lt;code&gt;bitcoind&lt;/code&gt; binary we want to hook into (not the one in
the &lt;code&gt;src/&lt;/code&gt; directory).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python3 contrib/tracing/p2p_monitor.py $(which bitcoind)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;ll be able to navigate with UP/DOWN and select a peer with SPACE to see
individual P2P messages being exchanged. It might take a few seconds for
peers to appear as there is generally a lot less traffic on testnet compared
to mainnet. The script only learns about a connected peer once we send or
receive a message from it. Keep the script running for a minute or two.&lt;/p&gt;
&lt;p&gt;This is also documented in &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing#p2p_monitorpy"&gt;contrib/tracing/README.md - p2p_monitor.py&lt;/a&gt;
if you want to revisit running this at a later point.&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;How many peers is your node connected to?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Does your node have any inbound peers? What are the connection types of your peers?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Were you able to observe, for example, a handshake with a peer or a ping-pong?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-3---tracing-a-unit-test"&gt;Task 3 - Tracing a Unit Test&lt;/h4&gt;
&lt;p&gt;When building a &lt;code&gt;bitcoind&lt;/code&gt; binary with tracepoints enabled, the Bitcoin Core
unit tests and benchmarks include these tracepoints, too, if they call the code
sections containing the tracepoints. When adding the tracepoints, we&amp;rsquo;ve made
sure to check that the tracepoints cause &lt;a href="https://github.com/bitcoin/bitcoin/pull/23724#issuecomment-996919963"&gt;only minimal overhead of a few CPU
instructions&lt;/a&gt; if we don&amp;rsquo;t hook into the tracepoint.&lt;/p&gt;
&lt;p&gt;eBPF is programmable. In the previous task, the tracing script sent the
data passed in tracepoint arguments back to userspace. However, for example,
we can further filter the data or use it in calculations. &lt;code&gt;bpftrace&lt;/code&gt; has
builtin functionality like, e.g., &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#3-sum-sum"&gt;&lt;code&gt;sum()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#2-count-count"&gt;&lt;code&gt;count()&lt;/code&gt;&lt;/a&gt;, and creating
histograms with &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#8-hist-log2-histogram"&gt;&lt;code&gt;hist()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bitcoin Core&amp;rsquo;s unit test suite has a test called
&lt;code&gt;coins_test/coins_cache_simulation_test&lt;/code&gt;. It runs the function
&lt;a href="https://github.com/bitcoin/bitcoin/blob/08bcfa27675da5c65e4c9eab7e7764eab0599298/src/test/coins_tests.cpp#L104-L118"&gt;&lt;code&gt;SimulationTest()&lt;/code&gt;&lt;/a&gt;. This is a large randomized insert/remove
simulation test for the in-memory UTXO cache of Bitcoin Core.
We can trace this test with the [utxocache] tracepoints.&lt;/p&gt;
&lt;p&gt;Run the test from inside the &lt;code&gt;bitcoin&lt;/code&gt; directory with the following command.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;./src/test/test_bitcoin --run_test=coins_tests/coins_cache_simulation_test --log_level=test_suite
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Count how many UTXOs are added and removed (spent) during the unit
test. Additionally, create a histogram of the UTXO values being added and
removed. Use the &lt;code&gt;bpftrace&lt;/code&gt; script &lt;code&gt;contrib/tracing/taks-3-stub.bt&lt;/code&gt; as a
starting point. Run the completed tracing script as root user (e.g. with
&lt;code&gt;sudo&lt;/code&gt;). In another SSH session, run the test. Stop the tracing script once
the test is done. This will print the &lt;code&gt;bpftrace&lt;/code&gt; counters and histograms.&lt;/p&gt;
&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;How many UTXOs are added and spent during the test?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What are the most common values of UTXOs added and removed?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="task-4---running-the-tracepoint-interface-tests"&gt;Task 4 - Running the tracepoint interface tests&lt;/h4&gt;
&lt;p&gt;We want the tracepoint API to be semi-stable. That means there shouldn&amp;rsquo;t
be unexpected breaking changes to the API, and tracepoint arguments should be
documented. However, as tracepoints expose internals, we might need to change
or drop the tracepoints if we refactor or drop Bitcoin Core internals. We test
them in the Bitcoin Core functional test suite written in Python to ensure that
the tracepoints don&amp;rsquo;t break. We use the BCC Python wrapper.
These tests are currently proposed in PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/24358"&gt;#24358&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Run the interface functional tests. There are two ways of running
the tests. You can either run them through &lt;code&gt;test/functional/test_runner.py&lt;/code&gt;
or execute the standalone Python scripts. I recommend to running them as
standalone Python scripts first to see the eventual reasons the tests are
skipped. Once you&amp;rsquo;re sure they aren&amp;rsquo;t skipped (i.e., they pass), you can run
them through &lt;code&gt;test_runner.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Running the tests as standalone Python scripts:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 test/functional/interface_usdt_net.py
$ python3 test/functional/interface_usdt_validation.py
$ python3 test/functional/interface_usdt_utxocache.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running the tests through &lt;code&gt;test_runner.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 test/functional/test_runner.py --filter=interface_usdt_*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Do the tests pass?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;We require permissions to do BPF syscalls and read BPF maps for the tests. What happens when you run the tests with the &lt;code&gt;workshop&lt;/code&gt; user?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Is blindly running Python scripts downloaded from the internet as root user on your own machine a good idea?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want, you can leave a short review on the PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/24358"&gt;#24358&lt;/a&gt;.
For example, if you think it would be good to test the tracepoints, you could
leave a &amp;ldquo;Concept ACK&amp;rdquo;.&lt;/p&gt;
&lt;h4 id="task-5---adding-new-tracepoints"&gt;Task 5 - Adding new tracepoints&lt;/h4&gt;
&lt;p&gt;Here, the goal is to get familiar with adding a new tracepoint to Bitcoin Core.
You&amp;rsquo;ll find documentation on this in &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#adding-tracepoints-to-bitcoin-core"&gt;doc/tracing.md&lt;/a&gt;. This
also includes a few guidelines and best practices. Make sure to familiarize
yourself with the documentation and to ask questions if something is unclear!&lt;/p&gt;
&lt;p&gt;For long-running tracing tools like &lt;a href="https://bitcoind.observer"&gt;bitcoind-observer&lt;/a&gt; it&amp;rsquo;s useful to know
when the Bitcoin Core instance we&amp;rsquo;re hooking into is stopped and started.
This can, for example, be used to reset the statistic counters on a restart.
These tracepoints don&amp;rsquo;t need to pass specific data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Add two new tracepoints. The first should trigger when
Bitcoin Core is shut down, and the second should trigger when Bitcoin Core
is started. Think about a name for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt; (see documentation)
that give a good description for a user not familar with Bitcoin Core internals.
Hint: You&amp;rsquo;ll probably want to have a look inside &lt;code&gt;src/init.cpp&lt;/code&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Spoiler: Help! Where should I put the shutdown tracepoint?&lt;/summary&gt;
The `src/init.cpp` file contains a function called `Shutdown()`.
Take a look into this function.
&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt;Spoiler: Help! Where should I put the shutdown tracepoint?&lt;/summary&gt;
The `src/init.cpp` file contains a function called `AppInitMain()`.
Take a look into this function.
&lt;/details&gt;
&lt;p&gt;You&amp;rsquo;ll need to recompile Bitcoin Core for your tracepoints to appear in the
&lt;code&gt;bitcoind&lt;/code&gt; binary. In the &lt;code&gt;/home/workshop/bitcoin&lt;/code&gt; directory, run the following
command as non-root (i.e. &lt;code&gt;workshop&lt;/code&gt;) user. This will bring the Bitcoin Core
build time dependencies into scope.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nix-shell
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, you&amp;rsquo;ll only need to run &lt;code&gt;make&lt;/code&gt;. We &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; have to re-run the autogen and
configure steps. Compiling Bitcoin Core might take a minute or two.&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;bpftrace&lt;/code&gt; script in &lt;code&gt;contrib/tracing/task-5-stub.bt&lt;/code&gt; to test
your tracepoints. However, you&amp;rsquo;ll first need to fill in the names you&amp;rsquo;ve
choosen for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt;. Then, run the script with the following
command.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo bpftrace contrib/tracing/task-5-stub.bt
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;&lt;em&gt;What name did you choose for &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;event&lt;/code&gt;?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Where did you choose to place your tracepoints in the functions resposible for startup and shutdown?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Is your shutdown tracepoint being triggered in case Bitcoin Core does &lt;strong&gt;not&lt;/strong&gt;
shutdown cleanly? If not, would a tracepoint for this make sense?&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>P2P monitoring</title><link>https://b10c.me/talks/014-p2p-monitoring/</link><pubDate>Mon, 28 Feb 2022 11:00:00 +0000</pubDate><guid>https://b10c.me/talks/014-p2p-monitoring/</guid><description>&lt;p&gt;I presented my planned P2P monitoring project to CoreDev attendees.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/talks/014-p2p/014-p2p-monitoring.html"&gt;Slides (reveal-js)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Grant from Brink for 2022</title><link>https://b10c.me/funding/2022-brink-grant/</link><pubDate>Tue, 01 Feb 2022 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2022-brink-grant/</guid><description>&lt;p&gt;&lt;a href="https://brink.dev"&gt;Brink.dev&lt;/a&gt; supported my work on Bitcoin for the full year of 2022 with a developer grant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://brink.dev/blog/2022/04/04/grantees/"&gt;Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Extracting the Private Key from Schnorr Signatures that reuse a Nonce</title><link>https://b10c.me/blog/009-schnorr-nonce-reuse-challenge/</link><pubDate>Mon, 17 Jan 2022 09:00:00 +0000</pubDate><guid>https://b10c.me/blog/009-schnorr-nonce-reuse-challenge/</guid><description>&lt;p&gt;Elliott (aka &lt;a href="https://twitter.com/robot__dreams"&gt;@robot__dreams&lt;/a&gt;) posted a Bitcoin-flavored cryptography &lt;a href="https://twitter.com/robot__dreams/status/1479518204036669441"&gt;challenge&lt;/a&gt; on Twitter.
The goal is to extract the private key from two Schnorr signatures that reuse a nonce.
I&amp;rsquo;ve recently reviewed and merged Kalle Rosenbaum&amp;rsquo;s cross-post of his &lt;a href="https://popeller.io/schnorr-basics"&gt;Schnorr Basics&lt;/a&gt; post to &lt;a href="https://bitcoin-dev.blog/blog/schnorr-basics/"&gt;bitcoin-dev.blog&lt;/a&gt;.
His post shows, for example, why nonce reuse leaks the private key.
The challenge was the perfect opportunity for me to apply the theory the blog post explains.
This post is a write-up of how I solved the challenge.&lt;/p&gt;
&lt;h1 id="challenge"&gt;Challenge&lt;/h1&gt;
&lt;p&gt;The challenge puts us into the role of an adversary.
The goal is to extract the private key used in the creation of two Schnorr signatures.
The nonce is reused in both signatures.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
&lt;p&gt;
You're given Schnorr signatures on two different messages signed by the same private key. Fortunately for you (the adversary), the signer screwed up their implementation of BIP-340 and reused a nonce.
Can you capitalize on this fatal error and extract the signer's private key?
&lt;/p&gt;
&lt;p&gt;
Note: You may find it helpful to interpret some of the byte strings as ASCII, in order to check your work.
&lt;/p&gt;
&lt;code&gt;
Public Key:&amp;nbsp;463F9E1F3808CEDF5BB282427ECD1BFE8FC759BC6F65A42C90AA197EFC6F9F26
&lt;br&gt;
Message&amp;nbsp;1:&amp;nbsp;6368616E63656C6C6F72206F6E20746865206272696E6B206F66207365636F6E
&lt;br&gt;
Signature&amp;nbsp;1:&amp;nbsp;F3F148DBF94B1BCAEE1896306141F319729DCCA9451617D4B529EB22C2FB521A32A1DB8D2669A00AFE7BE97AF8C355CCF2B49B9938B9E451A5C231A45993D920
&lt;br&gt;
Message&amp;nbsp;2:&amp;nbsp;6974206D69676874206D616B652073656E7365206A75737420746F2067657420
&lt;br&gt;
Signature&amp;nbsp;2:&amp;nbsp;F3F148DBF94B1BCAEE1896306141F319729DCCA9451617D4B529EB22C2FB521A974240A9A9403996CA01A06A3BC8F0D7B71D87FB510E897FF3EC5BF347E5C5C1
&lt;/code&gt;
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;h3 id="theory"&gt;Theory&lt;/h3&gt;
&lt;p&gt;We use the same notation as laid out in &lt;a href="https://popeller.io/schnorr-basics"&gt;Schnorr basics&lt;/a&gt;.
The Schnorr signature scheme used in Bitcoin uses the secp256k1 curve.
The generator point $G$ and the curve group order $n$ for the secp256k1 curve are listed, along with the other curve parameters, in &lt;a href="https://www.secg.org/sec2-v2.pdf"&gt;Standards for Efficient Cryptography&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We are looking for the private key $p$.
The public key $P$, two messages, $m_1$ and $m_2$, and two Signatures $(R, s_1)$ and $(R, s_2)$ are given.
Both signatures share the nonce commitment $R$, as they reuse the nonce $r$.
The responses $s_1$ and $s_2$ are different in both signatures as they belong to distinct messages.&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
p &amp;amp; \quad \text{private key} \\
P = pG &amp;amp; \quad \text{public key} \\
m &amp;amp; \quad \text{message} \\
r &amp;amp; \quad \text{random nonce} \\
R = rG &amp;amp; \quad \text{nonce commitment}
\end{align}
$$&lt;/p&gt;
&lt;p&gt;The signature signs a challenge hash $e$ which is the hash the concatenation of $R$, $P$, and $m$.&lt;/p&gt;
&lt;p&gt;$$
e = H(R||P||m) \tag{1}
$$&lt;/p&gt;
&lt;p&gt;The response $s$ is the addition of the nonce $r$ and $ep$.&lt;/p&gt;
&lt;p&gt;$$
s = r + ep
$$&lt;/p&gt;
&lt;p&gt;To extract the private key $p$, we can set up an equation system with two equations and two unknowns.
This is adapted from the &lt;a href="https://popeller.io/schnorr-basics"&gt;Schnorr basics&lt;/a&gt; post but shown in more detail.&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
I &amp;amp; \quad s_1 = r + e_1 p \\
II &amp;amp; \quad s_2 = r + e_2 p
\end{align}
$$&lt;/p&gt;
&lt;p&gt;We substract $II$ from $I$.&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
s_1 - s_2 &amp;amp;= r + e_1 p - (r + e_2 p) &amp;amp; \tag*{ negating $(r + e_2 p)$} \\
s_1 - s_2 &amp;amp;= r + e_1 p - r - e_2 p &amp;amp; \tag*{ $+r$ and $-r$ cancel out} \\
s_1 - s_2 &amp;amp;= e_1 p - e_2 p &amp;amp; \tag*{ extracting $p$ } \\
s_1 - s_2 &amp;amp;= p(e_1 - e_2) \tag*{ dividing by ($e_1 - e_2)$ } \\
\frac{s_1 - s_2}{e_1 - e_2} &amp;amp;= p \tag{2}
\end{align}
$$&lt;/p&gt;
&lt;p&gt;We can calculate $e_1$ and $e_2$ and know $s_1$ and $s_2$ from the provided signatures.
The nonces $r$ and $-r$ only cancel out because the nonce is reused.
With no nonce reuse, $r_1$ and $r_2$ wouldn&amp;rsquo;t cancel out, and we couldn&amp;rsquo;t solve for $p$.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve implemented a solution in Python.
The curve group order, messages, signatures, and the public key are Python&amp;rsquo;s integer type.
Python integers can hold arbitrarily large numbers (&lt;a href="https://www.python.org/dev/peps/pep-0237/"&gt;PEP 237&lt;/a&gt;).
Arithmetic on integers is modulo $n$, the curve group order.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# curve group order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# public key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x463F9E1F3808CEDF5BB282427ECD1BFE8FC759BC6F65A42C90AA197EFC6F9F26&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# first message and signature&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;m1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x6368616E63656C6C6F72206F6E20746865206272696E6B206F66207365636F6E&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sig1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xF3F148DBF94B1BCAEE1896306141F319729DCCA9451617D4B529EB22C2FB521A32A1DB8D2669A00AFE7BE97AF8C355CCF2B49B9938B9E451A5C231A45993D920&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# second message and signature&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x6974206D69676874206D616B652073656E7365206A75737420746F2067657420&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sig2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xF3F148DBF94B1BCAEE1896306141F319729DCCA9451617D4B529EB22C2FB521A974240A9A9403996CA01A06A3BC8F0D7B71D87FB510E897FF3EC5BF347E5C5C1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Bitcoin ECDSA signatures are DER-encoded and have an encoding overhead.
Bitcoin Schnorr signatures have no overhead as they are just the concatenation of 32 bytes of $R$ and 32 bytes of $s$.
See &lt;a href="https://b10c.me/blog/006-evolution-of-the-bitcoin-signature-length/"&gt;Evolution of the signature size in Bitcoin&lt;/a&gt; for more details.
As the nonce $r$ is reused, the signatures share the same $R$.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the first 256 bits of the signatures are R&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig2&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the last 256 bits of the signatures sig1 and sig2 are the reponses s1 and s2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The next step is to calculate $e_1$ and $e_2$ using formula $(1)$.&lt;/p&gt;
&lt;p&gt;$$
e = H(R||P||m)
$$&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki"&gt;BIP 340&lt;/a&gt; defines that the hashes are tagged with a context-specific tag.
This makes sure hashes used in one context can&amp;rsquo;t be reinterpreted in another context.
The tag for challenges is &lt;code&gt;SHA256(&amp;quot;BIP0340/challenge&amp;quot;) || SHA256(&amp;quot;BIP0340/challenge&amp;quot;)&lt;/code&gt;.
We use the &lt;code&gt;hashlib&lt;/code&gt; module provided by Python to calculate SHA256 hashes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;challenge_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BIP0340/challenge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As the &lt;code&gt;hashlib.sha256&lt;/code&gt; function expects parameters of type &lt;code&gt;bytes&lt;/code&gt; we need to convert our integers.
We define the &lt;code&gt;int_to_bytes(i)&lt;/code&gt; helper function and calculate the challenge hashes $e_1$ and $e_2$.
We treat integers as big-endian when converting them to and from byte arrays.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;byteorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;big&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;challenge_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;e1_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;challenge_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;challenge_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;e2_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;challenge_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;challenge_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;hashlib.sha256&lt;/code&gt; function returns a &lt;code&gt;bytes&lt;/code&gt; object.
To convert the result back to integers, we define the helper function &lt;code&gt;bytes_to_int(b)&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;e1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;e2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e2_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We now have the challenge hashes $e_1$ and $e_2$ and the responses $s_1$ and $s_2$.
These can be used in formula $(2)$&lt;/p&gt;
&lt;p&gt;$$
p = \frac{s_1 - s_2}{e_1 - e_2} = \frac{i}{j}
$$&lt;/p&gt;
&lt;p&gt;to calculate $p$.
We first calculate the dividend $i$ and the divisor $j$.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, implementing modular division isn&amp;rsquo;t as straightforward as dividing the dividend by the divisor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# incorrect modular division!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We need the &lt;a href="https://en.wikipedia.org/wiki/Modular_multiplicative_inverse"&gt;modular multiplicative inverse&lt;/a&gt; $j^{-1} = x$ of the divisor for modular division.
The modular inverse $x$ multiplied with the divisor $j$ is congruent to 1 modulo $n$.&lt;/p&gt;
&lt;p&gt;$$
j \cdot x \equiv 1 \mod n
$$&lt;/p&gt;
&lt;p&gt;The modular inverse is defined if the $i$ and $n$ are co-prime.
This is the case if the greatest common divisor ($\text{GCD}$) of $i$ and $n$ is $1$.
Then, to perform modular division, we can multiply the divisor $i$ and $x$.&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
\frac{i}{j} = i \cdot j^{-1} = i \cdot x \mod n \tag{3}
\end{align}
$$&lt;/p&gt;
&lt;p&gt;This is implemented using the &lt;a href="https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm"&gt;extended Euclidean algorithm&lt;/a&gt; &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; to calculate the $\text{GCD}(j, n)$, and, if $j$ and $n$ are co-prime, also the modular multiplicative inverse $x$.
We&amp;rsquo;ve adapted the recursive Python implementation of the extended Euclidean algorithm provided by &lt;a href="https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm"&gt;wikibooks.org&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;return (g, x, y) such that a*x + b*y = g = gcd(a, b)&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;b_div_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_mod_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;divmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b_mod_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b_div_a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gcd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gcd&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GCD of divisor mod n is not 1. Modular inverse is not defined.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We check that the $\text{GCD}$ of $i$ and $n$ is equal to $1$.
If that&amp;rsquo;s the case, we can use formula $(3)$ to calculate $p$.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The variable &lt;code&gt;p&lt;/code&gt; now holds the private key as an integer.
The challenge mentioned checking our results by printing the ASCII encoded private key.
We can convert the integer to &lt;code&gt;bytes&lt;/code&gt; with the &lt;code&gt;int_to_bytes(i)&lt;/code&gt; helper function we defined earlier.
When printed, the &lt;code&gt;bytes&lt;/code&gt; type automatically shows ASCII characters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The output is purposefully omitted here.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Another way of checking if the extracted private key is correct is to calculate the corresponding public key and compare it to the provided public key.
If the public keys match, the private key is correct.
We use the &lt;a href="https://pypi.org/project/fastecdsa/"&gt;fastecdsa&lt;/a&gt; Python module for elliptic curve scalar multiplication (a scalar multiplied with a point).
The public key is $P = pG$.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastecdsa.curve&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secp256k1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;secp256k1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Congratulations, you found the private key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Public keys don&amp;#39;t match: the private key is incorrect!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# prints: Congratulations, you found the private key&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This solves the challenge.
The complete Python code as a Jupyter Notebook can be found &lt;a href="https://gist.github.com/0xB10C/62cb252c24cbe5a8b3f3014ec0acc625"&gt;here&lt;/a&gt; and run online in Google Colab &lt;a href="https://colab.research.google.com/gist/0xB10C/62cb252c24cbe5a8b3f3014ec0acc625"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;
&lt;p&gt;While not part of the original challenge, knowing the private key $p$ allows us to extract the nonce $r$.
Solving formula $(1)$ for $r$.&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
s = &amp;amp; \ r + ep \tag*{- ep} \\
s-ep = &amp;amp; \ r
\end{align}
$$&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To double-check, we can calculate the nonce commitment $R = rG$.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;secp256k1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;The provided and the calculated nonce commitment R match!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nonce commitments don&amp;#39;t match!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# prints: The provided and the calculated nonce commitment R match!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="thanks"&gt;Thanks&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve enjoyed this challenge and wanted to thank Elliott for setting it up.
For me, as someone who is far from being an expert in cryptography, it turned out to hit the sweet spot between challenging and still being solvable.
Elliott already followed up with the &lt;a href="https://gist.github.com/robot-dreams/678bac9ef5d021fbf610d6e171243e0a"&gt;second and harder challenge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wouldn&amp;rsquo;t have looked at this challenge if I hadn&amp;rsquo;t read Kalle Rosenbaum&amp;rsquo;s &lt;em&gt;Schnorr basics&lt;/em&gt; post only a few days before.
He mentions he attempts to explain Schnorr signatures at a level he appreciates and hopes the reader finds it valuable too.
For me, this has been the case.
I think it&amp;rsquo;s often worth it writing a deep-dive into topics that you spent a lot of time on.&lt;/p&gt;
&lt;p&gt;I want to pay this forward with this post.
There are likely some who can&amp;rsquo;t dedicate enough time to solve this challenge but who find my solution valuable.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Elliott mentioned that I could have used Fermat&amp;rsquo;s Little Theorem to find the modular inverse too.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Bitcoin Core PR Review Club: #20827</title><link>https://b10c.me/talks/012-prreviewclub-20827/</link><pubDate>Wed, 29 Dec 2021 17:00:00 +0000</pubDate><guid>https://b10c.me/talks/012-prreviewclub-20827/</guid><description>&lt;p&gt;I&amp;rsquo;ve prepared and moderated a Bitcoin Core PR review club meeting on &lt;a href="https://github.com/luke-jr"&gt;luke-jr&lt;/a&gt;&amp;rsquo;s PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/20827"&gt;#20827: During IBD, prune as much as possible until we get close to where we will eventually keep blocks&lt;/a&gt;.
This is an potential IBD speed-up for pruned nodes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bitcoincore.reviews/20827"&gt;Bitcoin Core PR Review Club on #20827&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Bitcoin Core PR Review Club: #23724</title><link>https://b10c.me/talks/011-prreviewclub-23724/</link><pubDate>Wed, 15 Dec 2021 17:00:00 +0000</pubDate><guid>https://b10c.me/talks/011-prreviewclub-23724/</guid><description>&lt;p&gt;I&amp;rsquo;ve prepared and moderated a Bitcoin Core PR review club meeting on my PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/23724"&gt;#23724: add systemtap&amp;rsquo;s sys/sdt.h as depends for GUIX builds with USDT tracepoints&lt;/a&gt;. My goal was to get feedback and eventually reach consensus on having the tracepoints for Userspace, Statically Defined Tracing in GUIX, and release builds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bitcoincore.reviews/23724"&gt;Bitcoin Core PR Review Club on #23724&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/blog/008-bitcoin-core-usdt-support/"&gt;Blog post: Userspace, Statically Defined Tracing support for Bitcoin Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Monitoring Taproot Activation</title><link>https://b10c.me/projects/019-taproot-activation-monitoring/</link><pubDate>Sun, 14 Nov 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/019-taproot-activation-monitoring/</guid><description>&lt;p&gt;In November 2021, the Taproot soft-fork activated on the Bitcoin network.
I streamed my activation monitoring and helped pools not mining P2TR spends
to fix their issues.&lt;/p&gt;
&lt;p&gt;Soft-fork activations can keep Bitcoin developers awake at night. After all,
up-to-date nodes reduce what is allowed under the consensus rules to a subset.
If, for example, a block not adhering to a newly introduced rule is broadcast
shortly after soft-fork activation, the updated nodes will reject it while the
old nodes will still accept it. This could cause a chain split between
up-to-date and old nodes - a situation we want to avoid.&lt;/p&gt;
&lt;p&gt;Taproot getting activated meant that certain SegWit version 1
outputs now could only be spent according to the new consensus rules defined
in &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki"&gt;BIP-341&lt;/a&gt;. Before activation, there were no consensus rules on how to spend
these outputs. Bitcoin Core nodes wouldn&amp;rsquo;t accept and relay transactions
spending P2TR (Pay-to-Taproot) outputs to their peers. You could, however,
spend them directly asking a miner to include them, as I&amp;rsquo;ve demonstrated
&lt;a href="https://b10c.me/blog/007-spending-p2tr-pre-activation/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="taproot-activation-livestream"&gt;Taproot Activation Livestream&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve set out to monitor the activation of the Taproot soft-fork scheduled for
block 709632. I set up a machine that runs a Bitcoin Core node, connected it to
a &lt;a href="https://github.com/0xB10C/taproot-activation-observer"&gt;script&lt;/a&gt; I&amp;rsquo;ve written, and streamed the console output it generated.
The left side of the screen showed blocks being mined with, for example, the
pool name, information about the coinbase, and the number of transactions in
the block. The right side showed new taproot transactions (transactions with
P2TR inputs or P2TR outputs) entering my mempool. It showed the transaction
size, feerate, inputs and outputs, and eventual OP_RETURN messages the
transactions contained.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/019-taproot-activation/live-stream-screenshot.png'
alt='Screenshot of the Taproot Activation Livestream'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Screenshot of my Taproot Activation Livestream. Recent blocks on the left and recent taproot transactions on the right.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I expected users to create P2TR outputs in the hours before activation and
broadcast P2TR spending transactions right after block 709631, where the
policy rules changed to allow P2TR spends to be relayed. Then, the next
block should include the first P2TR spends.&lt;/p&gt;
&lt;p&gt;As expected, we saw P2TR output spends being broadcast right after block 709631.
Some of them included an &lt;code&gt;OP_RETURN&lt;/code&gt; message celebrating the activation.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Tj5bc1qmNoQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="Taproot Activation Livestream: First Taproot outputs being spend"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;After 18 minutes and 17 seconds, my node received block 709632 mined by F2Pool.
Taproot is active, and this block could have contained P2TR spends. However,
it didn&amp;rsquo;t. Why? Was there a bug? There were P2TR spends in my mempool, which
should have been included. The YouTube live stream chat&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; started
discussing what could have happened.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/adHBFUCQWpU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="Taproot Activation Livestream: Block 709632 being mined"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;The next block was mined by AntPool about 10 minutes later. It contained five
transactions creating P2TR outputs, but still, no P2TR spends. After 6 minutes
and 30 seconds, AntPool found the next block. Again, this one did not include
any P2TR spends. At this point, there were plenty of P2TR spending transactions
in my mempool, which should have been included. Both pools previously signaled
that they were ready for Taproot and upgraded their nodes.&lt;/p&gt;
&lt;p&gt;Foundry USA mined block 709635 8 minutes and 20 seconds later. It contained all
16 P2TR spending transactions currently in my mempool, which made up 1.69% of all
945 transactions in this block. Foundry including P2TR spends in their block
was a relief for everyone watching. This means miners were able to mine P2TR
spends. However, it was unclear why F2Pool and AntPool hadn&amp;rsquo;t mined them.&lt;/p&gt;
&lt;p&gt;Over the following blocks, it became clear that Poolin, SlushPool, BTC.com,
and Luxor also included P2TR spends, reassuring that the soft-fork
activated as planned. You can find the full live stream recording here:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/1jijKVB1cNA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="Bitcoin Taproot Activation Livestream 🌱"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;h2 id="pools-not-mining-p2tr-spends"&gt;Pools not mining P2TR spends&lt;/h2&gt;
&lt;p&gt;Over the next day, it became clear that F2Pool and AntPool still weren&amp;rsquo;t mining
P2TR spends. It was unclear if this was a technical problem or if they
didn&amp;rsquo;t like Taproot and were purposefully ignoring these transactions. When
asked why F2Pool didn&amp;rsquo;t mine P2TR spends and hasn&amp;rsquo;t upgraded their
infrastructure, the owner of F2Pool &lt;a href="https://twitter.com/satofishi/status/1459868549674065926"&gt;replied&lt;/a&gt; that they &amp;ldquo;Will upgrade soon&amp;rdquo;.
I suspect they had upgraded but didn&amp;rsquo;t know why they weren&amp;rsquo;t mining
P2TR spending transactions.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Looking at the pools that mined more than 3 blocks since taproot activation or included a P2TR spend, it&amp;#39;s clear that F2Pool and AntPool are, very likely, NOT including P2TR spends. F2Pool already mentioned that they will upgrade their infrastructure soon. &lt;a href="https://t.co/qVOwZvWO4q"&gt;pic.twitter.com/qVOwZvWO4q&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1460033874428317696?ref_src=twsrc%5Etfw"&gt;November 14, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Shortly after, an F2Pool employee contacted me to help troubleshoot why they
weren&amp;rsquo;t mining P2TR spends. He told me that they had upgraded their node a
while ago. They asked if I could craft a P2TR spending transaction to test with
&lt;code&gt;testmempoolaccept&lt;/code&gt; on their node. The transaction was accepted by their node and
showed that they indeed upgraded their node. I asked them if they could send me
the output of &lt;code&gt;bitcoin-cli getpeerinfo | jq .[].subver&lt;/code&gt; and if they are
configuring their peers manually. It turned out that they were only connected
to peers version &lt;code&gt;/Satoshi:0.19.1/&lt;/code&gt; or lower. This meant they were ready to mine
P2TR spends but didn&amp;rsquo;t have any peers relaying P2TR spends to them. Only Bitcoin
Core nodes version v0.21.1 or newer relay P2TR spends after activation. Other
versions don&amp;rsquo;t know about the soft-fork and reject these.&lt;/p&gt;
&lt;p&gt;It turned out that F2Pool was running up-to-date Bitcoin Core nodes but had
applied an old, custom patch. This patch disconnected nodes which version string
didn&amp;rsquo;t start with &lt;code&gt;/Satoshi:0.1&lt;/code&gt;. Bitcoin Core nodes with version &lt;code&gt;/Satoshi:0.21.*/&lt;/code&gt;
and &lt;code&gt;/Satoshi:22.0/&lt;/code&gt; were disconnected. It seems like they didn&amp;rsquo;t signal readiness
in bad faith. They just didn&amp;rsquo;t know they weren&amp;rsquo;t ready for Taproot activation
due to a legacy patch they had forgotten about. After fixing this, they mined
their first P2TR spend on November 18th, four days after activation.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Update: F2Pool included their first P2TR spend (&lt;a href="https://t.co/Kr9Z0HXdzn"&gt;https://t.co/Kr9Z0HXdzn&lt;/a&gt;) today in block 710269.&lt;br&gt;&lt;br&gt;F2Pool reached out, and we debugged the issue: Nodes are up-to-date but didn&amp;#39;t have any &amp;gt;=v0.21.1 peers (due to custom patches and addnode) that relayed P2TR spends. That&amp;#39;s fixed now. &lt;a href="https://t.co/Tg9jmIUmFq"&gt;https://t.co/Tg9jmIUmFq&lt;/a&gt; &lt;a href="https://t.co/2y1WtYjt40"&gt;pic.twitter.com/2y1WtYjt40&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1461392912600776707?ref_src=twsrc%5Etfw"&gt;November 18, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;AntPool was now the only pool with a significant number of blocks (107 blocks
mined since activation) that hadn&amp;rsquo;t included a P2TR spending transaction on
the 18th. The communication with them turned out to be not as easy as with
F2Pool. &lt;a href="https://twitter.com/bitentrepreneur"&gt;Alejandro De La Torre&lt;/a&gt; communicated with AntPool. They told him that
only having connections to old peers &amp;ldquo;may be the issue&amp;rdquo;. They were running
v0.21.1 and would soon upgrade to v22.0. It seems like they also weren&amp;rsquo;t
signaling readiness in bad faith.&lt;/p&gt;
&lt;p&gt;AntPool mined their P2TR spend in block 710494 on the November 20th, 2021.
All pools that mined ten or more blocks since taproot activation had now
included at least one P2TR spend. Alejandro and I &lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-November/019608.html"&gt;summarized&lt;/a&gt; this in a
bitcoin-dev mailing list post too.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Update 2: AntPool included their first P2TR spend (&lt;a href="https://t.co/H7FcJnLxRb"&gt;https://t.co/H7FcJnLxRb&lt;/a&gt;) in block 710494. &lt;br&gt;&lt;br&gt;All pools that mined ten or more blocks since taproot activation have included at least one P2TR spend. &lt;a href="https://t.co/oKqj8BHb6H"&gt;https://t.co/oKqj8BHb6H&lt;/a&gt; &lt;a href="https://t.co/jHqCbPRlUS"&gt;pic.twitter.com/jHqCbPRlUS&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1462027738898997249?ref_src=twsrc%5Etfw"&gt;November 20, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h2 id="learnings"&gt;Learnings&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve learned that it&amp;rsquo;s important to communicate that miners do not only need
to upgrade their node but also should make sure you are at least connected
to a few upgraded peers. Direct communication channels between mining pools
and developers are sometimes also helpful.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve received a lot of positive comments regarding the activation stream and
the monitoring I did. I think it&amp;rsquo;s essential to have some monitoring in place,
even if it&amp;rsquo;s only to see that everything went as smoothly as expected. Or, in
case it doesn&amp;rsquo;t, to be able to react to it as soon as possible. And more
generally, documenting soft-fork activations for future Bitcoin developers is a
good habit. This ensures they know what to expect and might not repeat the same
mistakes.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Sadly, the YouTube live-stream chat is lost as live-stream recordings longer
than 12h aren&amp;rsquo;t kept once the stream finishes.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Updates on USDT in Bitcoin Core</title><link>https://b10c.me/talks/009-coredev-usdt/</link><pubDate>Thu, 14 Oct 2021 10:00:00 +0100</pubDate><guid>https://b10c.me/talks/009-coredev-usdt/</guid><description>&lt;p&gt;I updated about my recent work on Userspace, Statically Defined Tracing (USDT) support for Bitcoin Core, and showed examples.
We discussed where debug logging and where USDT makes more sense and security and performance of USDT.
We briefly touched on adding automated testing for the tracepoints.
A few additional tracepoint ideas were proposed by different developers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://b10c.me/blog/008-bitcoin-core-usdt-support/"&gt;Blog post: Userspace, Statically Defined Tracing support for Bitcoin Core&lt;/a&gt; on my blog.&lt;/p&gt;</description></item><item><title>Update on Reorgs on SigNet</title><link>https://b10c.me/talks/008-coredev-signet-reorgs/</link><pubDate>Wed, 13 Oct 2021 11:00:00 +0100</pubDate><guid>https://b10c.me/talks/008-coredev-signet-reorgs/</guid><description>&lt;p&gt;I updated about my recent work on reorgs on SigNet and showed a work-in-progress block tree visualization.
Approach and reorg parameters were discussed.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019413.html"&gt;Discussion: Reorgs on SigNet - Looking for feedback on approach and parameters&lt;/a&gt; on the bitcoin-dev mailing list.&lt;/p&gt;</description></item><item><title>Userspace, Statically Defined Tracing support for Bitcoin Core</title><link>https://b10c.me/blog/008-bitcoin-core-usdt-support/</link><pubDate>Mon, 30 Aug 2021 09:00:00 +0200</pubDate><guid>https://b10c.me/blog/008-bitcoin-core-usdt-support/</guid><description>&lt;p&gt;&lt;em&gt;This report updates on what 0xB10C, &lt;a href="https://blog.coinbase.com/announcing-our-first-bitcoin-core-developer-grants-3d88559db068"&gt;Coinbase Crypto Community Fund grant recipient&lt;/a&gt;,
has been working on over the first half of his year-long Bitcoin development grant.
This specifically covers his work on Userspace, Statically Defined Tracing support
for Bitcoin Core. This report was published on the &lt;a href="https://blog.coinbase.com/userspace-statically-defined-tracing-support-for-bitcoin-core-e4076cd3e07"&gt;Coinbase blog&lt;/a&gt; too.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The reference implementation of the Bitcoin protocol rules, Bitcoin Core, is
the most widely used software to interact with the Bitcoin network. Bitcoin
Core is, however, a black box to most users. While information can be queried
via the RPC interface or searched in the debug log, there is no defined interface
for real-time insights into process internals. Yet, some users could benefit
from more observability into their node. Hobbyists and companies running Bitcoin
Core in production want to include their nodes in their real-time monitoring.
Developers need visibility into test deployments to evaluate, review, debug,
and benchmark changes. Researchers want to observe and analyze the behavior
of nodes on the peer-to-peer network. Exchanges and other services handling
large sums of bitcoin want to detect attacks and other anomalies early.&lt;/p&gt;
&lt;h3 id="peeking-inside-with-userspace-statically-defined-tracing"&gt;Peeking inside with Userspace, Statically Defined Tracing&lt;/h3&gt;
&lt;!--Intro eBPF and how it works--&gt;
&lt;p&gt;The &lt;a href="https://ebpf.io"&gt;eBPF&lt;/a&gt; technology present in the Linux kernel can be used for
observability into userspace applications. The technology allows
running a small, sandboxed program in the Linux kernel, which can hook
into predefined tracepoints in running processes. Once hooked into a
tracepoint, the program is executed each time the tracepoint is reached.
Tracepoints can pass data, for example, application state. Tracing scripts
can further process the data. The practice of hooking into tracepoints in
userspace applications is known as Userspace, Statically Defined Tracing
(USDT). For example, these tracepoints are also included in PostgreSQL,
MySQL, Python, NodeJS, Ruby, PHP, and libraries like libc, libpthread,
and libvirt.&lt;/p&gt;
&lt;!--Why eBPF, in the way we deploy it, is a good fit for Bitcoin Core--&gt;
&lt;p&gt;The static tracepoints can be leveraged by Bitcoin Core users wishing for
more insights into their node. Adding USDT support &lt;a href="https://github.com/bitcoin/bitcoin/pull/19866"&gt;did not require intrusive
changes&lt;/a&gt;, and no custom tooling had to be written. When not used, the performance
impact of the tracepoints is minimal to non-existent. Only privileged processes
can hook into the tracepoints, no information leaks to other processes on the host.
These properties make Userspace, Statically Defined Tracing a good fit for Bitcoin
Core.&lt;/p&gt;
&lt;!-- example tracepoint --&gt;
&lt;p&gt;For example, I &lt;a href="https://github.com/bitcoin/bitcoin/commit/4224dec22baa66547303840707cf1d4f15a49b20"&gt;placed two tracepoints&lt;/a&gt; in the peer-to-peer message handling
code of Bitcoin Core. For each inbound and outbound P2P message, the tracepoints
pass information about the peer, the connection, and the message. This data can
be filtered and processed by tracing scripts. As a demo, I have built a P2P Monitor
that shows the communication between two peers in real-time. Users can find this
script alongside other &lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing"&gt;USDT examples&lt;/a&gt; in the &lt;code&gt;contrib/tracing/&lt;/code&gt; directory of the
Bitcoin Core repository.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Realtime Bitcoin Core P2P monitoring with User-Space, Statically Defined Tracing (USDT) and eBPF.&lt;br&gt;&lt;br&gt;This is one of the examples from &lt;a href="https://t.co/1QGb7jgsnZ"&gt;https://t.co/1QGb7jgsnZ&lt;/a&gt; (with added IP masking). &lt;a href="https://t.co/E5mREYknEm"&gt;pic.twitter.com/E5mREYknEm&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1396889721859715077?ref_src=twsrc%5Etfw"&gt;May 24, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h3 id="use-cases-for-userspace-statically-defined-tracing"&gt;Use-cases for Userspace, Statically Defined Tracing&lt;/h3&gt;
&lt;p&gt;I list some use-cases for Userspace, Statically Defined Tracing
I have thought about or worked on. With only three tracepoints merged,
there is plenty of room for developers to add new tracepoints and
get creative with tracing scripts. &lt;a href="https://github.com/bitcoin/bitcoin/issues/20981"&gt;Issue #20981&lt;/a&gt; contains discussion
and ideas for additional tracepoints that can be implemented.&lt;/p&gt;
&lt;p&gt;Researchers and developers can use the P2P message tracepoints to
monitor P2P network anomalies in real-time. One example could be
detecting the recent &lt;code&gt;addr&lt;/code&gt; message flooding as reported in this
&lt;a href="https://bitcointalk.org/index.php?topic=5348856.0"&gt;bitcointalk.org post&lt;/a&gt;. The messages were announcing random IP
addresses not belonging to nodes on the Bitcoin network. The flooding
has been &lt;a href="https://arxiv.org/abs/2108.00815"&gt;covered&lt;/a&gt; in detail by Grundmann and Baumstark. They
discuss that the attacker could obtain the number of connected peers and
learn about other addresses, including Tor addresses, the node is listening
on. This would reduce the privacy of the node operator. It&amp;rsquo;s important to
stay vigilant to these attacks, discuss them, and then, if needed, react
to them.&lt;/p&gt;
&lt;p&gt;Similarly, I have been instrumenting the Bitcoin Core network address manager
with tracepoints. The addrman keeps track of gossiped network addresses for
potential outbound peers connections a node makes. It&amp;rsquo;s designed to be resiliant
against &lt;a href="https://cs-people.bu.edu/heilman/eclipse/"&gt;Eclipse Attacks&lt;/a&gt;, where a node only has connections to peers controlled
by the attacker. The attacker can choose which information to feed to the node,
enabling, for example, double-spending attacks. Information about the addresses
in the addrman might help detect the build-up of an eclipse attack when combined
with other data.&lt;/p&gt;
&lt;p&gt;Additionally, these addrman tracepoints can be helpful during debugging and
code review. To showcase this, I build a tool that visualizes the addresses
in the addrman data structure based on the data submitted to the tracepoints.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Quiz: Which Bitcoin Core data-structure does this visualization show? &lt;a href="https://t.co/U6cnHBFWWT"&gt;pic.twitter.com/U6cnHBFWWT&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1407019872681332742?ref_src=twsrc%5Etfw"&gt;June 21, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;!-- prometheus monitoring --&gt;
&lt;p&gt;A Prometheus metric exporter can also build on top of the tracepoints
without requiring additional code in Bitcoin Core. There already exist
RPC-based Prometheus exporters and projects like &lt;a href="https://statoshi.info/"&gt;Statoshi&lt;/a&gt;. However,
RPC-based exporters are limited by the information exposed via the RPC
interface, and Statoshi is large a patch-set that requires maintenance
on each Bitcoin Core release. I have published an experimental USDT-based
exporter called &lt;a href="https://github.com/0xb10c/bitcoind-observer"&gt;bitcoind-observer&lt;/a&gt; that hooks into the three currently
merged tracepoints and serves metrics in the Prometheus format. The exporter
can be used by everyone currently running a Bitcoin Core node compiled with
USDT support. A demo is available on &lt;a href="https://bitcoind.observer"&gt;bitcoind.observer&lt;/a&gt;. I&amp;rsquo;ve recently used
this to benchmark the bandwidth usage of &lt;a href="https://github.com/bitcoin/bitcoin/pull/21515"&gt;an implementation&lt;/a&gt; of &lt;a href="https://arxiv.org/abs/1905.10518"&gt;Erlay:
Bandwidth-Efficient Transaction Relay for Bitcoin&lt;/a&gt;. Initial results can be
found &lt;a href="https://github.com/naumenkogs/txrelaysim/issues/8#issuecomment-903255752"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The already existing tracepoint &lt;code&gt;validation:block_connected&lt;/code&gt; can be used
to benchmarking block validation. This allows, for example, to compare
the initial block download performance between different patches and can
aid in detecting performance improvements and regressions. For example,
the &lt;a href="https://github.com/chaincodelabs/bitcoinperf"&gt;bitcoinperf&lt;/a&gt; project might benefit from such tracepoints. I&amp;rsquo;ve used
the tracepoint to &lt;a href="https://github.com/bitcoin/bitcoin/pull/22702#issuecomment-900662089"&gt;benchmark&lt;/a&gt; Martin Ankerls pull request &lt;a href="https://github.com/bitcoin/bitcoin/pull/22702"&gt;#22702&lt;/a&gt;. If
merged, the changes he proposes would result in a substantial block
validation speed up and reduction in memory usage.&lt;/p&gt;
&lt;h3 id="next-steps"&gt;Next steps&lt;/h3&gt;
&lt;p&gt;I will collect further ideas for tracepoints and implement them alongside
example tracing scripts and more tooling. This will also involve
communicating with other Bitcoin and Bitcoin Core developers about
which tracepoints could be helpful in their projects. An example is
Antoine Riard&amp;rsquo;s &lt;a href="https://github.com/bitcoin/bitcoin/pull/18987"&gt;cross-layer anomaly detection watchdog&lt;/a&gt; which he
initially proposed as a new, internal module to Bitcoin Core. However,
many of the required events and metrics can be collected by hooking into
tracepoints. This means the watchdog could be an external runtime, which
would speed up the watchdog development and requires less code and
maintenance on the Bitcoin Core side.&lt;/p&gt;
&lt;p&gt;If everything goes according to plan, the v23.0 release of Bitcoin Core,
expected in early 2022, will include the first set of tracepoints. A
goal is to enable USDT support in release builds by default, which still needs
some work. Additionally, the tracepoint API should be semi-stable and thus
needs testing.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;In short&lt;/strong&gt;: I have been adding tracepoints to Bitcoin Core that users can hook
into to get insights into the internal state. The tracepoints are based on Linux
kernel technology and do not require intrusive changes or custom tooling. The
groundwork is done. Now further tracepoints can be added, and tooling can be written.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Different sources of funding are important. I accept community donations for my work &lt;a href="https://b10c.me/projects"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Contribution: Bitcoin Core Project</title><link>https://b10c.me/projects/contribution-bitcoin-core/</link><pubDate>Wed, 28 Jul 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/contribution-bitcoin-core/</guid><description>&lt;p&gt;I list my contributions to the Bitcoin Core project and detail their context and
background. Of course, not all contributions are worth mentioning here.&lt;/p&gt;
&lt;p&gt;Contributing to the &lt;a href="https://github.com/bitcoin/bitcoin"&gt;Bitcoin Core project&lt;/a&gt; is not limited to opening pull
requests which add or change code. Even more, and arguably most important, is
reviewing existing pull requests. The review helps discovering issues and
potential problems. Acknowledging a pull request once all issues are addressed
helps finding consensus on a proposed change. Bitcoin Core contributor &lt;a href="https://github.com/jonatack"&gt;Jon
Atack&lt;/a&gt; thinks that a good ratio is to &lt;a href="https://jonatack.github.io/articles/on-reviewing-and-helping-those-who-do-it#what-to-do"&gt;review 5 to 15 pull requests&lt;/a&gt;
for each pull request one opens.&lt;/p&gt;
&lt;h4 id="pr-22006-tracing-first-tracepoints-and-documentation-on-user-space-statically-defined-tracing-usdt-by-0xb10c"&gt;&lt;a href="https://github.com/bitcoin/bitcoin/pull/22006"&gt;PR #22006&lt;/a&gt;: tracing: first tracepoints and documentation on User-Space, Statically Defined Tracing (USDT) by &lt;a href="https://github.com/0xb10c"&gt;@0xB10C&lt;/a&gt;&lt;/h4&gt;
&lt;h5 id="merged-on-july-26th-2021"&gt;merged on July 26th, 2021&lt;/h5&gt;
&lt;p&gt;This PR adds documentation for User-Space, Statically Defined Tracing (USDT) as
well as three tracepoints (including documentation and usage examples). We can
hook into these tracepoints via a Linux kernel technology called &lt;a href="https://ebpf.io"&gt;eBPF&lt;/a&gt;. The
data passed into these tracepoints can then be processed with, for example,
&lt;a href="https://github.com/iovisor/bpftrace"&gt;bpftrace&lt;/a&gt;, or &lt;a href="https://github.com/iovisor/bcc"&gt;bcc&lt;/a&gt; scripts (in Python or Rust).&lt;/p&gt;
&lt;p&gt;I add three tracepoints. Two tracepoints for inbound and outbound P2P messages
and a tracepoint for the block connection function. The Bitcoin Core USDT
support is documented &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md"&gt;here&lt;/a&gt; and tracing script examples are listed
&lt;a href="https://github.com/bitcoin/bitcoin/tree/master/contrib/tracing"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="pr-19643-add--netinfo-peer-connections-dashboard-by-jon-atack"&gt;&lt;a href="https://github.com/bitcoin/bitcoin/pull/19643"&gt;PR #19643&lt;/a&gt;: Add -netinfo peer connections dashboard by &lt;a href="https://github.com/jonatack"&gt;Jon Atack&lt;/a&gt;&lt;/h4&gt;
&lt;h5&gt;&lt;/h5&gt;
&lt;p&gt;A Bitcoin Core node communicates with other nodes over a peer-to-peer network.
Nodes open outbound connections to other nodes, and some nodes allow inbound
connections. The connection management happens under the hood. A user can query
the information about the current peer-to-peer connections with the
&lt;code&gt;getpeerinfo&lt;/code&gt; RPC. It returns a JSON formatted response containing detailed
information about the connection to each peer. This JSON response is intended to
be machine-readable and does not provide a quick overview over the open
connections for developers and node operators. A script reformatting the RPC
response would be needed to produce a dashboard like overview out of the JSON
response.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://github.com/bitcoin/bitcoin/pull/19643"&gt;PR #19643&lt;/a&gt; by &lt;a href="https://github.com/jonatack"&gt;Jon Atack&lt;/a&gt; comes into play.
It extends &lt;code&gt;bitcoin-cli&lt;/code&gt; with a &lt;code&gt;-netinfo&lt;/code&gt; command. The command wraps the
&lt;code&gt;getnetworkinfo&lt;/code&gt; and the &lt;code&gt;getpeerinfo&lt;/code&gt; RPCs and displays information about the
inbound and outbound connections. In its current form, the command takes a
numerical argument ranging from zero to four. With zero it only displays the
connection counts grouped by in- and outbound connections. Passing one displays
a dashboard with detailed information about the connections, and passing four
extends this information with both the IP address and the version of the
connected peer.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ./src/bitcoin-cli -testnet -netinfo 1
Bitcoin Core v0.20.99.0-bf1f913c4 testnet - 70016/Satoshi:0.20.99/
Peer connections sorted by direction and min ping
&amp;lt;-&amp;gt; relay net mping ping send recv txn blk uptime id
out full ipv4 13 13 13 13 0 16 12
out block ipv4 16 19 35 35 16 15
out full ipv4 16 16 13 23 2 2 17 6
out block ipv4 32 32 36 36 16 14
out full ipv4 33 34 12 28 0 17 4
out full ipv4 34 34 13 25 0 16 10
out full ipv4 34 35 13 28 0 17 1
out full ipv4 38 38 12 25 1 17 5
out full ipv4 158 233 25 8 1 16 13
out full ipv4 174 236 21 12 9 16 7
ms ms sec sec min min min
ipv4 ipv6 onion total block-relay
in 0 0 0 0 0
out 10 0 0 10 2
total 10 0 0 10 2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This resolves the need for writing a custom script. The change itself only
affects &lt;code&gt;bitcoin-cli&lt;/code&gt; and not &lt;code&gt;bitcoind&lt;/code&gt;, which is neat. By running
&lt;code&gt;watch bitcoin-cli -netinfo 4&lt;/code&gt; a detailed dashboard is shown, which updates
itself every other second. Jon&amp;rsquo;s PR can only be used with Bitcoin Core
version v0.21.0 or higher as an extra field was added to the response to the
&lt;code&gt;getpeerinfo&lt;/code&gt; RPC in an earlier PR. I&amp;rsquo;ve archived a branch where it&amp;rsquo;s possible
to use the &lt;code&gt;netinfo&lt;/code&gt; command with older &lt;code&gt;bitcoind&lt;/code&gt; versions. This is available
&lt;a href="https://github.com/0xB10C/bitcoin/tree/jonatacks-netinfo-pre-v0.20.99"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="pr-18172-test-transaction-expiry-from-mempool-by-0xb10c"&gt;&lt;a href="https://github.com/bitcoin/bitcoin/pull/18172"&gt;PR #18172&lt;/a&gt;: test: Transaction expiry from mempool by &lt;a href="https://github.com/0xb10c"&gt;@0xB10C&lt;/a&gt;&lt;/h4&gt;
&lt;h5 id="merged-on-18th-february-2020"&gt;merged on 18th February 2020&lt;/h5&gt;
&lt;p&gt;There are multiple reasons a transaction can be removed from the mempool.
The two most common removal reasons are transaction confirmation and transaction replacement.
Transactions that remain in the mempool for a long time are probably not attractive to miners.
By default, these are removed after 336 hours (two weeks).
This default timeout can be overwritten with the &lt;code&gt;-mempoolexpiry=&amp;lt;n&amp;gt;&lt;/code&gt; command-line argument where &lt;code&gt;&amp;lt;n&amp;gt;&lt;/code&gt; specifies the timeout in hours.
This functionality is important as we don&amp;rsquo;t want our mempool to fill up with transactions that are unattractive to miners (either because they pay a low fee or through other quirks).
Having a test for this behavior is important to make sure that the functionally does not break.&lt;/p&gt;
&lt;p&gt;In &lt;a href="pr18172-2"&gt;PR #18172&lt;/a&gt; I&amp;rsquo;ve added a test for the mempool expiry behavior.
This test tests both the default and the user-definable timeout.
A first transaction called &lt;em&gt;parent&lt;/em&gt; is added to our test nodes mempool and its entry time is recorded.
Then the time is forwarded using &lt;code&gt;setmocktime()&lt;/code&gt;.
This simulates the test nodes time and conveniently allows for testing time-dependent functionality without having to wait until that time passes.
A second transaction called &lt;em&gt;child&lt;/em&gt; which spends an output from the first transaction is broadcasted to the test node after the first half of the expiry timeout.
Then the time is forwarded to five seconds before the expiry of the &lt;em&gt;parent&lt;/em&gt; transaction.
The &lt;em&gt;parent&lt;/em&gt; transaction &lt;strong&gt;should&lt;/strong&gt; still be in our test nodes mempool.
Five seconds after the expiry timeout the &lt;em&gt;parent&lt;/em&gt; transaction &lt;strong&gt;should not&lt;/strong&gt; be in our mempool anymore.
As the &lt;em&gt;child&lt;/em&gt; transaction depends on the removed &lt;em&gt;parent&lt;/em&gt; transaction it becomes invalid and &lt;strong&gt;should&lt;/strong&gt; be removed as well.&lt;/p&gt;</description></item><item><title>On anyone-can-spend Pay-to-Taproot outputs before activation</title><link>https://b10c.me/blog/007-spending-p2tr-pre-activation/</link><pubDate>Fri, 23 Jul 2021 10:24:20 +0200</pubDate><guid>https://b10c.me/blog/007-spending-p2tr-pre-activation/</guid><description>&lt;p&gt;While working on taproot support for &lt;a href="https://transactionfee.info"&gt;transactionfee.info&lt;/a&gt;, it became apparent
that there already exist a few &lt;a href="https://transactionfee.info/charts/outputs-p2tr/"&gt;Pay-to-Taproot (P2TR) outputs on mainnet&lt;/a&gt;.
Anyone can spend these outputs, but they are non-standard and thus not relayed
between nodes in the Bitcoin peer-to-peer network. However, a mining pool can
include non-standard transactions in their block.
We cover the six P2TR outputs already present on mainnet and explain
why the outputs can be spent by anyone before taproot activates. With the help
of the &lt;a href="https://f2pool.com"&gt;f2pool.com&lt;/a&gt; mining pool, four P2TR outputs are donated to &lt;a href="https://brink.dev"&gt;brink.dev&lt;/a&gt;,
a non-profit company focused on supporting open-source Bitcoin development.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki"&gt;Taproot&lt;/a&gt; is a softfork upgrade to the Bitcoin protocol and tightens the
consensus rules. A softfork forbids something that was previously allowed
once it activates. Taproot restricts how native SegWit outputs, with a witness
version of 1 and a 32-byte witness program, can be spent. The spending rules for
other witness versions, nested SegWit, and native SegWit outputs with a witness
version of 1, but longer or shorter witness programs are unaffected.&lt;/p&gt;
&lt;h4 id="existing-p2tr-outputs-on-bitcoin-mainnet"&gt;Existing P2TR outputs on Bitcoin mainnet&lt;/h4&gt;
&lt;p&gt;While it is possible to create P2TR outputs before taproot activates, it is not
recommended to do so. Likewise, &lt;a href="https://bitcoinops.org/en/newsletters/2021/07/21/"&gt;wallets shouldn&amp;rsquo;t generate P2TR addresses
before activation&lt;/a&gt;. Nonetheless, at the time of writing, there are already six
P2TR outputs on Bitcoin mainnet.&lt;/p&gt;
&lt;p&gt;The first P2TR output was created on December 17, 2019, in a withdrawal
&lt;a href="https://blockstream.info/tx/b53e3bc5edbb41b34a963ecf67eb045266cf841cab73a780940ce6845377f141?output:0"&gt;transaction&lt;/a&gt; from purse.io. Matthew Zipkin initiated a withdrawal
of 5431 sat to a witness version 1 bech32 address to test sending support for
witness version 1. He &lt;a href="https://github.com/bitcoinops/bitcoinops.github.io/pull/303"&gt;added&lt;/a&gt; his test results to the &lt;a href="https://bitcoinops.org/en/compatibility/"&gt;Bitcoin Optech
Compatibility Matrix&lt;/a&gt; and provided a &lt;a href="https://github.com/pinheadmz/bitcoinops.github.io/raw/2164c46765cc6fe0c223cc05c19799a683beb178/img/compatibility/purse/segwit/send-v1.png"&gt;screenshot&lt;/a&gt; showing the withdrawal on the
purse.io frontend. The public key encoded in the address is
&lt;code&gt;010​10101​01010101​010​101010101010​10​10​101010101​0101010101​0​101010​101&lt;/code&gt;, which is
likely not generated from a private key. It is to be assumed that the private
key to this public key is unknown, and the funds won&amp;rsquo;t be spendable once taproot
activates.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://blockstream.info/tx/86b161e8d06837fb2b34e177c728a8141f52821ef66e088a1e670d79a128c352?output:0"&gt;second P2TR output&lt;/a&gt; was created on January 28th, 2020. With a value of 700
sat, it is close to the default SegWit dust threshold of 294 sat. The creator of
this output is unknown.&lt;/p&gt;
&lt;p&gt;The third, fourth, and fifth P2TR outputs are test outputs created while
checking send support to a witness v1 bech32 addresses that Pieter Wuille
&lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-October/018254.html"&gt;posted&lt;/a&gt; to the bitcoin-dev mailing list on October 19th, 2020.
The &lt;a href="https://blockstream.info/nojs/tx/b48a59fa9e036e997ba733904f631b1a64f5274be646698e49fd542141ca9404?expand&amp;amp;output:0"&gt;third P2TR output&lt;/a&gt; has a value of 3656 sat and was created by
&lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-October/018257.html"&gt;Riccardo Casatta using the Blockstream Aqua wallet&lt;/a&gt;. The &lt;a href="https://blockstream.info/tx/7641c08f4bd299abfef26dcc6b477938f4a6c2eed2f224d1f5c1c86b4e09739d?expand&amp;amp;output:1"&gt;fourth P2TR output&lt;/a&gt;
with a value of 50.000 sat was likely created by &lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-November/018268.html"&gt;Mike Schmidt using the
BRD wallet&lt;/a&gt;. It&amp;rsquo;s unknown who created the &lt;a href="https://blockstream.info/tx/fd43650477e0ba6825ae0482a8b0b2b509d5443fa2a8cdd101872752a9171dd6?expand&amp;amp;output:1"&gt;fifth P2TR output&lt;/a&gt;
with a value of 100.000 sat.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://blockstream.info/tx/b7e3c981b06983cdb0082dd8e386ce6715805a75e1b8b3ce2df45c6e48172b6a?expand&amp;amp;output:1"&gt;sixth P2TR output&lt;/a&gt;, with a value of 1324 sat, was created on July
7th, 2021. It is the only P2TR output created after taproot locked in on
June 13th, 2021. The other P2TR outputs were created while it was still unclear
when or if taproot would activate.&lt;/p&gt;
&lt;h4 id="why-can-p2tr-outputs-currently-be-spent-by-anyone"&gt;Why can P2TR outputs currently be spent by anyone?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; (added on 2021-07-24): This post mentions &lt;em&gt;anyone-can-spend&lt;/em&gt; outputs.
While this term is commonly used to describe outputs with not yet enforced
spending rules, it has been the source for a lot of misinformation about SegWit
in the past &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. A better name might be &lt;em&gt;&amp;ldquo;outputs with not yet consensus
enforced spending rules&amp;rdquo;&lt;/em&gt; or &lt;em&gt;&amp;ldquo;unenforced outputs before activation&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Before taproot activates, these six P2TR outputs can be spent by anyone. However,
none of these P2TR outputs have been spent yet, as the spending transaction is
currently considered non-standard. Non-standard transactions aren&amp;rsquo;t relayed on
the Bitcoin network &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Bitcoin Core checks each input for standardness in the
&lt;code&gt;AreInputsStandard&lt;/code&gt; function before accepting a transaction to the mempool. If
the &lt;a href="https://github.com/bitcoin/bitcoin/blob/bd2f4164c6be0cc20af44c57b93f87c879c97729/src/policy/policy.cpp#L186-L189"&gt;spend UTXO is a P2TR output&lt;/a&gt;, the function currently returns &lt;code&gt;false&lt;/code&gt;,
indicating that the transaction has non-standard inputs. Nodes don&amp;rsquo;t accept the
transaction to their mempool and it is not relayed to peers.&lt;/p&gt;
&lt;p&gt;However, a non-standard transaction can still be valid when directly included in
a block. Next to other checks, the transaction has to pass the script
verification. Standardness is not checked. In Bitcoin Core, the &lt;a href="https://github.com/bitcoin/bitcoin/blob/bd2f4164c6be0cc20af44c57b93f87c879c97729/src/script/interpreter.cpp#L1937-L1985"&gt;&lt;code&gt;VerifyScript&lt;/code&gt;&lt;/a&gt;
function is responsible for script verification. The script verification for
native SegWit spends consists of two parts: the script evaluation and the
verification of the witness program.&lt;/p&gt;
&lt;p&gt;During script evaluation, the &lt;code&gt;scriptSig&lt;/code&gt; is evaluated first. The resulting
stack is used as the initial stack state when evaluating the &lt;code&gt;scriptPubKey&lt;/code&gt; from
the UTXO referenced in the input. The script evaluation succeeds if it finishes
without failing, the stack is not empty, and the top stack element casts to
&lt;code&gt;true&lt;/code&gt; (is not zero). When spending native SegWit outputs like, for example, P2TR
outputs, the &lt;code&gt;scriptSig&lt;/code&gt; is empty. The &lt;code&gt;scriptPubKey&lt;/code&gt; contains the witness
version and the witness program. As the &lt;code&gt;scriptSig&lt;/code&gt; is empty, an empty stack is
used for the &lt;code&gt;scriptPubKey&lt;/code&gt; evaluation. There, the witness version and witness
program are pushed onto the stack. The script evaluation finishes. As the stack
isn&amp;rsquo;t empty and the top stack element is the witness program (which usually
isn&amp;rsquo;t zero), the script evaluation will succeed. This behavior makes SegWit a
softfork. SegWit transactions are valid for nodes without SegWit support as the
top stack element is not empty.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/007-p2tr-output-spending/p2tr-keypath-spend.png'
alt='UTXO and input in an P2TR keypath spend'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
&lt;p&gt;A keypath P2TR spend that will be valid and standard once taproot activates.
A anyone-can-spend P2TR spend will likely have an empty witness, but otherwise
look the same.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As a next step, the witness program is verified if the witness version is known.
If the witness version is unknown, the witness program &lt;a href="https://github.com/bitcoin/bitcoin/blob/bd2f4164c6be0cc20af44c57b93f87c879c97729/src/script/interpreter.cpp#L1931-L1932"&gt;is treated as valid&lt;/a&gt; &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.
One of the first checks during verification of version 1 witness programs (i.e.
when spending P2TR outputs) is if the &lt;code&gt;SCRIPT_VERIFY_TAPROOT&lt;/code&gt; script verification
flag is set. &lt;a href="https://github.com/bitcoin/bitcoin/blob/bd2f4164c6be0cc20af44c57b93f87c879c97729/src/script/interpreter.cpp#L1887"&gt;If unset, the verification succeeds&lt;/a&gt; before checking the witness
program. This flag is only set &lt;a href="https://github.com/bitcoin/bitcoin/blob/bd2f4164c6be0cc20af44c57b93f87c879c97729/src/validation.cpp#L1919-L1922"&gt;when taproot is active&lt;/a&gt;. This causes
taproot outputs to be anyone-can-spend before activation. The witness program
isn&amp;rsquo;t verified, and the witness can be left empty.&lt;/p&gt;
&lt;h4 id="spending-p2tr-outputs-before-activation"&gt;Spending P2TR outputs before activation&lt;/h4&gt;
&lt;p&gt;We demonstrate the spending of P2TR outputs before the taproot softfork activates
by constructing a non-standard transaction that is consensus valid. The mining
pool &lt;a href="https://f2pool.com"&gt;f2pool.com&lt;/a&gt; helps by including the non-standard transaction in a block.&lt;/p&gt;
&lt;p&gt;For our transaction, we pick the first, third, fourth, and fifth P2TR output out
of the six available outputs discussed earlier. The first output with a value of
5.431 sat will be unspendable upon taproot activation and would otherwise remain
in the UTXO-set forever. The third (3.656 sat), fourth (50.000 sat), and fifth
(100.000 sat) P2TR output were sent in test transactions with the knowledge that
the funds will most likely be lost or donated to charity. This allows us to
spend a total of 159.087 sat. We leave the other two P2TR outputs (700 sat and
1.324 sat) for someone else to spend either before or after activation.&lt;/p&gt;
&lt;p&gt;Our transaction contains two outputs. The first output donates the full input
amount of 159.087 sat (about 50 USD at the time of writing) to &lt;a href="https://brink.dev"&gt;brink.dev&lt;/a&gt; to
support open-source Bitcoin development. The transaction purposefully doesn&amp;rsquo;t
pay a miner fee to maximize the donation amount. The second output is an
OP_RETURN output with a link to this blog post. This makes it possible for
someone finding the anyone-can-spend transaction to learn more about why the
P2TR outputs were spendable before Taproot activation.&lt;/p&gt;
&lt;p&gt;We constructed the transaction with a quick and dirty Rust &lt;a href="https://gist.github.com/0xB10C/edc6e50ce755d4e0e5f9f4a1c2a7d8d0"&gt;script&lt;/a&gt;. For Bitcoin
Core to accept the non-standard transaction, &lt;a href="https://gist.github.com/0xB10C/e95363e041458074aa7a8ee122d16df2"&gt;this patch&lt;/a&gt; had to be applied to
&lt;a href="https://github.com/bitcoin/bitcoin/tree/0.21"&gt;v0.21.1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The transaction &lt;code&gt;b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41&lt;/code&gt;
was included in block
&lt;code&gt;0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad&lt;/code&gt;
(height 692261) mined by &lt;a href="https://f2pool.com"&gt;f2pool.com&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on &lt;a href="https://blockstream.info/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41?expand"&gt;blockstream.info&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;on &lt;a href="https://mempool.space/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41"&gt;mempool.space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;on &lt;a href="https://explorer.btc.com/btc/transaction/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41"&gt;explorer.btc.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;on &lt;a href="https://oxt.me/transaction/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41"&gt;oxt.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;on &lt;a href="https://blockchair.com/bitcoin/transaction/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41"&gt;blockchair.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;on &lt;a href="https://www.blockchain.com/btc/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41"&gt;blockchain.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
Show decoded transaction
&lt;/summary&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;txid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;hash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vsize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;weight&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;936&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;locktime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45324&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;txid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;b53e3bc5edbb41b34a963ecf67eb045266cf841cab73a780940ce6845377f141&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptSig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sequence&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6817749&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;txid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;b48a59fa9e036e997ba733904f631b1a64f5274be646698e49fd542141ca9404&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptSig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sequence&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45324&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;txid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;7641c08f4bd299abfef26dcc6b477938f4a6c2eed2f224d1f5c1c86b4e09739d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptSig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sequence&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45324&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;txid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fd43650477e0ba6825ae0482a8b0b2b509d5443fa2a8cdd101872752a9171dd6&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptSig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sequence&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45324&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00159087&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptPubKey&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OP_HASH160 fb94f9a556bae3f98f44186e1ccaa4f5ff6a3187 OP_EQUAL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;a914fb94f9a556bae3f98f44186e1ccaa4f5ff6a318787&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reqSigs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;scripthash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;addresses&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;3QdFzfpU3u92GeRN4U5pLLezbBaHbj2ppr&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scriptPubKey&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;asm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OP_RETURN 68747470733a2f2f623130632e6d652f37&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;6a1168747470733a2f2f623130632e6d652f37&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nulldata&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;details class="m-3"&gt;
&lt;summary&gt;
Show raw transaction
&lt;/summary&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010000000441f1775384e60c9480a773ab1c84cf665204eb67cf3e964ab341bbedc53b3eb50000000000d50768000494ca412154fd498e6946e64b27f5641a1b634f9033a77b996e039efa598ab400000000000cb100009d73094e6bc8c1f5d124f2d2eec2a6f43879476bcc6df2feab99d24b8fc0417601000000000cb10000d61d17a952278701d1cda8a23f44d509b5b2b0a88204ae2568bae077046543fd01000000000cb10000026f6d02000000000017a914fb94f9a556bae3f98f44186e1ccaa4f5ff6a3187870000000000000000136a1168747470733a2f2f623130632e6d652f370cb10000&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; (added on 2021-07-24): There is a downside to including a transaction
spending P2TR outputs in a block before the taproot softfork activates. A few
months or years after the softfork has activated, the activation height becomes
fact and can be hardcoded into Bitcoin Core &lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. If there would have been no
spends from P2TR outputs on-chain before activation, the taproot activation
height would have been irrelevant. The taproot rules could have been enforced
starting with the Genesis block (or maybe starting at the SegWit activation).
However, with the P2TR outputs spending transaction included in a block, the
activation height needs to be kept as part of the consensus logic. This slightly
increases the software complexity of Bitcoin nodes.&lt;/p&gt;
&lt;h4 id="changes-on-taproot-activation"&gt;Changes on taproot activation&lt;/h4&gt;
&lt;!-- # topic: what changes on activation --&gt;
&lt;p&gt;When taproot activates on block 709632, Bitcoin Core v0.21.1 and newer
will start enforcing the taproot rules. Spending P2TR will only be possible
by supplying a valid signature for the keypath spend or a by satisfying the
script requirements for a scriptpath spend. However, older nodes that
don&amp;rsquo;t know about the taproot softfork will continue to treat the P2TR spends as
anyone-can-spend. This can become a problem if, for example, a mining pool
forgets to upgrade and includes a transaction spending from a P2TR output
without satisfying the taproot rules enforced by the network. The chain would
split between upgraded nodes enforcing taproot and not upgraded nodes.
It is recommended to upgrade production and payment handling nodes before
taproot activates.&lt;/p&gt;
&lt;!-- topic: Summary --&gt;
&lt;p&gt;&lt;em&gt;I like to thank &lt;a href="https://f2pool.com"&gt;f2pool.com&lt;/a&gt; for their cooperation. Without them including
the non-standard transaction in a block, donating the P2TR outputs to
&lt;a href="https://brink.dev"&gt;brink.dev&lt;/a&gt;, wouldn&amp;rsquo;t have been possible. You can support me and my work
&lt;a href="https://b10c.me/projects"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Some SegWit opponents claimed that the SegWit softfork could be reversed
after activation and the outputs would then be anyone-can-spend. To learn more
about this I recommend reading &lt;a href="https://blog.bitmex.com/the-blocksize-war-chapter-5-scaling-ii-segwit/"&gt;The Blocksize War – Chapter 5 – SegWit&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;A good summary on non-standard transactions and forward compatibility can
be found in &lt;a href="https://news.ycombinator.com/item?id=27934728"&gt;this comment&lt;/a&gt; by nullc on Hacker News.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;This allows introducing of new rules to currently unused witness versions
via a future softforks. The witness program verification succeeds at nodes
unaware of the softfork.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;See, for example, the notes from the Bitcoin Core PR Review Club on
&lt;a href="https://bitcoincore.reviews/19438"&gt;#19438: Introduce deploymentstatus&lt;/a&gt; for more information.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Summer of Bitcoin 2021</title><link>https://b10c.me/projects/018-summerofbitcoin-2021/</link><pubDate>Mon, 28 Jun 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/018-summerofbitcoin-2021/</guid><description>&lt;p&gt;This is a placeholder for the Summer of Bitcoin mentoring I did.
I plan to fill this in, once I get the time to do so.&lt;/p&gt;</description></item><item><title>bitcoind-observer</title><link>https://b10c.me/projects/017-bitcoind-observer/</link><pubDate>Sun, 27 Jun 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/017-bitcoind-observer/</guid><description>&lt;p&gt;My experimental bitcoind-observer tool is a Bitcoin Core Prometheus metrics
exporter utilizing and demonstrating the newly added tracepoints in
Bitcoin Core.&lt;/p&gt;
&lt;p&gt;The idea for a Bitcoin Core Prometheus metrics exporter based on the new
tracepoints originated in a discussion on potential tracepoints between
&lt;a href="https://jb55.com"&gt;jb55&lt;/a&gt; and &lt;a href="https://github.com/laanwj"&gt;laanwj&lt;/a&gt; in the Bitcoin Core issue &lt;a href="https://github.com/bitcoin/bitcoin/issues/20981"&gt;20981&lt;/a&gt;. Similar tools exist,
for example, &lt;a href="https://lopp.net"&gt;Jameson Lopp&amp;rsquo;s&lt;/a&gt; website &lt;a href="https://statoshi.info"&gt;statoshi.info&lt;/a&gt;. However,
one downside with Statoshi is the patch-set that has to be rebased with every
release. This can be cumbersome work if there are, for example, refactors in
Bitcoin Core.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
&lt;a href="https://jb55.com"&gt;jb55&lt;/a&gt;: I was looking at this a long time ago. In theory you could get the same functionality as statoshi (stats gathering via prometheus) with https://github.com/cloudflare/ebpf_exporter Would be an interesting project.
&lt;a class="text-muted" href="https://github.com/bitcoin/bitcoin/issues/20981#issuecomment-767170859"&gt; source&lt;/a&gt;
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;a href="https://github.com/laanwj"&gt;laanwj&lt;/a&gt;: I agree! This would be my preferred way to do this kind of reporting, to be honest. It'd flexible and built into the operating system. It has low impact on the code and low impact on performance. It could potentially work for all software.
&lt;br&gt;
&lt;br&gt;
One reason Statoshi never got merged is because it's just too much hassle to integrate and maintain a Bitcoin Core specific framework for this. Not because it's not a great thing to have.
&lt;a class="text-muted" href="https://github.com/bitcoin/bitcoin/issues/20981#issuecomment-767493889"&gt; source&lt;/a&gt;
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;The tracepoints in Bitcoin Core have a &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#semi-stable-api"&gt;semi-stable&lt;/a&gt; API.
Upgrading applications that utilize the tracepoints to a new Bitcoin Core
release should be easy. The tracepoints are flexible enough that we
don&amp;rsquo;t instrument Bitcoin Core with an additional interface for a single
application. The tracepoints can be used in debugging, review, for education
about internals, monitoring, and data collection in small and large projects.&lt;/p&gt;
&lt;p&gt;The bitcoind-observer tool attaches to multiple tracepoints in Bitcoin Core and
transforms the tracepoint arguments to Prometheus metrics. It uses the bcc (BPF
compiler collection) Rust &lt;a href="https://crates.io/crates/bcc"&gt;crate&lt;/a&gt; to interact with BPF. For me, it also serves
as a test bed for working with the tracepoints in Rust and for evaluating newly
proposed tracepoints.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;I build an experimental Bitcoin Core Prometheus metrics exporter based on the eBPF and USDT tracepoints I add in PR #22006 (&lt;a href="https://t.co/1QGb7jgsnZ"&gt;https://t.co/1QGb7jgsnZ&lt;/a&gt;).&lt;a href="https://t.co/RTLdhCLEf6"&gt;https://t.co/RTLdhCLEf6&lt;/a&gt;&lt;br&gt;&lt;br&gt;Grafana demo available on &lt;a href="https://t.co/uEgE7d7Z7q"&gt;https://t.co/uEgE7d7Z7q&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1419992148414537728?ref_src=twsrc%5Etfw"&gt;July 27, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;!-- &lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;While limited by three trancepoints added in #22006, it is already showing real time P2P message count and traffic per message type.&lt;a href="https://t.co/Blepk1wFqj"&gt;https://t.co/Blepk1wFqj&lt;/a&gt; &lt;a href="https://t.co/cvOXoDaqTI"&gt;pic.twitter.com/cvOXoDaqTI&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1419992149941227550?ref_src=twsrc%5Etfw"&gt;July 27, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
--&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;And block connection timings during IBD and data about validated blocks/second, transactions/second, inputs/second, and sigops/second.&lt;a href="https://t.co/syfJ0pEAJZ"&gt;https://t.co/syfJ0pEAJZ&lt;/a&gt; &lt;a href="https://t.co/sGuIv3NavH"&gt;pic.twitter.com/sGuIv3NavH&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1419992156266184711?ref_src=twsrc%5Etfw"&gt;July 27, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;I&amp;rsquo;ve used the bitcoind-observer to provide feedback to &lt;a href="https://github.com/naumenkogs"&gt;Gleb&lt;/a&gt; for his work on
Erlay by measuring the bandwidth consumption between erlay and non-erlay nodes.
My &lt;a href="https://github.com/naumenkogs/txrelaysim/issues/8#issuecomment-903255752"&gt;measurements&lt;/a&gt; match Glebs calculations showing that we can archive the
hoped bandwidth savings in this specific setup.&lt;/p&gt;</description></item><item><title>Bitcoin Core PR Review Club: #22006</title><link>https://b10c.me/talks/010-prreviewclub-22006/</link><pubDate>Wed, 26 May 2021 17:00:00 +0000</pubDate><guid>https://b10c.me/talks/010-prreviewclub-22006/</guid><description>&lt;p&gt;I&amp;rsquo;ve prepared and moderated a Bitcoin Core PR review club meeting on my PR &lt;a href="https://github.com/bitcoin/bitcoin/pull/22006"&gt;#22006 Tracing: first tracepoints and documentation on User-Space, Statically Defined Tracing (USDT)&lt;/a&gt;. My goal was to familiarize Bitcoin Core contributors with the concept of Userspace, Statically Defined Tracing, and to get review on the first tracepoints, examples, and documentation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bitcoincore.reviews/22006"&gt;Bitcoin Core PR Review Club on #22006&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/blog/008-bitcoin-core-usdt-support/"&gt;Blog post: Userspace, Statically Defined Tracing support for Bitcoin Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Regulatory conform mining on einundzwanzig Podcast (German)</title><link>https://b10c.me/talks/007-einundzwanzig-mining/</link><pubDate>Fri, 14 May 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/007-einundzwanzig-mining/</guid><description>&lt;p&gt;&lt;a href="https://twitter.com/dennisreimann"&gt;Dennis Reimann&lt;/a&gt; and I chat about regulatory
conform mining in the context of my &lt;a href="https://miningpool.observer"&gt;miningpool.observer&lt;/a&gt; project.&lt;/p&gt;
&lt;a href='https://anchor.fm/einundzwanzig/episodes/Interview-41---Regulatorisch-konformes-Mining-und-Miningpool-Observer-mit-Timo-0xB10C-e10r717' class="btn btn-outline-danger my-2" role="button"&gt;Podcast (German)&lt;/a&gt;</description></item><item><title>miningpool-observer: Observing Bitcoin Mining Pools</title><link>https://b10c.me/projects/016-miningpool-observer/</link><pubDate>Thu, 06 May 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/016-miningpool-observer/</guid><description>&lt;p&gt;My miningpool-observer project aims to bring transparency to mining pool
transaction selection. The tool can detect missing and extra transactions
in blocks. One goal is to detect censorship by mining pools.&lt;/p&gt;
&lt;p&gt;The idea for this project originated while reading the article &lt;a href="https://blog.bitmex.com/bitcoin-miner-transaction-fee-gathering-capability/"&gt;Bitcoin
Miner Transaction Fee Gathering Capability&lt;/a&gt; by BitMex Research. They
touch on a new North American mining pool called Marathon that announced
that they plan to filter out transactions that are not &lt;a href="https://home.treasury.gov/policy-issues/office-of-foreign-assets-control-sanctions-programs-and-information"&gt;OFAC&lt;/a&gt; compliant.
While this wouldn&amp;rsquo;t cause immediate problems for Bitcoin, as there are
plenty of other pools mining these transactions, it&amp;rsquo;s still valuable to
get an overview of mining pool censorship on the Bitcoin network.&lt;/p&gt;
&lt;p&gt;Marathon Digital Holdings announced in a &lt;a href="https://ir.marathondh.com/news-events/press-releases/detail/1233/correction-marathon-digital-holdings-to-launch-the-first"&gt;press release&lt;/a&gt;
on March 30th, 2021, that they have licensed technology to set up a
mining pool that allows them to filter transactions. Around the same
time, I started my work on miningpool-observer. The goal was to have
the tool ready from the day Marathon starts to mine blocks to check
if they are leaving transactions out.&lt;/p&gt;
&lt;p&gt;On May 5th, Marathon published a &lt;a href="https://ir.marathondh.com/news-events/press-releases/detail/1239/marathon-digital-holdings-becomes-the-first-north-american"&gt;press release&lt;/a&gt; stating they
have directed all of their hashrate to the new pool. On May 6th, they
mined their first block 682170. The coinbase message stated:&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;MARA Pool mined its first &amp;#39;clean&amp;#39; block today.&lt;a href="https://t.co/v7WzKrzt9P"&gt;https://t.co/v7WzKrzt9P&lt;/a&gt; &lt;a href="https://t.co/qTQOoQsq0D"&gt;pic.twitter.com/qTQOoQsq0D&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1390194321127772165?ref_src=twsrc%5Etfw"&gt;May 6, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;As planned, I published my mingingpool-observer project on the same day.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;In response to block #682170, mined by MARA Pool, I&amp;#39;m announcing a project I&amp;#39;ve been working on. I hope this brings transparency into mining pool transaction selection.&lt;br&gt;&lt;br&gt; &lt;a href="https://t.co/QrMdv9gADB"&gt;https://t.co/QrMdv9gADB&lt;/a&gt;&lt;br&gt;&lt;br&gt;This compares block templates generated by my node to the blocks mined by pools.&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1390333123611746308?ref_src=twsrc%5Etfw"&gt;May 6, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Inspecting the &lt;a href="https://miningpool.observer/template-and-block/000000000000000000003f8cb66fe1ecfb38754abc9c4d4a62b71de45fef8777"&gt;block on miningpool.observer&lt;/a&gt; reveals that they&amp;rsquo;ve mined
166 transactions which the node powering miningpool-observer would have
mined too, included 12 extra transactions, and had 5 missing transactions.
These 5 missing transactions were only seen 12 seconds before the block was
mined, which indicates that the pool might not have seen them yet as most
pools update the block they are mining about every 30 seconds. The 12 extra
transactions the pool include consist of the coinbase transaction and 11 low
feerate transactions my node didn&amp;rsquo;t consider as it knew about the 5 missing
transactions that paid a higher feerate. It was also &lt;a href="https://twitter.com/LaurentMT/status/1390328013565349897"&gt;pointed out&lt;/a&gt; that the
block did include transactions sending to the Russian Darknet market Hydra
Market.&lt;/p&gt;
&lt;p&gt;Similarly, the next Marathon Pool blocks did not filter transactions to or
from OFAC-compliant addresses.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;1/ A quick thread on “compliant” blocks mined so far by MARA group.&lt;br&gt;&lt;br&gt;It’s important to remember that missing transactions are not necessarily censored, but could just be due to propagation delays or other normal network behavior.&lt;/p&gt;&amp;mdash; Seth For Privacy (@sethforprivacy) &lt;a href="https://twitter.com/sethforprivacy/status/1393206480195788805?ref_src=twsrc%5Etfw"&gt;May 14, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;On May 31st, Marathon Digital Holdings published a third &lt;a href="https://ir.marathondh.com/news-events/press-releases/detail/1244/marathon-signals-for-taproot"&gt;press release&lt;/a&gt;
stating they&amp;rsquo;ve ceased to filter transactions as they&amp;rsquo;re upgrading to Bitcoin
Core v0.21.1 to signal taproot. We didn&amp;rsquo;t see Marathon Pool leave out and include
any OFAC-sanctioned transactions during these 25 days of mining OFAC-compliant
blocks, likely due to OFAC-sanctioned transactions being rare.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve given a talk at the MIT Bitcoin Expo 2022 about this project. I discuss
implementation details and other findings there. See &lt;a href="https://b10c.me/talks#monitoring-bitcoin-mining-pool-transaction-selection"&gt;here&lt;/a&gt;
for slides and a recording.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This summary was written in December 2022.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Grant from Coinbase for 2021</title><link>https://b10c.me/funding/2021-coinbasegiving-grant/</link><pubDate>Mon, 01 Feb 2021 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2021-coinbasegiving-grant/</guid><description>&lt;p&gt;Coinbase Giving provided a Bitcoin developer grant my work on Bitcoin in 2021.&lt;/p&gt;
&lt;blockquote class="blockquote p-2 border"&gt;
&lt;span class="mb-0 small"&gt;
0xB10C plans to build and improve existing open source tools, including a web interface visualizing forks on, for example, the new Signet test network. His work builds on his previous efforts launching mempool.observer and transactionfee.info. Additionally, he will continue to publish research articles on how the Bitcoin network is being used, provide review on Bitcoin Core PRs, and help improve functional testing."
&lt;/span&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20210222104447/https://blog.coinbase.com/announcing-our-first-bitcoin-core-developer-grants-3d88559db068"&gt;Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://b10c.me/data/projects/sponsors/coinbase-announcement.pdf"&gt;Annoucement PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Evolution of the signature size in Bitcoin</title><link>https://b10c.me/blog/006-evolution-of-the-bitcoin-signature-length/</link><pubDate>Tue, 10 Nov 2020 00:00:00 +0000</pubDate><guid>https://b10c.me/blog/006-evolution-of-the-bitcoin-signature-length/</guid><description>&lt;p&gt;Digital signatures are an essential building block of the Bitcoin protocol and
account for a large part of the data stored on the blockchain. We detail how the
size of the encoded ECDSA signatures reduced multiple times over the last years
and how the proposed Schnorr signature compares to the length of the currently
used ECDSA signatures.&lt;/p&gt;
&lt;p&gt;Digital signatures in Bitcoin transactions are located in the &lt;em&gt;SigScript&lt;/em&gt; for
inputs that are not spending SegWit or in the &lt;em&gt;Witness&lt;/em&gt; for SegWit spending
inputs. They consist of the encoded &lt;em&gt;r&lt;/em&gt; and &lt;em&gt;s&lt;/em&gt; values and a so-called &lt;em&gt;SigHash&lt;/em&gt;
flag, which specifies which part of the transaction the signature signs. The &lt;em&gt;r&lt;/em&gt;
and &lt;em&gt;s&lt;/em&gt; values are both 256 bit (32-byte) integers.&lt;/p&gt;
&lt;h2 id="der-encoded-ecdsa-signatures"&gt;DER Encoded ECDSA Signatures&lt;/h2&gt;
&lt;p&gt;Since its first version, the Bitcoin client relied on OpenSSL for signature
validation and encoding. ECDSA signatures are encoded with the Distinguished
Encoding Rules (DER) defined in the &lt;a href="https://www.itu.int/rec/T-REC-X.690-201508-I/en"&gt;ANS.1&lt;/a&gt; encoding rules. While the DER
encoding only allows for exactly one way of representing a signature as a byte
sequence, the OpenSSL library deemed derivations from the DER standard as valid.
When this changed in the OpenSSL library, it caused some nodes with the newer
OpenSSL version to &lt;a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-January/007097.html"&gt;reject the chain&lt;/a&gt; from nodes using an older version of
the library. &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki"&gt;BIP-66&lt;/a&gt; proposed a consensus soft fork where only signatures
strictly following the DER encoding are accepted.&lt;/p&gt;
&lt;p&gt;A DER-encoded ECDSA signature starts with a &lt;code&gt;0x30&lt;/code&gt; identifier marking a compound
structure. Next is a length byte containing the length of the structure followed
by the compound structure itself. The compound contains the r- and s- values as
integers. These are marked with the integer identifier &lt;code&gt;0x02&lt;/code&gt; and followed by a
length byte defining the respective lengths of the values.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/ecdsa-der-encoding.png'
alt='Format of a DER-encoded Bitcoin signature with SigHash flag'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Format of a DER-encoded Bitcoin signature with SigHash flag&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While the ANS.1 encoding requires a signed integer, the r and s-values in ECDSA
are expected to be unsigned integers. This causes an issue when the first bit of
the r- or s-values are set. To solve this, the value is prepended with a 0x00
byte. Thus the unsigned integer is encoded as a positive, signed integer. A
value with the first bit set is referred to as &lt;em&gt;high&lt;/em&gt; and a value with an unset
first bit as &lt;em&gt;low&lt;/em&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/73b-sig.png'
alt='A 73-byte high-r and high-s Bitcoin ECDSA signature'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A 73-byte high-r and high-s Bitcoin ECDSA signature&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The r and s-values are random. When both values are &lt;em&gt;high&lt;/em&gt; (both have their
first bit set), they both require a prepended 0x00 byte. With two extra bytes,
the encoded r- and s-values and the SigHash flag result in a total signature
length of 73 bytes. The probability of both values being &lt;em&gt;high&lt;/em&gt; in the same
signature is 25%. Until early 2014, a distribution of about 25% 73-byte, 50%
72-byte, and about 25% of 71-byte signatures can be observed on the blockchain.
In the 72-byte signatures, one of the two values is &lt;em&gt;high&lt;/em&gt; and the other one is
&lt;em&gt;low&lt;/em&gt;. In a 71-byte signature, both values have to be &lt;em&gt;low&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The share of the signatures with a &lt;em&gt;high-s&lt;/em&gt; value started to reduce the release
of &lt;a href="https://bitcoin.org/en/release/v0.9.0"&gt;Bitcoin Core v0.9.0&lt;/a&gt; in March 2014. This release contained a change to
the Bitcoin Core wallet to only create &lt;em&gt;low-s&lt;/em&gt; signatures. With the release of
Bitcoin Core &lt;a href="https://bitcoin.org/en/release/v0.10.3#test-for-lows-signatures-before-relaying"&gt;v0.10.3&lt;/a&gt; and &lt;a href="https://bitcoin.org/en/release/v0.11.1#test-for-lows-signatures-before-relaying"&gt;v0.11.1&lt;/a&gt; in October 2015, &lt;em&gt;high-s&lt;/em&gt;
signatures were made non-standard to completely remove the malleability vector.
This forbids transactions with &lt;em&gt;high-s&lt;/em&gt; values from being relayed or used in
mining. Starting December 2015, nearly all transactions on the blockchain have
only &lt;em&gt;low-s&lt;/em&gt; values in their signatures.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/72b-sig.png'
alt='A 72-byte high-r and low-s Bitcoin ECDSA signature'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A 72-byte high-r and low-s Bitcoin ECDSA signature&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Between December 2015 and early 2018, the signatures on the blockchain are
nearly evenly split between 72 and 71 bytes in length. The 72-byte signatures
have a &lt;em&gt;low-s&lt;/em&gt; and a &lt;em&gt;high-r&lt;/em&gt; value, which requires a prepended &lt;code&gt;0x00&lt;/code&gt; byte. The
71-byte signatures are &lt;em&gt;low-r&lt;/em&gt; and &lt;em&gt;low-s&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;End of August 2017, the SegWit soft-fork activated. SegWit moves the contents of
the &lt;em&gt;SigScript&lt;/em&gt;, which contains, for example, the signature, into the &lt;em&gt;Witness&lt;/em&gt;.
While the witness gets discounted when calculating the transaction weight, the
signature size on the blockchain remains the same.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/71b-sig.png'
alt='A 71-byte low-r and low-s Bitcoin ECDSA signature'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A 71-byte low-r and low-s Bitcoin ECDSA signature&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Bitcoin Core &lt;a href="https://bitcoin.org/en/release/v0.17.0"&gt;v0.17.0&lt;/a&gt; release in October 2018 included an improvement
to the Bitcoin Core wallet to produce only 71-byte signatures. By re-signing a
transaction with a different nonce, a new r-value can be grinded until a &lt;em&gt;low&lt;/em&gt;
value is found. The technique has been adopted by other projects such as the
&lt;a href="https://github.com/MetacoSA/NBitcoin/pull/510"&gt;NBitcoin&lt;/a&gt; library and the &lt;a href="https://github.com/spesmilo/electrum/pull/5820"&gt;Electrum Bitcoin Wallet&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="schnorr-signatures"&gt;Schnorr Signatures&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki"&gt;BIP-340&lt;/a&gt; introduces Schnorr signatures for Bitcoin and &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki"&gt;BIP-341&lt;/a&gt;
proposes a new SegWit version 1 output type and its spending rules based on
Schnorr signatures, Taproot, and Merkle branches. Unlike ECDSA signatures, the
Schnorr signatures are not DER-encoded.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/schnorr-format.png'
alt='Format of a Bitcoin Schnorr signature'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Format of a Bitcoin Schnorr signature&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Schnorr signatures contain the 32 byte r-value followed by the 32 byte s-value.
The most commonly used &lt;em&gt;SigHash&lt;/em&gt; flag &lt;code&gt;SIGHASH_ALL&lt;/code&gt; is assumed by default and does
not need to be set explicitly. Other SigHash flags are placed after the s-value.
Schnorr signatures with the default &lt;code&gt;SIGHASH_ALL&lt;/code&gt; flag have a length of exactly
64 byte. Signatures with a different &lt;em&gt;SigHash&lt;/em&gt; flag are 65 byte long.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/006-signatures/schnorr-sig.png'
alt='A 64-byte SIGHASH_ALL Bitcoin Schnorr signature'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;A 64-byte SIGHASH_ALL Bitcoin Schnorr signature&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Compared to ECDSA signatures, Schnorr signatures are between 6 and 9 byte
shorter. These savings stem from the removed encoding overhead and the default
SigHash flag. With a Schnorr signature adoption of 20%, and assuming all of the
&lt;a href="https://transactionfee.info/charts/inputs-and-outputs/"&gt;800.000 inputs spent per day&lt;/a&gt; contain only a single signature, more than
1MB of blockchain space is saved per day.&lt;/p&gt;
&lt;h4 id="related-reading"&gt;Related reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://luca.ntop.org/Teaching/Appunti/asn1.html"&gt;A Layman’s Guide to a Subset of ASN.1, BER, and DER&lt;/a&gt; - An RSA Laboratories Technical Note - 1993&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.eternitywall.com/2017/12/12/shortest-transaction/"&gt;Create the shortest transaction&lt;/a&gt; - Eternity Wall Blog - 2017&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.eternitywall.com/content/20171212_Exact_Probabilities/"&gt;Exact probabilities of obtaining a DER encoded signature of a certain length&lt;/a&gt; - Eternity Wall Blog&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This article was re-published on the &lt;a href="https://www.advancingbitcoin.com/blog/evolution-signature-size-bitcoin/"&gt;Advancing Bitcoin&lt;/a&gt; blog and &lt;a href="https://www.hackernoon.com/analyzing-bitcoins-signature-size-how-it-evolved-over-the-years-uy233zz3"&gt;Hackernoon&lt;/a&gt;.
Furthermore, it was translated into Chinese by Chen Bo Yu and Hsu Tzu Hsiu who
published their translation on &lt;a href="https://www.chainnews.com/articles/668338392823.htm"&gt;链闻 ChainNews&lt;/a&gt; and &lt;a href="https://mp.weixin.qq.com/s/m-rBw19927JLHrgRQP0rPw"&gt;Binance Blockchain Research
Institute&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Support from Samourai Wallet for mempool.observer</title><link>https://b10c.me/funding/2020-samourai-wallet-support/</link><pubDate>Thu, 01 Oct 2020 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2020-samourai-wallet-support/</guid><description>&lt;p&gt;Samourai Wallet supported my work on &lt;a href="https://b10c.me/projects/mempool-observer-2019-version/"&gt;mempool.observer&lt;/a&gt; with a one-time donation that payed for hosting costs.&lt;/p&gt;</description></item><item><title>Following the Blockchain.com feerate recommendations</title><link>https://b10c.me/observations/03-blockchaincom-recommendations/</link><pubDate>Mon, 13 Jul 2020 11:00:00 +0200</pubDate><guid>https://b10c.me/observations/03-blockchaincom-recommendations/</guid><description>&lt;p&gt;Transactions sent with Blockchain.com wallets make up for about a third of all
Bitcoin transactions. A methodology to identify these transactions is described
and used. Insights about the wallet-usage are derived from the resulting
dataset. The privacy implications and possible improvements are discussed.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;One of the first observations made when building the &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction
Monitor&lt;/a&gt; was that many transactions precisely follow the recommendations of
a feerate estimator. These transactions appear as horizontal bands, which rise
and sink as the feerate recommendations change.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/bands.png'
alt='Transactions following the Blockchain.com feerate recommendations'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most of these transactions share the same fingerprint. Only P2PKH outputs are
spent. No SegWit and neither multisig are spent. With every transaction, either
one or two outputs are created. When two outputs are created, then at least one
of them is a P2PKH output. The transactions are not time-locked, have a version
of one, and do not signal &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki"&gt;BIP-125 replaceability&lt;/a&gt;. However, all are
&lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki"&gt;BIP-69&lt;/a&gt; compliant.&lt;/p&gt;
&lt;p&gt;This matches the fingerprint of the Blockchain.com wallets: namely a Web, an
iOS, and an Android wallet. The wallets can only receive and spend P2PKH
outputs. While users can pay to all address formats&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, the change-output, if
created, is a P2PKH output. The wallets construct the transactions with a
locktime of zero and a transaction version of one. The inputs and outputs are
all lexicographically sorted as specified by BIP-69.&lt;/p&gt;
&lt;p&gt;The wallets use the Blockchain.com feerate estimator, which is publicly
accessible via &lt;a href="https://b10c.me/blog/003-a-list-of-public-bitcoin-feerate-estimation-apis/#blockchaininfo-api"&gt;an API&lt;/a&gt;. The API returns two feerate estimates: &lt;em&gt;priority&lt;/em&gt;
and &lt;em&gt;regular&lt;/em&gt;. The &lt;em&gt;priority&lt;/em&gt; feerate aims for confirmation in the next hour
and the &lt;em&gt;regular&lt;/em&gt; feerate for confirmation in an hour or more. By default,
the wallets follow the recommendations closely. Users can set a custom feerate,
but a warning is displayed.&lt;/p&gt;
&lt;h3 id="methodology"&gt;Methodology&lt;/h3&gt;
&lt;p&gt;Combining the feerate estimates and the transaction fingerprints makes it
possible to identify transactions sent with one of the Blockchain.com wallets.
While the majority of the Blockchain.com transactions pay exactly the
recommended feerate, some under- or overpay by a fixed percentage. This is
caused by incorrect assumptions about the transaction size during the
calculation of the transaction fee. The transaction fee is the product of the
targeted feerate and the assumed transaction size. The final and actual
transaction size is only known after adding the signature to the transaction.&lt;/p&gt;
&lt;pre class="text-center"&gt;
fee = target feerate × assumed transaction size
&lt;/pre&gt;
&lt;p&gt;All underpaying transactions have two outputs. However, during the fee
calculation, the size of a one-output transaction is assumed. For example, for a
P2PKH &lt;em&gt;1in ⇒ 2out&lt;/em&gt; transaction (226 bytes), the size of a &lt;em&gt;1in ⇒ 1out&lt;/em&gt;
transaction (192 bytes) is used. This incorrect assumption results in the
transaction only paying around 85% (192 byte / 226 byte) of the recommended
feerate. As the transaction inputs make up for a large part of the transaction
size, the effect is smaller for transactions with more inputs. This behavior was
only present in the Blockchain.com Web wallet. A fix was &lt;a href="https://github.com/blockchain/blockchain-wallet-v4-frontend/releases/tag/v4.32.6"&gt;released&lt;/a&gt; on
April 21st, 2020.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/over-underpaying.png'
alt='Transactions over- and underpaying by a fixed percentage'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The overpaying transactions all have a single output. For these, a second output
is assumed during the fee calculation. To calculate the fee of a P2PKH
&lt;em&gt;1in ⇒ 1out&lt;/em&gt; transaction (192 bytes), the size of a &lt;em&gt;1in ⇒ 2out&lt;/em&gt; transaction
(226 bytes) is used. This results in the transaction paying about 118% (226 byte
/ 192 byte) of the recommended feerate. Similar to the underpaying transactions,
the effect is smaller for transactions with more inputs. These transactions are
assumed to originate from the Blockchain.com iOS wallet. This has not yet been
confirmed.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/methodology.png'
alt='Visual explainer for methodology used to identify Blockchain.com transactions'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Out of the set of transactions with the Blockchain.com wallet fingerprint, the
transactions paying the feerate recommended by the Blockchain.com feerate
estimator are selected. Transactions broadcast on April 19th, 2020, are shown.
The y-axis is centered around the &lt;em&gt;regular&lt;/em&gt; recommendation, which was 3
sat/vbyte for most of the day. Between 12:00 UTC and 17:00 UTC, the &lt;em&gt;regular&lt;/em&gt;
recommendation briefly jumped to 4 sat/vbyte for a few minutes each. On other
days the feerate recommendations are usually more volatile. April 19th is a
Sunday. Sundays are known for less network activity compared to weekdays. This
day has been specifically chosen to showcase the methodology.
&lt;/div&gt;
&lt;p&gt;Identifying Blockchain.com wallet transactions with this methodology is not
assumed to be perfectly accurate or reliable. For example, transactions send
with a custom feerate can not be identified and are false negatives.
Transactions constructed by different wallets that pay a similar feerate and
share the fingerprint could be identified as false positives. When the
recommended feerate is volatile, which is often the case for the &lt;em&gt;priority&lt;/em&gt;
recommendation (for example, shortly after &lt;a href="https://b10c.me/observations/02-bitmex-broadcast-13-utc/"&gt;the daily BitMEX broadcast&lt;/a&gt;),
then some transactions might pay a feerate not recoded by the Bitcoin
Transaction Monitor. Additionally, the wallets could construct a transaction
using an older recommendation, which is different from the recommendation at the
time the transaction is broadcast. These transactions are false negatives as
well.&lt;/p&gt;
&lt;h3 id="observations"&gt;Observations&lt;/h3&gt;
&lt;p&gt;The described methodology is used to identify the transactions send with
Blockchain.com wallets between April 1st and May 20th, 2020. The resulting
dataset spans over 50 days and contains about 4 million transactions. These pay
a total fee of 445.73 BTC and account for about 1.34 GB of block space. Roughly
two-thirds of the Blockchain.com wallet transactions target the &lt;em&gt;regular&lt;/em&gt; feerate
while the remaining third targets the &lt;em&gt;priority&lt;/em&gt; feerate.&lt;/p&gt;
&lt;p&gt;Roughly the same number of outputs are created as are spend. Blockchain.com
wallet transactions have either a single payment-output or a payment-output and
a change-output. As the change-outputs are always P2PKH outputs, it is possible
to determine the payment-output type. Out of all outputs created about 31.7% are
P2PKH, 23.3% are P2SH, 0.34% are P2WPKH, and less than 0.01% are P2WSH
payment-outputs. The remaining 45.5% are P2PKH change-outputs. The most commonly
used input-output combinations are &lt;em&gt;P2PKH ⇒ P2PKH + P2PKH&lt;/em&gt; with 33%,
&lt;em&gt;P2PKH ⇒ P2SH + P2PKH&lt;/em&gt; with 26%, and &lt;em&gt;P2PKH ⇒ P2PKH&lt;/em&gt; with around 7%.&lt;/p&gt;
&lt;br&gt;
&lt;!--topic: activity hours--&gt;
&lt;p&gt;Users of the Blockchain.com wallet are most active between 15:00 UTC and 18:00
UTC and least active between 4:00 UTC and 5:00 UTC. At around 5:00 UTC, the
number of transactions per minute starts to rise. At this time it is 8am in
Moscow, and 7am in central Europe. Between 5:00 UTC and 10:00 UTC, the number
of transactions per minute rises from about 30 to just above 60. The
transactions per minute remain constant until rising again at noon UTC, which is
8am on the US east coast. The daily maximum is reached at around 16:00 UTC with
just above 75 transactions per minute. From there on, the activity declines
until reaching the minimum number of transactions per minute at around 4:00 UTC
again.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/time-of-day.png'
alt='Activity hours of Blockchain.com wallet users.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
The transactions broadcast per minute with Blockchain.com wallets are shown. The
error bands show the standard deviation. The time between 8am and 8pm is
marked for central Asia, Europe, and eastern US timezones.
&lt;/div&gt;
&lt;br&gt;
&lt;!--topic: network's tx share of Blockchain--&gt;
&lt;p&gt;&lt;a href="https://thecryptofeed.net/articles/blockchain-com-says-they-account-for-a-third-of-all-bitcoin-transactions/"&gt;Reportedly&lt;/a&gt;, Blockchain.com claims that their wallets are responsible for
one-third of all Bitcoin transactions. They &lt;a href="https://www.blockchain.com/charts/my-wallet-n-tx"&gt;publish&lt;/a&gt; the daily number of
transactions sent by their wallets. This lead to a discussion on the accuracy
and correctness of these numbers. The described dataset can be used to verify
this claim. The number of daily transactions in the dataset and the published
numbers can be compared. The total number of transactions sharing the
fingerprint with the Blockchain.com wallet transactions acts as an upper-bound.
The total transactions per day are retrieved from &lt;a href="https://transactionfee.info/charts/transactions-per-day/"&gt;transactionfee.info&lt;/a&gt; to
calculate Blockchain.com&amp;rsquo;s share of the network.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/one-third.png'
alt='Showing that the Blockchain.com published numbers could be reasonably accurate.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The daily transaction count published by Blockchain.com translates into a
network share of 30% to 35%. The share of the transactions with the same
fingerprint, the upper-bound, is on average about three absolute percent higher.
The share of the identified transactions in the dataset is about four to five
absolute percent lower than the Blockchain.com reported numbers at around 27% on
average. The transactions account for about 13% of the daily fees paid, and 20%
of the daily block space used.&lt;/p&gt;
&lt;p&gt;However, the numbers reported by Blockchain.com still lie in a reasonable range.
There are multiple reasons why the described dataset could contain fewer
transactions than are reported by Blockchain.com. Some users might send
transactions with a custom feerate. These are not picked up by the described
methodology. Furthermore, it&amp;rsquo;s not clear if the reported numbers include
transactions send with the &lt;a href="https://www.blockchain.com/de/api/blockchain_wallet_api"&gt;Blockchain.com Wallet API&lt;/a&gt;. The API allows
users to construct transactions sending to multiple recipients which are not
accounted for in the described dataset.&lt;/p&gt;
&lt;br&gt;
&lt;!--topic: wallet platform share--&gt;
&lt;p&gt;With the knowledge that the Blockchain.com Web wallet underpaid the recommended
feerate for transactions with two outputs, and the iOS wallet
overpays on transactions with one output, the wallet&amp;rsquo;s shares can be estimated.
For this, the assumption that the ratio of two-output to one-output transactions
is similar in all wallets must hold. The Web wallet accounts for one-third and
the iOS wallet for half of the Blockchain.com wallet transactions. The Android
wallet probably accounts for a majority of the remaining 17%. However, this can
not be verified as no data is indicating the share of the Android wallet.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/web-wallet-share.png'
alt='Share of Web wallet transactions with two outputs.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Between April 1st and April 22nd, the two-output transactions send with the Web
wallet made up for about a third of all two-output transactions send with
Blockchain.com wallets. The shown mean is weighted with the transaction counts.
A fix &lt;a href="https://github.com/blockchain/blockchain-wallet-v4-frontend/releases/tag/v4.32.6"&gt;released&lt;/a&gt; on April 21st resolved the underpaying behavior for
two-output transactions in the Web wallet. It took a few days until the release
got deployed.
&lt;/div&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/iOS-wallet-share.png'
alt='Share of iOS wallet transactions with a single output.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Between April 1st and May 20th, the one-output transactions send with the iOS
wallet made up for about half of all one-output transactions constructed by
Blockchain.com wallets. The shown mean is weighted with the transaction counts
per input-output combination. It&amp;rsquo;s unclear why the iOS wallet would account for
60% to 70% of the &lt;em&gt;4+in ⇒ 1out&lt;/em&gt; transactions.
&lt;/div&gt;
&lt;br&gt;
&lt;!--topic: time to confirmation--&gt;
&lt;p&gt;The iOS wallet overpays and the Web wallet underpaid the recommended feerate for
some input-output combinations. This is noticeable in the time it takes for a
transaction to confirm when targeting the &lt;em&gt;regular&lt;/em&gt; feerate recommendation. The
overpaying transactions fill up most of the block space, and transactions paying
or underpaying the recommended feerate are only included in later blocks. In
median, a one-output transaction send with the iOS wallet confirms the fastest.
Two-output transactions sent with the Web wallet took the longest. The Web
wallet&amp;rsquo;s effect was the strongest for transactions with one-input and
two-outputs, which is the &lt;a href="https://transactionfee.info/charts/transactions-1in-2out/"&gt;most commonly used&lt;/a&gt; input-output combination.
These only paid about 85% of the recommended feerate. Transactions send with the
Android wallet, iOS wallet transactions with two outputs or Web wallet
transactions with one output confirm after the iOS wallet transactions with
one-output and before the Web wallet transactions with two-outputs.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/time-to-confirmation.png'
alt='Time to confirmation by input-output combination'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Box plots for the different input-output combinations show the distribution of
the times it took for the transactions to confirm. The data ranges from April
1st to April 22nd, 2020. On April 21st, the fix for the Web wallet was released.
The median time to confirmation is annotated. Not outliners are shown in the box
plots.
&lt;/div&gt;
&lt;p&gt;Paying a slightly higher feerate than the Blockchain.com iOS wallet pays for a
one-output transactions could be a good trade-off between a fast confirmation
and paying a low transaction fee. An advanced user could target a feerate of,
for example, about 120% of the &lt;em&gt;regular&lt;/em&gt; recommendation. A miner would include
the transaction in a block before any of the Blockchain.com transactions are
included. Targeting, for example, 102% of the &lt;em&gt;regular&lt;/em&gt; recommendation could be
an option too. This would be cheaper, but the transaction might take longer to
confirm as the overpaying iOS wallet transactions confirm first. The
effectiveness of these strategies might be reduced when the feerate
recommendations are volatile during high activity hours.&lt;/p&gt;
&lt;br&gt;
&lt;!--topic: closer look at recommended feerate--&gt;
&lt;p&gt;Taking a closer look at the transactions paying the recommended feerate shows
transactions with P2SH outputs pay a slightly higher feerate than transactions
with P2PKH outputs. In the Blockchain.com wallets, the assumed size of a single
P2PKH output with 34 bytes is used in the fee calculation. P2SH outputs, with 32
bytes, are slightly smaller. Transactions with a P2SH output pay for two extra
bytes, when the P2PKH output size is used. This results in transactions with
P2SH outputs paying a slightly higher feerate. Something similar can be observed
for transactions with a P2WPKH output, which have a size of 31 bytes. These pay
for three bytes they do not have. P2WSH outputs take up 43 bytes, and thus
transactions with a P2WSH output slightly underpay the recommended feerate as
they are not paying for 9 bytes.&lt;/p&gt;
&lt;p&gt;Users sending their funds to other wallets or services by creating a transaction
that pays to a P2SH or a P2WPKH output unknowingly pay a minimally higher fee
than they would have to. On average, these transactions confirm earlier.
Transactions with P2WSH outputs pay slightly less than the recommendation and
take longer to confirm on average. These effects are probably most noticeable
during high activity hours.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/03-blockchaincom-recommendations/closer-look.png'
alt='Closer look at the transactions paying the recommended feerate.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Transactions paying exactly the recommended feerate of 3 sat/vbyte on April 19th,
2020, are shown. Between 12:00 UTC and 15:00 UTC, the recommended feerate was
briefly at 4 sat/vbyte or more. This is out-of-bounds and not shown. The most
common input-output combinations are annotated. It&amp;rsquo;s visible that transactions
with P2SH outputs, here marked in orange, pay a slightly higher feerate that
P2PKH transactions. Transactions with P2WSH outputs are out-of-bounds on this
graph, and transactions with more than four outputs are not shown.
&lt;/div&gt;
&lt;p&gt;Some transactions with the same input-output combinations appear multiple times
at different feerates and have slightly different sizes. Low and high R-values
in the ECDSA signatures can cause a one-byte size difference per input. Some
transactions have the same input-output combination and the same size but pay a
different fee, even when targeting the same feerate. This is caused by the iOS
and Android wallet assuming a different P2PKH input size as the Web wallet. The
Web wallet uses &lt;a href="https://github.com/blockchain/blockchain-wallet-v4-frontend/blob/cbea2ba2ccfc50327186b8c8e9313a3c812c5ee5/packages/blockchain-wallet-v4/src/coinSelection/coin.js#L20-L24"&gt;147 bytes&lt;/a&gt;, and the &lt;a href="https://github.com/blockchain/My-Wallet-V3-Android/blob/cba6a66c93c41daf6dcabfc398cf25f0f9ec2fb4/wallet/src/main/java/info/blockchain/wallet/payment/CoinSelection.kt#L6-L8"&gt;Android&lt;/a&gt; and &lt;a href="https://github.com/blockchain/My-Wallet-V3-iOS/blob/master/BitcoinKit/Services/CoinSelection/CoinSelection.swift#L35-L39"&gt;iOS&lt;/a&gt;
wallet both use 149 bytes. P2PKH inputs are usually either 147 or 148 bytes.
This depends on the R-value in the signature being either low or high. The sizes
assumed by the Android and iOS wallet are incorrect. P2PKH inputs with 149 bytes
were only possible when high-S values were allowed in the standardness rules
before October 2015. More history on the lengths of ECDSA signatures can be
found &lt;a href="https://transactionfee.info/charts/bitcoin-script-ecdsa-length/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="privacy"&gt;Privacy&lt;/h3&gt;
&lt;p&gt;The more information a passive observer can derive from a Bitcoin transaction
and public metadata, the worse the impact on the privacy of the sender and the
recipient. Being able to identify the sending wallet is an information leak. To
improve the privacy of Blockchain.com wallet users and to reduce the
effectiveness of the described methodology, the recommended feerate should not
be followed as closely, and the wallet fingerprint should be broadened.&lt;/p&gt;
&lt;p&gt;A key part of reliably identifying Blockchain.com wallet transactions is to
select the transactions that pay exactly the recommended feerates. By
introducing randomness in feerates, the transactions mix with
non-Blockchain.com wallet transactions. This increases the false-positive rate
of the described methodology making it less reliable.&lt;/p&gt;
&lt;p&gt;The term anonymity pool is used to describe the set of transactions with the
same wallet fingerprint. The more wallets construct transactions with the same
fingerprint, the harder it gets to identify the wallet a transaction originates
from. This improves the privacy of all Bitcoin users. While the Blockchain.com
anonymity pool consists of often more than 100000 transactions per day, the
Blockchain.com wallets make up for at least 80% of these transactions. The
fingerprint can be broadened in different ways, which increases the
anonymity pool size and thus decreases the Blockchain.com&amp;rsquo;s share. This would
have a positive effect on the privacy of Blockchain.com wallet users and the
privacy of all other Bitcoin users. To broaden the fingerprint of the
Blockchain.com wallet, it could, for example, support receiving to and spending
from different address types, time-lock some of the created transactions to the
current block height, or set a random transaction version when constructing the
transaction.&lt;/p&gt;
&lt;p&gt;For advanced users, it might be possible to hide their transactions in the
anonymity pool of the Blockchain.com transactions. This could be done by
mimicking the Blockchain.com wallet fingerprint and paying exactly the
recommended feerate. If done correctly, somebody trying to wallet fingerprint
transactions with the described methodology would false positively identify the
transaction as sent by a Blockchain.com wallet. Blockchain.com could track which
transactions were constructed by one of their wallets and which only try to
mimic their wallets. This information could be sold to adversaries.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Personal note: I value the privacy of the individual. I won&amp;rsquo;t publish the
transactions or txids I identified as Blockchain.com transactions.
However, a motivated passive listener could easily use the outlined methodology to
start tagging Blockchain.com users. I publish the above intending to raise
awareness about the issue. Especially the awareness of Blockchain.com, users of
the Blockchain.com wallets, and developers of other wallets closely following a
feerate estimator. If you think I should have disclosed this privately before
publishing it, please let me know - either in private or by calling me out
publicly.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;While my &lt;a href="https://b10c.me/observations/"&gt;Mempool Observations&lt;/a&gt; are fun to write, the process is very
time-consuming. You can support me &lt;a href="https://b10c.me/projects/"&gt;here&lt;/a&gt;, which enables me to spend more
of my time researching and writing. If you work for a company interested in
improving your on-chain foot- and fingerprint, then I&amp;rsquo;d be more than happy to
chat. If you are working on improving Bitcoin privacy, especially on removing
wallet fingerprints, then I&amp;rsquo;d like to contribute the things I&amp;rsquo;ve learned
starring at my &lt;a href="https://mempool.observer/monitor/"&gt;Bitcoin Transaction Monitor&lt;/a&gt;. My contact information can be
found at the bottom of this page.&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I realized that I was not able to send to a bech32 P2WPKH address in the
Android wallet when trying to withdraw funds used to test the wallet behavior.
It is however possible to send to a bech32 address in the Web wallet.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>The daily BitMEX broadcast at 13:08 UTC</title><link>https://b10c.me/observations/02-bitmex-broadcast-13-utc/</link><pubDate>Mon, 04 May 2020 13:08:30 +0000</pubDate><guid>https://b10c.me/observations/02-bitmex-broadcast-13-utc/</guid><description>&lt;p&gt;At around 13:00 UTC every day, BitMEX, a cryptocurrency exchange and derivative
trading platform, broadcasts multiple megabytes of large transactions into the
Bitcoin network. This affects the transaction fees paid during European
afternoons and US business hours. The transaction size could be greatly reduced
by implementing current industry standards in the BitMEX wallet. Once
activated, utilizing Schnoor and Taproot combined with output batching seems to
be the most promising for improving the transaction count and size.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The observation that BitMEX broadcasts transactions every day at around 13:00
UTC is &lt;a href="https://twitter.com/ziggamon/status/1134490591264497664"&gt;not novel&lt;/a&gt;. The transactions are mainly withdrawals initiated by
BitMEX users and some internal UTXO consolidations&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. As part of their &lt;a href="https://www.bitmex.com/app/security#Wallet-Security"&gt;wallet
security practice&lt;/a&gt;, BitMEX reviews and processes all withdrawals by hand.
They &lt;a href="https://twitter.com/BitMEXdotcom/status/1135306176344862721"&gt;claim&lt;/a&gt; that doing so multiple times a day would be infeasible and
worry that spreading the transaction broadcast over the day, which would lighten
the burden on the network, could decrease their user&amp;rsquo;s experience.&lt;/p&gt;
&lt;p&gt;BitMEX transactions have a unique fingerprint which originates from their
in-house multi-signature wallet solution. All transactions strictly spend P2SH
outputs with 3-of-4 multisig redeem scripts. The four public keys used in the
redeem script are uncompressed. The hashed and encoded redeem script result in
addresses with the prefix &amp;lsquo;3BMex&amp;rsquo;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. BitMEX does not spend SegWit outputs, and
all transactions have a version of 2. These properties make it straightforward
to recognize BitMEX transactions on the chain and in the mempool.&lt;/p&gt;
&lt;h3 id="observations"&gt;Observations&lt;/h3&gt;
&lt;p&gt;A dataset consisting of the transactions observed by the &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction
Monitor&lt;/a&gt; between September 2019 and March 2020 is used. In these six
months, BitMEX broadcast around 415 000 transactions into the Bitcoin network.
Summed together, these take up around 593 MB and pay a total miner fee of 181
BTC. This represents about 2.8% of the total bytes and 3.8% of the total fees
broadcast in this period.&lt;/p&gt;
&lt;p&gt;The median broadcast contains 2209 transactions, 5688 3-of-4 multi-signature
inputs, pays 0.95 BTC in miner fees, and has a total size of 3.16 MB. The input
count, the miner fees, and the total broadcast size grow linear with the
transaction count. On weekends, fewer transactions are broadcast than on
weekdays, and thus the input count, total miner fee, and broadcast size are
usually less.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/stats.png'
alt='Broadcasts statistics covering transaction count, fees and size split up by weekend and weekday.'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With more than 500 bytes each, the 3-of-4 multi-signature inputs account for
most of the transaction size. About 26% of the transactions have one, 36% have
two, 32% have three, 3% have four, and about 2% have five or more inputs. The
transactions have either one (46%) or two outputs (54%).&lt;/p&gt;
&lt;p&gt;The miner fees for withdrawals are not paid by BitMex. They are deducted from
the withdrawing users. The transaction feerate does not depend on the input
count nor the transaction size. Users choose a miner fee when withdrawing.
However, they do not know how many inputs the final withdrawal transaction will
spend and thus can not reason about the transaction size and required feerate.
All observed transaction fees are a multiple of 10 000 satoshi, which is the
minimum step size the withdrawal frontend allows. 10 000 satoshi is the
smallest, and most commonly (44%) observed withdrawal fee. It is followed by
100 000 satoshi (30%), 20 000 satoshi (17.5%), and 50 000 satoshi (3%). The most
commonly observed feerates, which result from the combination of a user-picked
fee and an algorithmically chosen number of inputs, are 17 (24%), 9 (19%), 60
(16%), 12 (10%), 88 (8%) and 18 sat/vbyte (8%).&lt;/p&gt;
&lt;p&gt;BitMEX starts processing withdrawals at &lt;a href="https://bitmex.freshdesk.com/support/solutions/articles/13000015790-what-time-are-withdrawals-processed-"&gt;13:00 UTC&lt;/a&gt;. It takes a few minutes
before the transactions are broadcast to the network. On some days, the first
transactions can be observed as early as 13:05 UTC. In median, the first
transaction arrives at 13:08:30 UTC. It takes about 2:14 minutes in median until
all transactions arrive on weekdays and 1:57 minutes in median on weekends.&lt;/p&gt;
&lt;p&gt;&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/time-distribution.png'
alt='time of day when BitMex transactions are broadcast'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
On three days in the observed six months, the broadcast was delayed by a few
minutes. These broadcasts are out-of-bounds for this plot. On the 1st of
November, 2449 transactions were broadcast starting at 13:40 UTC, on the 14th of
December, 1715 transactions were broadcast at 13:35 UTC, and on the 7th of
February, 2794 transactions were broadcast at 13:26 UTC.
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The 25th percentile of broadcast transactions takes about 10 minutes to confirm,
the 50th percentile about 27 minutes and the 75th for about 71 minutes. The 80th
percentile takes about two hours, the 86th for more than three hours, the 92nd
over five hours, and the 99th percentile over ten hours.&lt;/p&gt;
&lt;h3 id="effects"&gt;Effects&lt;/h3&gt;
&lt;p&gt;Broadcasting multiple megabytes of transactions at various feerate levels has
immediate and noticeable effects on the network. The feerate estimators adjust
their recommendations, and the wallets using these recommendations set a higher
feerate when constructing a transaction. The minimum feerate for block-inclusion
rises and the time-to-confirmation spikes.&lt;/p&gt;
&lt;p&gt;Feerate estimators correctly react to the thousands of large transactions spread
over different feerate levels. They recommend paying a higher feerate to outbid
the BitMEX transactions, which take up a significant part of the available space
in the next blocks. The median estimated feerates for inclusion in the next, in
the next three and the next six blocks, increase sharply at around 13:00 UTC.
The next block estimate stays at a high level for a few hours. The three and six
block estimates continue to increase to a maximum between 16:00 and 17:00 UTC.&lt;/p&gt;
&lt;p&gt;&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/effect-estimated-feerate.png'
alt='Plot showing the effect on the feerate estimators'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
&lt;/p&gt;
&lt;p&gt;The median estimated feerate is calculated by aggregating multiple estimates.
For each feerate estimator, the estimates between September 2019 and March 2020
are grouped by minute of day&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. The median feerate estimate is picked and
averaged with the median estimates of other estimators for the block target. A
list of public feerate estimators with their block targets can be found
&lt;a href="https://b10c.me/blog/003-a-list-of-public-bitcoin-feerate-estimation-apis/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The feerates of the newly arriving transactions react to the increased
estimates. The average feerate sharply increases at around 13:00 UTC and
continues to rise over the next hours. At about 15:30 UTC, the daily maximum is
reached. From there on, the feerate starts to decrease slowly. It takes until
around 21:00 UTC before it sinks below the level of the initial spike. At 13:00
UTC, it is afternoon in Europe and morning in the US. The higher network
activity at this time likely amplifies and lengthens the effect of the BitMex
broadcast. However, most of the near-vertical increase is presumably caused by
BitMEX alone. The effect of the US business hours is visible as a slow increase
and decrease over multiple hours.&lt;/p&gt;
&lt;p&gt;&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/effect-observed-feerate.png'
alt='Plot showing the effect on the observed feerate'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
&lt;/p&gt;
&lt;p&gt;The feerate relative to midnight UTC is grouped by minute of day. The group
average is plotted.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Calculating the additional fees Bitcoin users pay as a result of the BitMEX
broadcast is hard. There is no data for days without a broadcast to compare to.
However, the magnitude of the effect can be estimated. Therefore, the following
assumption about the effect is made: The BitMEX broadcast causes an average
feerate increase by 4 sat/vbyte between 13:00 UTC and 21:00 UTC. The blue area
in the figure above visualizes the assumption. On average, 42.5 vMB of
non-BitMEX transactions arrive in the eight hours between 13:00 UTC and 21:00
UTC. If 4 additional satoshi are paid for each broadcast vbyte, then a total of
1.7 BTC of additional fees are paid by Bitcoin users per day due to the BitMEX
broadcast. This represents about 17% of the total fees arriving during the eight
hours and about 6.8% of the total daily transaction fees. However, it is unclear
if the assumption holds. The estimate can only show the magnitude of the average
daily effect. One or even multiple days without a BitMEX broadcast would be
needed to further study it.&lt;/p&gt;
&lt;p&gt;Both the median block feerate and the 5th percentile block feerate spike and
stay at an elevated level before slowly declining to pre-broadcast levels at
around 22:00 UTC. Transactions send with a low feerate might take a few hours
until they are included in a block during this period. The 5th percentile block
feerate represents nearly the minimum feerate included a block while not
picking up, for example, the parents of child-pays-for-parent transactions.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/effect-block-feerate.png'
alt='Plot showing the effect on the block feerate'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
&lt;p&gt;The median and 5th percentile block feerate are calculated from the blocks in
the blockchain. The observed block arrival time (not the miner timestamp in the
block header) is used to group the blocks by minute of day. The group median
and the 5th percentile are plotted.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The median time-to-confirmation, the time between a transaction being first
observed and its confirmation in a block, spikes between 13:00 and 14:00 UTC and
remains at a slightly elevated level until around 21:00 UTC.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/02-bitmex-broadcast/effect-time-to-confirmation.png'
alt='Plot showing the effect on the time to first confirmation'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
&lt;p&gt;Transactions are grouped by the minute of day of their arrival time. The median
time-to-confirmation for each minute of day is plotted. Only transactions seen
in the mempool and a block are used. Replaced transactions are ignored.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="improvements"&gt;Improvements&lt;/h3&gt;
&lt;p&gt;The effects can be minimized by reducing the number of bytes broadcast to the
Bitcoin network. This can be achieved both by decreasing the transaction count
and size of the individual transactions.&lt;/p&gt;
&lt;p&gt;The four uncompressed public keys used in every BitMEX input could be replaced
by compressed public keys. An uncompressed public key is 65 bytes long and can
be encoded in 33 bytes as a compressed public key by leaving out redundant data.
Wallets started using compressed public keys &lt;a href="https://transactionfee.info/charts/bitcoin-script-pubkey-compression/?start=2011-07-26&amp;amp;end=2013-03-13"&gt;as early as 2012&lt;/a&gt;, and &lt;a href="https://transactionfee.info/charts/bitcoin-script-pubkey-compression/?start=2016-11-01"&gt;by
2017 more than 95%&lt;/a&gt; of all public keys added to the blockchain per day are
compressed&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. By using compressed public keys, BitMEX could reduce their
transaction size by as much as 23%.&lt;/p&gt;
&lt;p&gt;Transaction batching could help to minimize the amount of 3-of-4 multisig change
outputs being created. Spending the 3-of-4 multisig outputs makes up for most of
the block space used by BitMEX. Every output created needs to be spent by
supplying three signatures of around 71 bytes each and a redeem script with four
uncompressed public keys of 65 bytes each. This totals at around 532 bytes per
input. However, an even more block space-efficient alternative would be to use a
non-multisig wallet for user deposits, which are periodically consolidated into
a multisig wallet. Spending a few high-value 3-of-4 multisig outputs for
multiple withdrawals batched together greatly reduces the overall transaction
count. Additionally, BitMEX operational costs for manually reviewing and
processing a few high-value transactions per day would likely be lower than they
currently are for reviewing a few thousand lower-value withdrawals.&lt;/p&gt;
&lt;p&gt;Spending SegWit would help as well. The large scripts used in the 3-of-4 P2SH
multisig inputs would be placed in the witness data. There they have less effect
on the transaction weight. In December 2019, BitMEX &lt;a href="https://blog.bitmex.com/bitmex-enables-bech32-sending-support/"&gt;mentioned&lt;/a&gt; that their
priority lies on upgrading their wallet to use P2SH wrapped SegWit. They
estimated around 65% in transaction weight savings for an average withdrawal
transaction.&lt;/p&gt;
&lt;p&gt;Once activated, BitMEX users and the whole network could greatly benefit from
BitMEX utilizing Schnorr and Taproot. The three ECDSA signatures in the inputs
of BitMEX transactions with around 71 bytes each can be replaced by a single 64
bytes aggregate signature. The four uncompressed public keys of 65 bytes each
can be replaced by a single 32 byte tweaked public key. This public key would be
an aggregate of the three most commonly used public keys. It could be tweaked
for additional spending conditions&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. A single P2TR (Pay-to-Taproot) input
does account for around 57 vbytes. This is an 89% reduction when compared to the
current input size. Combined with output batching, this could drastically
minimize the on-chain footprint of the BitMEX broadcast. Additionally, Taproot
would remove the unique fingerprint of BitMEX transactions, which would, in
turn, increase the privacy of BitMEX users.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;By using the fingerprint of BitMEX transactions, their footprint on the Bitcoin
network is observed and discussed. The daily broadcast has a significant impact
on the Bitcoin network and user fees. By utilizing scaling techniques, some of
which have been industry standards for multiple years, the impact could be
reduced. BitMEX is stepping in the right direction by planning to switch to
nested SegWit. They, however, shouldn&amp;rsquo;t stop there.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: I&amp;rsquo;ve written this article to educate and inform. It&amp;rsquo;s published
without bad intentions and no malice aforethought against BitMEX. I have not
been paid to cover this topic by neither BitMEX nor a competitor nor anybody
else. The information and data herein have been obtained from sources I believe
to be reliable. I make no representation or warranty as to its accuracy,
completeness, or correctness. This article has not been extensively
peer-reviewed.&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;These usually don&amp;rsquo;t contain many inputs, which is untypical for
consolidations. On second thought, these might as well be withdrawals to other
accounts on BitMEX.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I suspect that BitMEX reuses three of the four public keys of their
multi-signature setup for multiple addresses (redeem scripts). A fourth and
extra public key is used to iterate the redeem scripts until a &amp;lsquo;3BMex&amp;rsquo; prefixed
vanity address is found. This is clever but wastes 1+65 bytes for each UTXO
spent. Additionally, it might not have any security benefit if the fourth
private key is not kept.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Minute of day: Imagine a vector with 24*60 (1440) entries. The first would
be for minute 00:00, the second for 00:01 the sixty-third for minute 01:02, and
the last for minute 23:59.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;I assume that BitMEX currently accounts for the majority uncompressed
public keys added to the blockchain per day.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;If the second footnote holds, then the tweak might not even be required.
Bonus: It could, however, be used to create vanity addresses prefixed with, for
example, &lt;code&gt;'bc1mex'&lt;/code&gt; (&lt;code&gt;'bc1bmex'&lt;/code&gt; is not possible as the character &lt;code&gt;'b'&lt;/code&gt; can&amp;rsquo;t be
used in the data part of the Bech32 address format).&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>The stair-pattern in time-locked Bitcoin transactions</title><link>https://b10c.me/observations/01-locktime-stairs/</link><pubDate>Mon, 27 Apr 2020 13:00:00 +0000</pubDate><guid>https://b10c.me/observations/01-locktime-stairs/</guid><description>&lt;p&gt;Some of the regularly used Bitcoin wallets, for example, the Bitcoin Core wallet
and the Electrum Bitcoin Wallet, set the locktime of newly constructed
transactions to the current block height. This is as an anti-fee-sniping
measure and visible as a stair-like pattern when plotting time-locked
transactions by their mempool arrival time and locktime. The plot, however,
reveals transactions time-locked to a future block height. These should,
usually, not be relayed through the Bitcoin network.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Bitcoin transactions can contain so-called time locks as a time-based validity
condition. Different types of time locks are used in Bitcoin. The focus here
lies on transactions time-locked with a lock-by-block-height to an absolute
block height by utilizing the &lt;code&gt;nLocktime&lt;/code&gt; field. This field holds a 32-bit
unsigned integer and is present in every Bitcoin transaction. A &lt;code&gt;nLocktime&lt;/code&gt;
value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;500000000&lt;/code&gt; specifies a lock-by-block-height. The Bitcoin
consensus rules define that a transaction with a lock-by-block-height of &lt;code&gt;n&lt;/code&gt; can
only be included in a block with a height of &lt;code&gt;n&lt;/code&gt; &lt;code&gt;+&lt;/code&gt; &lt;code&gt;1&lt;/code&gt; or higher. Likewise, a
block with the height &lt;code&gt;n&lt;/code&gt; can only include transactions with a locktime of
&lt;code&gt;n&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;1&lt;/code&gt; or less. At least one input of the transaction must have a
&lt;code&gt;nSequence&lt;/code&gt; below &lt;code&gt;0xFFFFFFFF&lt;/code&gt; for the time lock to be enforced. Otherwise, the
lock is ignored. The Bitcoin Core software does not relay transactions with an
enforced locktime higher than the current block height.&lt;/p&gt;
&lt;p&gt;Some Bitcoin wallets specify a lock-by-block-height with the current height when
creating a new transaction. This makes a potentially disruptive mining strategy,
called fee-sniping, less profitable. Fee-sniping is currently not used by miners
but could cause chain reorgs in the future. Since the block subsidy declines to
zero over time, transaction fees will become the main monetary incentive for
miners. If a recent block contains a transaction paying a relatively high fee,
then it could be profitable for a larger miner to attempt to reorg this block.
By mining a replacement block, the sniping miner can pay out the transaction
fees to himself, as long as his block ends up in the strong-chain. The
fee-sniping miner is not limited to pick the same transactions as the miner he
is trying to snipe. He would want to maximize his profitability by picking the
highest feerate transactions from both the to-be-replaced-block and the recently
broadcast transactions currently residing in the mempool. The more hashrate a
fee-sniping miner controls, the higher is the probability that he will win the
block-race. Miners have the risk of losing such a block-race, leaving them
without reward.&lt;/p&gt;
&lt;p&gt;A constant backlog of transactions paying a similar or even the same feerate
would be needed to incentivize miners to move the chain forward. When mining a
next block yields roughly the same revenue as sniping the previous, then a
rational miner would not start a block-race. However, setting the current block
height as a lock-by-block-height forbids a fee-sniping miner to use transactions
from the mempool his replacement block. It creates an incentive to move the
chain forward to be able to include the next batch of time-locked transactions
in a block. While this does not fully mitigate fee-sniping, it reduces
profitability. The more transactions enforce a time lock to the current block
height, the less profitable fee-sniping is.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/observations/01-locktime-stairs/fee-sniping.png'
alt='anti-fee-sniping example'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
Example: Making fee-sniping less profitable by setting the current block height
as a lock-by-block-height. Block &lt;code&gt;b2&lt;/code&gt;, with a height of &lt;code&gt;n&lt;/code&gt; &lt;code&gt;+&lt;/code&gt; &lt;code&gt;1&lt;/code&gt;, pays its
miner a high fee. Another miner, Eve, attempts to snipe block &lt;code&gt;b2&lt;/code&gt; with block
&lt;code&gt;b2*&lt;/code&gt;. The mempool includes two transactions paying a relatively high fee of one
bitcoin each. However, Eve can only include transaction &lt;code&gt;tx1&lt;/code&gt; in block &lt;code&gt;b2*&lt;/code&gt;.
Transaction &lt;code&gt;tx2&lt;/code&gt; can not be included in &lt;code&gt;b2*&lt;/code&gt; as it is only valid in a block
with a hight of &lt;code&gt;n&lt;/code&gt; &lt;code&gt;+&lt;/code&gt; &lt;code&gt;2&lt;/code&gt; or more.
&lt;/div&gt;
&lt;p&gt;When the block height increases, the locktime newly constructed transactions
should increase as well. Transactions with the locktime set to the current block
height should appear as a stair-pattern when plotted by mempool arrival time and
locktime.&lt;/p&gt;
&lt;h3 id="observation"&gt;Observation&lt;/h3&gt;
&lt;p&gt;Transactions observed by the &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction Monitor&lt;/a&gt; project on
December 17, 2019 are plotted. A stair-pattern of transactions with a
lock-by-block-height is visible.&lt;/p&gt;
&lt;p&gt;&lt;div class="chart mt-4" id='chart-with-bug-1' style="min-height: 600px;"&gt;
&lt;noscript&gt;
&lt;div class="alert alert-warning" role="alert"&gt;
Javascript is disabled or blocked.
Here is a screenshot of the interactive chart you are not seeing.
&lt;/div&gt;
&lt;img class="img img-fluid rounded mx-auto m-1 d-block" alt="noscript chart replacement" src='https://b10c.me/data/observations/01-locktime-stairs/noscript-chart-1.png'&gt;
&lt;/noscript&gt;
&lt;/div&gt;
&lt;style&gt;
.tooltip {
position: absolute;
pointer-events: none;
border: 5px solid --var(--body-bg);
background: var(--body-bg);
transition: width 2s;
}
table.table-tooltip {
background: --var(--body-bg);
margin: 5px;
}
.chart {
width: 100%;
position: relative;
}
.chart &gt; div {
position: absolute;
}
svg {
pointer-events: none;
}
circle {
stroke-width: 1px;
opacity: 0.5;
fill: none;
}
&lt;/style&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
The scatterplot shows a dot for each transaction with a locktime between 608507
and 608519 that was broadcast on December 17, 2019, between 12:06 and 14:08 UTC.
The radius represents the feerate the transaction paid. Blocks found in the
displayed timeframe (height 608509 to 608519) are drawn.
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Some transactions are locked to a height below the current block height. These
are likely not broadcast immediately after their creation. This happens, for
example, with high-latency mixers and some CoinJoin implementations.
Additionally, the Bitcoin Core wallet &lt;a href="https://github.com/bitcoin/bitcoin/blob/e6acd9f72c61bf535d9413854b1434ec40633ca0/src/wallet/wallet.cpp#L2490-L2495"&gt;randomly chooses&lt;/a&gt; a locktime up to
100 blocks below the current height for 10% of the signed transactions to
increase the privacy of the not immediately broadcast transactions.&lt;/p&gt;
&lt;p&gt;The plot, however, does show transactions with two different locktimes arriving
at the same time. Transactions time-locked to the current block height and
transactions time-locked to the next block height. Between block 608511 and
block 608512, for example, transactions with a locktime of 608511, the current
height, and 608512, the next block height, arrived. These should, normally, not
be relayed trough the network.&lt;/p&gt;
&lt;h3 id="interpretation"&gt;Interpretation&lt;/h3&gt;
&lt;p&gt;The transactions time-locked to the next block height have inputs with a
&lt;code&gt;nSequence&lt;/code&gt; number of &lt;code&gt;0xFFFFFFFF&lt;/code&gt;. The lock-by-block-height is thus not
enforced. All of these transactions have common properties and share a somewhat
unique fingerprint. All are spending P2SH, nested P2WSH, or P2WSH inputs and are
&lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki"&gt;BIP-69&lt;/a&gt; compliant (inputs and outputs ordered lexicographically). Most of
them pay a feerate of 12.3 sat/vbyte and all spending 2-of-3 multisig inputs.
This leads to the assumption that these transactions are constructed with the
same wallet or by the same entity.&lt;/p&gt;
&lt;p&gt;&lt;div class="chart mt-4" id='chart-with-bug-2' style="min-height: 600px;"&gt;
&lt;noscript&gt;
&lt;div class="alert alert-warning" role="alert"&gt;
Javascript is disabled or blocked.
Here is a screenshot of the interactive chart you are not seeing.
&lt;/div&gt;
&lt;img class="img img-fluid rounded mx-auto m-1 d-block" alt="noscript chart replacement" src='https://b10c.me/data/observations/01-locktime-stairs/noscript-chart-2.png'&gt;
&lt;/noscript&gt;
&lt;/div&gt;
&lt;style&gt;
.tooltip {
position: absolute;
pointer-events: none;
border: 5px solid --var(--body-bg);
background: var(--body-bg);
transition: width 2s;
}
table.table-tooltip {
background: --var(--body-bg);
margin: 5px;
}
.chart {
width: 100%;
position: relative;
}
.chart &gt; div {
position: absolute;
}
svg {
pointer-events: none;
}
circle {
stroke-width: 1px;
opacity: 0.5;
fill: none;
}
&lt;/style&gt;
&lt;div class="mx-3 mx-md-5 mx-lg-5 mb-3 text-justify"&gt;
The same transactions as above are shown but transactions spending multisig
inputs are highlighted in blue.
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The fingerprint, especially spending 2-of-3 multisig, limits the number of
wallets or entities which the transactions could originate from. Reaching out to
a possible entity, which prefers to remain unnamed, proved to be successful.
They confirmed the two off-by-one-errors. Firstly, the transactions should be
time-locked to the current block height and not the next block height. Secondly,
at least one of the inputs should have a nSequence number of &lt;code&gt;0xFFFFFFFF-1&lt;/code&gt; or
less for the time-lock to be enforced. A fix for this has been released in early
2020. However, it will take a while before all instances of the currently
deployed software are upgraded.&lt;/p&gt;
&lt;p&gt;Between September 2019 and March 2020, about 10.9 million (19%) out of the 57.49
million total transactions set a locktime. Out of these, 1.16 million
transactions had an unenforced locktime. That represents 2% of the total and
about 10% of all transactions with a locktime. More than 98% of the transactions
with an unenforced time-lock share the same fingerprint as the transactions
observed on the 17th of December. The remaining 21577 transactions are likely
broadcast by other entities. Out of these, 92.5% have an unenforced
lock-by-block-height and 7.5% an unenforced lock-by-block-time. Some
transactions with unenforced locktimes might have valid use-cases while others
unknowingly set an unenforced locktime.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Some bitcoin wallets reduce the profitability of fee-sniping by time-locking
transactions to the next block. This appears as a stair-pattern when plotting
the arrival time and the locktime of transactions. The plot reveals transactions
with locktimes higher than the current block height. These should, usually, not
be relayed through the Bitcoin network. This leads to the discovery that a large
entity transacting on the Bitcoin network does not properly set the &lt;code&gt;nSequence&lt;/code&gt;
field of their transaction inputs. This allowed for the off-by-one-error in the
transaction locktimes to remain unnoticed.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I thank &lt;a href="https://dtrt.org/"&gt;David Harding&lt;/a&gt; for commenting on an early draft of this article.
I found the background information he provided to be very valuable. Any errors
that remain are my own.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;script src="https://b10c.me/js/d3.js" integrity="sha256-x4D7g5KkbQk6aRjxRFqlCsyMytoqhcisSwVVsVepuuE=" crossorigin="anonymous"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src='https://b10c.me/data/observations/01-locktime-stairs/locktime-charts.js'&gt;&lt;/script&gt;&lt;/p&gt;</description></item><item><title>BIP42 on einundzwanzig Podcast (German)</title><link>https://b10c.me/talks/006-einundzwanzig-bip42/</link><pubDate>Wed, 01 Apr 2020 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/006-einundzwanzig-bip42/</guid><description>&lt;p&gt;&lt;a href="https://twitter.com/dennisreimann"&gt;Dennis Reimann&lt;/a&gt; and I chat about BIP42.
We talk about the background of BIP42, Satoshi&amp;rsquo;s implementation of the subsidy function, the bug in the implementation and the fix which enables a finite monetary supply for Bitcoin.&lt;/p&gt;
&lt;a href='https://anchor.fm/einundzwanzig/episodes/Einundzwanzig-1--April-Special-zum-BIP42-mit-Timo-0xB10C-ec17vv' class="btn btn-outline-danger my-2" role="button"&gt;Podcast (German)&lt;/a&gt;</description></item><item><title>Interview on einundzwanzig Podcast (German)</title><link>https://b10c.me/talks/005-einundzwanzig-podcast/</link><pubDate>Fri, 20 Mar 2020 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/005-einundzwanzig-podcast/</guid><description>&lt;p&gt;&lt;a href="https://twitter.com/dennisreimann"&gt;Dennis Reimann&lt;/a&gt; and I talk about my projects, the Chaincode Labs Residency, and more on the German &lt;a href="https://twitter.com/_einundzwanzig_"&gt;einundzwanzig&lt;/a&gt; Podcast.&lt;/p&gt;
&lt;a href='https://anchor.fm/einundzwanzig/episodes/Einundzwanzig-Interview-04---Mempool-Observer-mit-Timo-0xB10C-eblvpo' class="btn btn-outline-danger my-2" role="button"&gt;Podcast (German)&lt;/a&gt;</description></item><item><title>transactionfee.info (2020 version)</title><link>https://b10c.me/projects/transactionfee-info-2020-version/</link><pubDate>Fri, 31 Jan 2020 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/transactionfee-info-2020-version/</guid><description>&lt;p&gt;The website &lt;a href="https://transactionfee.info"&gt;transactionfee.info&lt;/a&gt; shows Bitcoin protocol layer statistics.
This includes statistics about Bitcoin transactions, their in- and outputs, about blocks and Bitcoin scripts.
The project is a joint effort with &lt;a href="https://www.bitrefill.com/?utm_source=b10c_me"&gt;Bitrefill&lt;/a&gt; CEO &lt;a href="https://twitter.com/ziggamon"&gt;@ziggamon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The transactionfee.info website was initially designed in 2018 to raise awareness about the inefficient use of block space by exchanges, services, and wallets.
Read about the 2018 version &lt;a href="https://b10c.me/projects"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We iterated and released the 2020 version as we had reached our goal of raising awareness and saw the usage of the fee efficiency checker decline over 2019.
&lt;a href="https://blockstream.info"&gt;blockstream.info&lt;/a&gt; can be used as an alternative to our fee efficiency checker for now.&lt;/p&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Some of the new charts I&amp;#39;m excited about below:&lt;br&gt; &lt;a href="https://t.co/VHtSX8o8Fc"&gt;https://t.co/VHtSX8o8Fc&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1223265422071541767?ref_src=twsrc%5Etfw"&gt;January 31, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;I was surprised that there is still somebody using uncompressed PubKeys &lt;a href="https://t.co/LB8vXLtekv"&gt;https://t.co/LB8vXLtekv&lt;/a&gt; &lt;br&gt;&lt;br&gt;(yes, for example you &lt;a href="https://twitter.com/brbtcoficial?ref_src=twsrc%5Etfw"&gt;@brbtcoficial&lt;/a&gt;)&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1223265425263415297?ref_src=twsrc%5Etfw"&gt;January 31, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item><item><title>Contribution: Colab version of the Optech Schnorr / Taproot Workshop</title><link>https://b10c.me/projects/contribution-optech-taproot-workshop-colab/</link><pubDate>Wed, 27 Nov 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/contribution-optech-taproot-workshop-colab/</guid><description>&lt;p&gt;&lt;a href="https://bitcoinops.org/"&gt;Bitcoin Optech&lt;/a&gt; created a &lt;a href="https://bitcoinops.org/en/schorr-taproot-workshop/"&gt;workshop&lt;/a&gt; explaining the Schnorr and Taproot upgrade to engineers.
However, users needed to compile a patched version of Bitcoin Core with Taproot support and download and set up the Jupyter notebooks.&lt;/p&gt;
&lt;p&gt;This causes &lt;a href="https://github.com/bitcoinops/taproot-workshop/issues/145"&gt;friction&lt;/a&gt; for users who want to quickly try out the notebooks.
I&amp;rsquo;ve &lt;a href="https://github.com/bitcoinops/taproot-workshop/pull/157"&gt;contributed&lt;/a&gt; the option to run the notebooks in Google Colab, a cloud-based Jupyter notebook environment.
This reduced the setup time to less than 20 seconds and the only requirement is a Google account.
The links to the notebooks in the Google Cloud can be found in the &lt;a href="https://github.com/bitcoinops/taproot-workshop#google-colab-cloud"&gt;Bitcoin Optech Schnorr / Taproot Workshop repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Our schnorr/taproot workshop notebooks are now available on Google Colab so you can play with taproot without having to clone and build a bitcoind locally: &lt;a href="https://t.co/zfeH9YmrLO"&gt;https://t.co/zfeH9YmrLO&lt;/a&gt;&lt;br&gt;&lt;br&gt;Thanks to &lt;a href="https://twitter.com/0xB10C?ref_src=twsrc%5Etfw"&gt;@0xB10C&lt;/a&gt; for setting these up!&lt;/p&gt;&amp;mdash; Bitcoin Optech (@bitcoinoptech) &lt;a href="https://twitter.com/bitcoinoptech/status/1199771997783363584?ref_src=twsrc%5Etfw"&gt;November 27, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;If you have not tried out the schnorr/taproot workshop demos yet, the Google Colab option has made it really easy! &lt;br&gt;&lt;br&gt;Thanks &lt;a href="https://twitter.com/0xB10C?ref_src=twsrc%5Etfw"&gt;@0xB10C&lt;/a&gt; ! &lt;a href="https://t.co/Eu0hwTovT6"&gt;https://t.co/Eu0hwTovT6&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mike Schmidt (@bitschmidty) &lt;a href="https://twitter.com/bitschmidty/status/1202673590996484096?ref_src=twsrc%5Etfw"&gt;December 5, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item><item><title>Frequently Asked Questions: Bitcoin Transaction Monitor</title><link>https://b10c.me/blog/005-bitcoin-transaction-monitor-faq/</link><pubDate>Thu, 10 Oct 2019 09:09:09 +0000</pubDate><guid>https://b10c.me/blog/005-bitcoin-transaction-monitor-faq/</guid><description>&lt;p&gt;The &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction Monitor&lt;/a&gt; provides deeper insights into the usage of the Bitcoin network by showing transactions by time and feerate.
This post answers frequently asked questions about the Bitcoin Transaction Monitor itself.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/005-bitcoin-transaction-monitor-faq/header.png'
alt='header image'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Bitcoin Transaction Monitor: transactions plotted over time and feerate&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h5 id="why-did-you-build-a-bitcoin-transaction-monitor"&gt;Why did you build a Bitcoin Transaction Monitor?&lt;/h5&gt;
&lt;p&gt;With Bitcoin, a permissionless network has been created, where everybody can join.
Companies, services, and individual users broadcast transactions to the network.
Plotting these transactions by &lt;em&gt;arrival time&lt;/em&gt; and &lt;em&gt;feerate&lt;/em&gt; reveals interesting activity patterns.
The Bitcoin Transaction Monitor is built to visualize, share and inform about these patterns.
I hope it lets us gain deeper insights into the usage of the Bitcoin network.&lt;/p&gt;
&lt;h5 id="where-do-you-get-the-data-about-the-transactions-from"&gt;Where do you get the data about the transactions from?&lt;/h5&gt;
&lt;p&gt;I run a Bitcoin Core node connected to the Bitcoin network, which passes valid transactions to &lt;code&gt;memod&lt;/code&gt; (mempool observer daemon) over the ZMQ-interface.
Each passed transaction is processed by &lt;code&gt;memod&lt;/code&gt; and written into a database.&lt;/p&gt;
&lt;h5 id="doesnt-the-transaction-monitor-reveal-private-information-about-transactions-"&gt;Doesn&amp;rsquo;t the Transaction Monitor reveal private information about transactions ?&lt;/h5&gt;
&lt;p&gt;The Bitcoin Transaction Monitor shows activity and usage patterns of the Bitcoin network.
This information can be (and probably is already) used by bad actors to weaken the privacy or even completely depseudonymize transactions.
Yet this information broadcasted on the Bitcoin network is entirely public.
Raising the awareness of what transactions can reveal is far more valuable than hiding public information.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If I can build a Transaction Monitor in my free time that visualizes this data and could run on your laptop, what can a motivated bad actor do with far more resources?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id="what-are-future-ideas-for-the-bitcoin-transaction-monitor"&gt;What are future ideas for the Bitcoin Transaction Monitor?&lt;/h5&gt;
&lt;p&gt;Provided I have the time and come up with an efficient architecture I&amp;rsquo;d like to archive and display historical data.
Additionally, providing a live visualization of incoming transactions would be interesting.
There is a &lt;a href="https://github.com/0xB10C/memo/issues/50"&gt;GitHub issue&lt;/a&gt; which has a few feature suggestions.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://b10c.me/projects/bitcoin-transaction-monitor/' class="btn btn-outline-danger my-2" role="button"&gt;Project: Bitcoin Transaction Monitor&lt;/a&gt;
&lt;a href='https://github.com/0xB10C/memo/issues/50' class="btn btn-outline-danger my-2" role="button"&gt;GitHub issue with feature suggestions&lt;/a&gt;
&lt;/p&gt;</description></item><item><title>Bitcoin Transaction Monitor</title><link>https://b10c.me/projects/bitcoin-transaction-monitor/</link><pubDate>Thu, 10 Oct 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/bitcoin-transaction-monitor/</guid><description>&lt;p&gt;Whenever you, an exchange or somebody else sends a Bitcoin transaction, it gets broadcast to all nodes in the Bitcoin network.
Each broadcast transaction is represented by a dot on the &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction Monitor&lt;/a&gt; scatterplot.
The transactions are arranged by the time of arrival at my Bitcoin node and its feerate (fee per size).
The plot reveals activity patterns of wallets, exchanges, and users transacting on the Bitcoin network.&lt;/p&gt;
&lt;h5 id="building-the-transaction-monitor"&gt;Building the Transaction Monitor&lt;/h5&gt;
&lt;p&gt;I first got the idea of plotting Bitcoin transactions by their arrival time and feerate as I was working on my &lt;a href="https://b10c.me/projects/mempool-observer-2019-version/"&gt;mempool.observer&lt;/a&gt; project.
Plotting the output of Bitcoin Core&amp;rsquo;s &lt;code&gt;getrawmempool&lt;/code&gt; RPC generated chart below.
While there are big white areas of confirmed transactions, there are definitely activity patterns visible.
This sparked my interest and I started to dive deeper.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/bitcoin-transaction-monitor/early-idea.png'
alt='plotting the output of the getrawmempool RPC of Bitcoin Core'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;plotting the output of the getrawmempool RPC of Bitcoin Core&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;My first goal was to find a way to efficiently extract the incoming transactions.
Polling the mempool via the &lt;code&gt;getrawmempool&lt;/code&gt; RPC was not an option.
The RPC can run for multiple seconds if the mempool holds a few thousand transactions which is not too uncommon.
I started profiling the RPC but found no obvious way to speed it up.
Additionally, by polling, I would miss the confirmed transactions that entered the mempool between my last poll and a new block.&lt;/p&gt;
&lt;p&gt;Bitcoin Core can be configured to publish transactions that enter the mempool via a ZMQ interface.
These ZMQ messages contain the raw binary Bitcoin transaction.
However, as I previously noted in my blog post &lt;a href="https://b10c.me/blog/002-plotting-the-bitcoin-feerate-distribution/"&gt;Plotting the Bitcoin Feerate Distribution&lt;/a&gt;, Bitcoin transactions don&amp;rsquo;t contain the fee they pay as an explicit value.
The fee is implicitly set by leaving a bit of the previous output amount to the miner when creating the new outputs.
This means that I would have had to query the transaction fee for every transaction that arrived.
As this would have made the project quite resource hungry I started thinking about my alternatives.&lt;/p&gt;
&lt;p&gt;The best performing alternative I found was to &lt;a href="https://github.com/0xB10C/bitcoin/commit/f03785b4e0c0aa4d74a2934e20ed607d6b8758e2"&gt;patch&lt;/a&gt; my Bitcoin Core instance and to create a custom ZMQ publisher that sends the transaction and the fee.
This approach allowed me to just subscribe to my newly added ZMQ publisher and be able to extract this data.
The biggest downside here is probably that it meant that this creates a big hurdle for somebody wanting to self-host the Transaction Monitor.&lt;/p&gt;
&lt;p&gt;The next step was to build a backend that keeps the last few thousand transactions and attach an API to it for retrieval in a frontend.
I choose a Redis &lt;a href="https://redislabs.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/"&gt;sorted-set&lt;/a&gt; and used the transaction arrival timestamp as score.
This allowed me to quickly retrieve the most recent entries while being able to drop older transactions.
I ended up implementing a 30-second cache and gzipped the JSON responses to speed up API calls even more.
All in all, this allowed me to reduce the average response time to around 700ms (from more than 12s when starting off) and to respond to concurrent requests with nearly no increase in the response time.&lt;/p&gt;
&lt;p&gt;For maximal flexibility, I choose &lt;a href="https://d3js.org/"&gt;D3.js&lt;/a&gt; to visualize the data in the frontend.
D3.js comes with a steep learning curve but was the only library that allowed for the interactivity and performance I aimed for.
At first, I tried to draw the dots for the transactions as objects in an SVG.
However, this is slow with multiple thousand transactions drawn.
The alternative was to use an HTML canvas that basically acts as a bitmap image.
By using a Canvas the interactivity and the data-bindings that D3.js offers are lost.
I ended up using a &lt;a href="https://en.wikipedia.org/wiki/Quadtree"&gt;Quadtree&lt;/a&gt; (a tree data structure where each internal node has four children) to find transactions close to the user&amp;rsquo;s mouse pointer which enabled me to restore the interactivity while keeping the performance high.&lt;/p&gt;
&lt;p&gt;I wanted to be able to filter transactions by their properties and thus I wrote a Golang library that allows me to answer questions about raw Bitcoin transactions.
This library is called &lt;a href="https://github.com/0xB10C/rawtx"&gt;rawtx&lt;/a&gt; (&lt;a href="https://b10c.me/projects/library-rawtx/"&gt;project page&lt;/a&gt;).&lt;/p&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;The Bitcoin Transaction Monitor lets you now highlight transactions based on the block they are included in.&lt;a href="https://t.co/q1sbHn4Tqy"&gt;https://t.co/q1sbHn4Tqy&lt;/a&gt; &lt;a href="https://t.co/hQcKvocg6O"&gt;pic.twitter.com/hQcKvocg6O&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1217471074671243266?ref_src=twsrc%5Etfw"&gt;January 15, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;
&lt;h4 id="privacy-implications"&gt;Privacy Implications&lt;/h4&gt;
&lt;p&gt;I reflected a bit about the privacy implications before publishing the Transaction Monitor.
For somebody familiar with the Bitcoin ecosystem, it&amp;rsquo;s companies, wallets, and users, it&amp;rsquo;s fairly trivial to attribute some transactions to entities using the Transaction Monitor.
This was a point for not publishing it.
However, I think there is a bigger total gain for the community in raising the awareness that the pseudonymity sets are small.
Users and companies often leave distinct &lt;em&gt;mempool-fingerprints&lt;/em&gt;.
I&amp;rsquo;ve asked the following question in my &lt;a href="https://b10c.me/blog/005-bitcoin-transaction-monitor-faq/"&gt;Frequently Asked Questions: Bitcoin Transaction Monitor&lt;/a&gt; post as well:&lt;/p&gt;
&lt;p&gt;Everything I display is public information. If I can build a Transaction Monitor in my free time that visualizes this data and could run on your laptop, what can a motivated bad actor do with far more resources?&lt;/p&gt;
&lt;h4 id="prior-art"&gt;Prior Art&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;basil00&amp;rsquo;s &lt;a href="https://reqrypt.org/txmon.html"&gt;TxMon&lt;/a&gt;, &lt;a href="http://archive.ph/gr11T"&gt;archived&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Conor Scott&amp;rsquo;s &lt;a href="https://github.com/conscott/draw_mempool"&gt;draw_mempool (master)&lt;/a&gt;, &lt;a href="https://github.com/conscott/draw_mempool/tree/9feb575f1597ed9e42140af9fbec924702578fb3"&gt;screenshots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Jan Vornberger&amp;rsquo;s &lt;a href="http://www.bitcoinmonitor.com/"&gt;bitcoinmonitor.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;How is the Bitcoin network being used?&lt;br&gt;&lt;br&gt;I&amp;#39;ve build a Bitcoin Transaction Monitor to gain deeper insights on the Bitcoin network usage. Transactions are plotted by time and feerate, which reveals interesting activity patterns. &lt;a href="https://t.co/CWgyPpdjJo"&gt;https://t.co/CWgyPpdjJo&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1182295216709148672?ref_src=twsrc%5Etfw"&gt;October 10, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;This is a neat new visualization of recent bitcoin transactions that makes it abundantly clear that some services are still hard coding their fee rates - there are no known fee estimators that recommended over 60 satoshis / vbyte during this period. &lt;a href="https://t.co/VKJ2ivg6xE"&gt;https://t.co/VKJ2ivg6xE&lt;/a&gt; &lt;a href="https://t.co/0MoYtWkvco"&gt;pic.twitter.com/0MoYtWkvco&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jameson Lopp (@lopp) &lt;a href="https://twitter.com/lopp/status/1182302770285748226?ref_src=twsrc%5Etfw"&gt;October 10, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item><item><title>ShiftCrypto sponsoring mempool.observer</title><link>https://b10c.me/funding/2019-shiftcrypto-support/</link><pubDate>Tue, 01 Oct 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2019-shiftcrypto-support/</guid><description>&lt;p&gt;&lt;a href="https://bitbox.swiss/"&gt;ShiftCrypto&lt;/a&gt; sponsored some of my work on &lt;a href="https://b10c.me/projects/mempool-observer-2019-version/"&gt;mempool.observer&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>rawtx library</title><link>https://b10c.me/projects/library-rawtx/</link><pubDate>Sat, 28 Sep 2019 23:53:07 +0000</pubDate><guid>https://b10c.me/projects/library-rawtx/</guid><description>&lt;p&gt;The rawtx Golang module helps you (and me) to answer questions about raw Bitcoin transactions, their inputs, outputs, and scripts.
I use the rawtx package for example in my &lt;a href="https://b10c.me/projects/bitcoin-transaction-monitor/"&gt;Bitcoin Transaction Monitor&lt;/a&gt; and &lt;a href="https://b10c.me/projects/transactionfee-info-2020-version/"&gt;transactionfee.info&lt;/a&gt; projects.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/rawtx/rawtx.png'
alt='rawtx logo'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The package has a high unit test coverage (&amp;gt; 90%) and is battle-tested (all mainnet Bitcoin transactions have been analyzed at least once).
However, I&amp;rsquo;d strongly advise to not use it in a consensus-critical environment.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://github.com/0xB10C/rawtx/tree/master' class="btn btn-outline-danger my-2" role="button"&gt;rawtx on GitHub&lt;/a&gt;
&lt;a href='https://godoc.org/github.com/0xB10C/rawtx' class="btn btn-outline-danger my-2" role="button"&gt;Documentation on GoDoc&lt;/a&gt;
&lt;/p&gt;</description></item><item><title>The Incomplete History of Bitcoin Development</title><link>https://b10c.me/blog/004-the-incomplete-history-of-bitcoin-development/</link><pubDate>Sun, 04 Aug 2019 09:09:09 +0000</pubDate><guid>https://b10c.me/blog/004-the-incomplete-history-of-bitcoin-development/</guid><description>&lt;p&gt;To fully understand the rationale behind the current state of Bitcoin development, knowledge about historical events is essential.
This blog post highlights selected historical events, software releases and bug fixes before and after Satoshi left the project.
It additionally contains a section about the current state of Bitcoin development.
The linked &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#timeline-bitcoin-history"&gt;timeline&lt;/a&gt; provides extra detail for each event.&lt;/p&gt;
&lt;p&gt;I wasn&amp;rsquo;t following the Bitcoin space when a majority of these events happened.
A big part of the timeline is adapted from a talk &lt;a href="https://twitter.com/jfnewbery"&gt;John Newbery&lt;/a&gt; gave on the &lt;a href="https://www.meetup.com/BitDevsNYC/events/262321510/"&gt;History and Philosophy of Bitcoin Development&lt;/a&gt;.
The title of this blog post is supposed to remind you that it can&amp;rsquo;t and doesn&amp;rsquo;t include every event.
History is in the beholder&amp;rsquo;s eye.
History evolves.
If you have a suggestion about something missing or want to propose a change, please create an issue in the open-source project &lt;a href="https://github.com/0xB10C/bitcoin-development-history"&gt;bitcoin-development-history&lt;/a&gt;, which is used to generate the attached timeline.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/004-bitcoin-development-history/timeline-2007.png'
alt='picture of the timeline starting in in 2007'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="with-satoshi"&gt;With Satoshi&lt;/h4&gt;
&lt;p&gt;The timeline tells a story beginning in early 2007.
Satoshi Nakamoto starts working on Bitcoin.
The peer-to-peer electronic cash system with no trusted third party.
A system only controlled by the software which its users run.&lt;/p&gt;
&lt;p&gt;Early on contributors join Satoshi working on Bitcoin.
These new contributors add, next to many other things, support for &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2009-release-0-2-0"&gt;Linux&lt;/a&gt; and &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-release-0-3-0"&gt;macOS&lt;/a&gt; to the project.
Over the summer of 2010 Satoshi authors a few critical software changes.
For example, &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-release-0-3-2"&gt;checkpoints&lt;/a&gt; are introduced as a safeguard against malicious peers broadcasting low difficulty chains.
A node enforcing these checkpoints rejects chains that don’t include specific block hashes at specific heights.
Checkpoints are hard-coded by Satoshi alone which in theory allows Satoshi to control which chain the network follows.&lt;/p&gt;
&lt;p&gt;A few days after adding checkpoints Satoshi releases the first consensus change in &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-release-0-3-3"&gt;version v0.3.3&lt;/a&gt;.
Satoshi urges users to upgrade.
Over the next month, multiple &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-release-multiple-0-3-xx"&gt;minor versions&lt;/a&gt; are released.
One of them fixes a &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-bug-overflow-bug"&gt;critical overflow bug&lt;/a&gt;.
This bug was exploited to create two high-value UTXOs.
Satoshi advises miners to reorg the chain containing the blocks with the malicious transactions.&lt;/p&gt;
&lt;p&gt;A week later Satoshi introduces an &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-post-alert-system"&gt;alert system&lt;/a&gt; to inform node operators about similar bugs and problems in the network.
The alert system includes a safe mode.
The safe mode, once triggered, disables all money handling RPC methods in the entire network.
Only Satoshi can create valid network alerts by signing them with a private key.
Some users raise the question of what could happen when a second party, for example, a government, gets access to this key.&lt;/p&gt;
&lt;p&gt;Satoshi has a lot of power over the Bitcoin network at this point.
The main concern here isn&amp;rsquo;t that Satoshi would turn evil and try to destroy the network, but rather that there shouldn&amp;rsquo;t be such a single point of failure in a decentralized system.&lt;/p&gt;
&lt;p&gt;In December 2010 Satoshi opens his &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-post-final"&gt;last thread&lt;/a&gt; on bitcointalk announcing the removal of the safe mode.
Satoshi writes in one of his &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2011-other-last-contact-satoshi"&gt;last emails&lt;/a&gt;:
&lt;em&gt;»I&amp;rsquo;ve moved on to other things. It&amp;rsquo;s in good hands with Gavin and everyone.«&lt;/em&gt;
Some might argue Satoshi stepping away from Bitcoin is one of his greatest contributions.&lt;/p&gt;
&lt;h4 id="without-satoshi"&gt;Without Satoshi&lt;/h4&gt;
&lt;p&gt;Around the same time, the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2010-other-moved-to-github"&gt;development process&lt;/a&gt; moves from SVN to GitHub, which leads to longtime &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2011-other-new-contributors"&gt;contributors&lt;/a&gt; like TheBlueMatt, sipa, laanwj and gmaxwell joining the project.
In mid-2011 the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2011-other-first-bip"&gt;BIP process&lt;/a&gt; for Bitcoin Improvement Proposals is introduced.
In the last quarter of 2011 and the first months of 2012, the community discusses &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2011-other-p2sh"&gt;various proposals&lt;/a&gt;, which would allow the receiver of a transaction to specify the script needed to spend it.
Out of them, P2SH is merged.&lt;/p&gt;
&lt;p&gt;In fall 2012 the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2012-other-bitcoin-foundation"&gt;Bitcoin Foundation&lt;/a&gt; is announced.
The Bitcoin Foundation hopes to achieve for Bitcoin what the Linux Foundation does for Linux.
Some people raise the fear of development centralization in the announcement thread.&lt;/p&gt;
&lt;p&gt;Bitcoin version &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2013-release-0-8-0"&gt;v0.8.0 is released&lt;/a&gt; in spring 2013.
Two weeks after the release an &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2013-bug-hardfork"&gt;unexpected hardfork&lt;/a&gt; splits the network in nodes that have and haven&amp;rsquo;t yet upgraded.
The hardfork is resolved fairly quickly.
Different miners react by shifting their hashpower to the chain valid for both upgraded and not upgraded nodes.&lt;/p&gt;
&lt;p&gt;In late 2013 the Bitcoin software is &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2013-other-rebranding-to-core"&gt;rebranded to Bitcoin Core&lt;/a&gt;.
In the following year companies such as &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2014-company-chaincode"&gt;Chaincode&lt;/a&gt; and &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2014-company-blockstream"&gt;Blockstream&lt;/a&gt; are founded.
Later the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2015-other-mit-dci"&gt;MIT Digital Currency Initiative&lt;/a&gt; joins Chaincode and Blockstream by paying developers and researchers to work on Bitcoin.
In February 2015 Joseph Poon and Tadge Dryja release the first draft of the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2015-other-lightning-whitepaper"&gt;Lightning Whitepaper&lt;/a&gt;.
The next year Luke Dashjr revises the BIP process with &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2016-other-bip-2"&gt;BIP 2&lt;/a&gt; and the Bitcoin Core release v0.13.0 includes &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2016-release-0-13-1"&gt;SegWit&lt;/a&gt; as a softfork.
In November 2016 the &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2016-other-alert-system-retired"&gt;alert system&lt;/a&gt; is retired and in August 2017 &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2017-other-segwit-activated"&gt;SegWit&lt;/a&gt; gets activated.
The year 2019 brings a new company, &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2019-company-squarecrypto"&gt;Square Crypto&lt;/a&gt;, sponsoring Bitcoin development.
In May Pieter Wuille proposes &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2019-post-taproot"&gt;BIP taproot&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="the-current-state-of-bitcoin-development"&gt;The current state of Bitcoin development&lt;/h4&gt;
&lt;p&gt;Over the years the Bitcoin development culture became more decentralized, well-defined and rigorous.
There are currently &lt;a href="https://bitcointalk.org/index.php?topic=1774750.0"&gt;six Bitcoin Core maintainers&lt;/a&gt;, distributed over three continents.
Only they can merge commits by contributors.
Before commits get merged, however, they have to go through a &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#peer-review"&gt;review process&lt;/a&gt;, which has gotten a lot stricter.&lt;/p&gt;
&lt;p&gt;For example, a competing proposal, to the earlier mentioned P2SH, was &lt;a href="https://b10c.me/projects/bitcoin-dev-history/#2011-other-p2sh"&gt;&lt;code&gt;OP_EVAL&lt;/code&gt;&lt;/a&gt;.
The &lt;a href="https://github.com/bitcoin/bitcoin/pull/669"&gt;pull request&lt;/a&gt; implementing &lt;code&gt;OP_EVAL&lt;/code&gt; was merged at the end of 2011.
It had only one reviewer, though it changes consensus-critical code.
Russell O’Connor opened an &lt;a href="https://github.com/bitcoin/bitcoin/issues/729"&gt;issue&lt;/a&gt; criticizing parts of the implementation and that such a big and consensus-critical change should have had a lot more review and testing.&lt;/p&gt;
&lt;p&gt;This fueled an ongoing discussion on how to ensure higher code quality through more testing and review.
Today each pull request should at least be reviewed by multiple developers.
If a change touches security-critical or even consensus-critical code, the review process needs many reviewers, a lot of testing and usually spans over multiple months.
John Newbery, an active Bitcoin Core contributor, told me that there is &lt;em&gt;&amp;ldquo;no chance a consensus change would be merged with a single reviewer today&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A lot of work went into automated testing.
There are &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/src/test/README.md"&gt;unit-tests&lt;/a&gt; written in C++ and &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/test/functional/README.md"&gt;functional-test&lt;/a&gt; written in Python.
Every non-trivial change should update existing tests or add new ones to the frameworks.
Next to unit- and functional-tests, there is an initiative to do &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/fuzzing.md"&gt;fuzz-testing&lt;/a&gt; on Bitcoin Core and a &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/benchmarking.md"&gt;benchmarking framework&lt;/a&gt; to monitor code performance.
The website &lt;a href="https://bitcoinperf.com"&gt;bitcoinperf.com&lt;/a&gt;, for example, offers both a &lt;a href="https://bitcoinperf.com/d/YiV16Vsik/overview"&gt;Grafana&lt;/a&gt; and a &lt;a href="https://codespeed.bitcoinperf.com/"&gt;codespeed&lt;/a&gt; interface visualizing periodic benchmarking results.&lt;/p&gt;
&lt;p&gt;A well-defined &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/release-process.md"&gt;release process&lt;/a&gt; has been put together over the years.
Major releases of Bitcoin Core are scheduled every six months.
The &lt;a href="https://github.com/bitcoin/bitcoin/issues/15940"&gt;schedule&lt;/a&gt; includes a &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md"&gt;translation process&lt;/a&gt;, a feature freeze and usually multiple release candidates.
Recent efforts by &lt;a href="https://github.com/theuni"&gt;Cory Fields&lt;/a&gt; and &lt;a href="https://twitter.com/carl_dong"&gt;Carl Dong&lt;/a&gt; aim to increase the &lt;a href="https://www.youtube.com/watch?v=I2iShmUTEl8"&gt;Bitcoin Core build system security&lt;/a&gt; with &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/contrib/guix/README.md"&gt;deterministic and bootstrappable&lt;/a&gt; builds.
The new build system might not be fully ready for the v0.19.0 release of Bitcoin Core this fall but could provide greater build security in the future.&lt;/p&gt;
&lt;h4 id="conclusion"&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;Over the past ten years, the Bitcoin development culture has changed a lot.
Moving from being very centralized around Satoshi to being more decentralized with more than a &lt;a href="https://twitter.com/_jonasschnelli_/status/1080713877355081729"&gt;thousand GitHub contributors in 2018&lt;/a&gt;.
It has become clear that high standards for code review, code quality, and security are needed.
These standards are followed and constantly improved.&lt;/p&gt;
&lt;p&gt;I think to fully understand the rationale behind the current state of Bitcoin development, knowledge about historical events is essential.
There is a timeline with more events attached below.
Some suggested further reading could be &lt;a href="https://medium.com/@bergealex4/the-tao-of-bitcoin-development-ff093c6155cd"&gt;The Tao Of Bitcoin Development&lt;/a&gt; written by &lt;a href="https://twitter.com/bergealex4"&gt;Alex B.&lt;/a&gt;, &lt;a href="https://medium.com/@elombrozo/the-bitcoin-core-merge-process-74687a09d81d"&gt;The Bitcoin Core Merge Process&lt;/a&gt; written by &lt;a href="https://twitter.com/eric_lombrozo"&gt;Eric Lombrozo&lt;/a&gt; and the blog post by &lt;a href="https://twitter.com/lopp"&gt;Jameson Lopp&lt;/a&gt;: &lt;a href="https://blog.lopp.net/who-controls-bitcoin-core-/"&gt;Who Controls Bitcoin Core?&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="acknowledgments"&gt;Acknowledgments&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m thankful for John Newbery helping me to shape and review this blog post.
He did a lot of the historical digging for his &lt;a href="https://www.meetup.com/BitDevsNYC/events/262321510/"&gt;History and Philosophy of Bitcoin Development&lt;/a&gt; talk, which this blog post is based upon.
I&amp;rsquo;m also very grateful for &lt;a href="https://chaincode.com"&gt;Chaincode Labs&lt;/a&gt; inviting me to their 2019 Summer Residency where I met a lot of awesome people, learned a ton and started working on this blog post and the timeline.&lt;/p&gt;
&lt;a href='https://b10c.me/projects/bitcoin-dev-history/' class="btn btn-outline-danger my-2" role="button"&gt;Timeline&lt;/a&gt;
&lt;div class="timeline my-5" id="timeline-bitcoin-history"&gt;&lt;/div&gt;
&lt;script type="text/javascript" src="https://b10c.me/data/projects/bitcoin-dev-history/timeline.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="https://b10c.me/data/projects/bitcoin-dev-history/timeline.css" /&gt;</description></item><item><title>Timeline: Historical events in the development of Bitcoin</title><link>https://b10c.me/projects/bitcoin-dev-history/</link><pubDate>Sun, 04 Aug 2019 09:09:09 +0000</pubDate><guid>https://b10c.me/projects/bitcoin-dev-history/</guid><description>&lt;p&gt;To fully understand the rationale behind the current state of Bitcoin development, knowledge about historical events is essential.
I created an open-source project containing the data for a timeline of historical developments in Bitcoin.
Most data points are adopted from a talk &lt;a href="https://twitter.com/jfnewbery"&gt;John Newbery&lt;/a&gt; gave on the &lt;a href="https://www.meetup.com/BitDevsNYC/events/262321510/"&gt;History and Philosophy of Bitcoin Development&lt;/a&gt;.
I&amp;rsquo;ve used this timeline in my blog post &lt;a href="https://b10c.me/blog/004-the-incomplete-history-of-bitcoin-development/"&gt;The Incomplete History of Bitcoin Development&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This timeline based on an open-source project called &lt;a href="https://github.com/0xB10C/bitcoin-development-history"&gt;bitcoin-development-history&lt;/a&gt;.
If you have a suggestion about something missing or want to propose a change, please open an issue there.&lt;/p&gt;
&lt;div class="timeline my-5" id="timeline-bitcoin-history"&gt;&lt;/div&gt;
&lt;script type="text/javascript" src="https://b10c.me/data/projects/bitcoin-dev-history/timeline.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="https://b10c.me/data/projects/bitcoin-dev-history/timeline.css" /&gt;</description></item><item><title>A List of Public Bitcoin Feerate Estimation APIs</title><link>https://b10c.me/blog/003-a-list-of-public-bitcoin-feerate-estimation-apis/</link><pubDate>Sat, 29 Jun 2019 14:41:14 +0000</pubDate><guid>https://b10c.me/blog/003-a-list-of-public-bitcoin-feerate-estimation-apis/</guid><description>&lt;p&gt;My search for a list of public Bitcoin feerate estimation APIs ended without any real results.
&lt;a href="https://twitter.com/lopp"&gt;Jameson Lopp&lt;/a&gt; has a section on feerate estimators on his &lt;a href="https://www.lopp.net/bitcoin-information/fee-estimates.html"&gt;bitcoin.page&lt;/a&gt; and &lt;a href="https://twitter.com/khannib"&gt;Antoine Le Calvez&amp;rsquo;s&lt;/a&gt; dashboard &lt;a href="https://txstats.com/dashboard/db/fee-estimation"&gt;txstats.com&lt;/a&gt; provides a visualization of different estimation APIs.
But that is not what I was looking for.
That&amp;rsquo;s why I compiled this list.&lt;/p&gt;
&lt;p&gt;I opted to only include publicly advertised feerate estimation APIs by e.g. payment processors and block explorers.
I&amp;rsquo;m purposefully leaving out list APIs by wallets, such as Mycelium and Trezor because their APIs are not publicly advertised.
Additionally, I&amp;rsquo;m leaving out Bitcoin Core&amp;rsquo;s feerate estimates via the &lt;a href="https://bitcoincore.org/en/doc/0.18.0/rpc/util/estimatesmartfee/"&gt;&lt;code&gt;estimatesmartfee&lt;/code&gt; RPC&lt;/a&gt;.
I don&amp;rsquo;t consider the RPC publicly reachable as in &lt;em&gt;reachable over the web by everyone&lt;/em&gt; (some &lt;a href="https://wasabiwallet.io/swagger/index.html"&gt;services&lt;/a&gt; wrap the &lt;code&gt;estimatesmartfee&lt;/code&gt; RPC however).&lt;/p&gt;
&lt;p&gt;The following list of public feerate APIs is lexicographically sorted.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="bitcoinerlive-api"&gt;bitcoiner.live API&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://bitcoiner.live/"&gt;bitcoiner.live&lt;/a&gt; API provides &lt;code&gt;sat/vByte&lt;/code&gt; estimates for confirmation in half an hour, 1 hour, 2 hours, 3 hours, 6 hours, 12 hours and 24 hours.
It&amp;rsquo;s reachable under &lt;a href="https://bitcoiner.live/api/fees/estimates/latest"&gt;&lt;code&gt;https://bitcoiner.live/api/fees/estimates/latest&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1563456789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;estimates&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;30&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sat_per_vbyte&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;60&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sat_per_vbyte&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;120&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sat_per_vbyte&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="bitgo-api"&gt;BitGo API&lt;/h3&gt;
&lt;p&gt;Bitgo&amp;rsquo;s feerate API is reachable under &lt;a href="https://www.bitgo.com/api/v2/btc/tx/fee"&gt;&lt;code&gt;https://www.bitgo.com/api/v2/btc/tx/fee&lt;/code&gt;&lt;/a&gt; and there is documentation available &lt;a href="https://bitgo.com/api/v2/#operation/v2.tx.getfeeestimate"&gt;here&lt;/a&gt;.
The API returns estimates for different block targets in &lt;strong&gt;&lt;code&gt;sat/kB&lt;/code&gt;&lt;/strong&gt;. &lt;small&gt; (&lt;em&gt;&lt;code&gt;sat/kB / 1000 = sat/Byte&lt;/code&gt;&lt;/em&gt;) &lt;/small&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;feePerKb&amp;#34;&lt;/span&gt;: 61834,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cpfpFeePerKb&amp;#34;&lt;/span&gt;: 61834,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;numBlocks&amp;#34;&lt;/span&gt;: 2,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;confidence&amp;#34;&lt;/span&gt;: 80,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;multiplier&amp;#34;&lt;/span&gt;: 1,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;feeByBlockTarget&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;: 64246,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;: 61834,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;: 56258,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; ... &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="bitpay-insight-api"&gt;Bitpay Insight API&lt;/h3&gt;
&lt;p&gt;The API of Bitpay&amp;rsquo;s Insight instance is available under &lt;a href="https://insight.bitpay.com/api/utils/estimatefee?nbBlocks=2,4,6"&gt;&lt;code&gt;https://insight.bitpay.com/api/utils/estimatefee?nbBlocks=2,4,6&lt;/code&gt;&lt;/a&gt;.
With the parameter &lt;code&gt;nbBlocks&lt;/code&gt; the confirmation target in the next &lt;em&gt;&lt;code&gt;n&lt;/code&gt;&lt;/em&gt; blocks can be specified.
Feerates are in &lt;strong&gt;&lt;code&gt;BTC/kB&lt;/code&gt;&lt;/strong&gt;. &lt;small&gt; &lt;em&gt;(&lt;code&gt;BTC/kB x 100000 = sat/Byte&lt;/code&gt;)&lt;/em&gt; &lt;/small&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00051894&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00047501&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;6&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00043338&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="blockchaininfo-api"&gt;Blockchain.info API&lt;/h3&gt;
&lt;p&gt;Blockchain.info recommends &lt;strong&gt;&lt;code&gt;sat/Byte&lt;/code&gt;&lt;/strong&gt; feerates via &lt;a href="https://api.blockchain.info/mempool/fees"&gt;&lt;code&gt;https://api.blockchain.info/mempool/fees&lt;/code&gt;&lt;/a&gt;.
They provide a &lt;code&gt;regular&lt;/code&gt; and a &lt;code&gt;priority&lt;/code&gt; feerate.
Additionally a &lt;code&gt;minimum&lt;/code&gt; and &lt;code&gt;maximum&lt;/code&gt; feerate are included.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;limits&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;min&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;max&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;79&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;regular&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;priority&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;53&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="blockchair-api"&gt;Blockchair API&lt;/h3&gt;
&lt;p&gt;The Blockchair API offers a transaction fee suggestion in &lt;strong&gt;&lt;code&gt;sat/Byte&lt;/code&gt;&lt;/strong&gt; via &lt;a href="https://api.blockchair.com/bitcoin/stats"&gt;&lt;code&gt;https://api.blockchair.com/bitcoin/stats&lt;/code&gt;&lt;/a&gt;.
While their API is publicly available for occasional requests, they require an API key for more and periodical requests.
You can read more about the API &lt;a href="https://github.com/Blockchair/Blockchair.Support/blob/master/API.md"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;suggested_transaction_fee_per_byte_sat&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="blockcypher-api"&gt;BlockCypher API&lt;/h3&gt;
&lt;p&gt;The BlockCypher API includes a &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt; and &lt;code&gt;high&lt;/code&gt; feerate estimate in &lt;a href="https://api.blockcypher.com/v1/btc/main"&gt;&lt;code&gt;https://api.blockcypher.com/v1/btc/main&lt;/code&gt;&lt;/a&gt;.
The feerates are in &lt;strong&gt;&lt;code&gt;sat/kB&lt;/code&gt;&lt;/strong&gt;. &lt;small&gt; (&lt;em&gt;&lt;code&gt;sat/kB / 1000 = sat/Byte&lt;/code&gt;&lt;/em&gt;) &lt;/small&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;high_fee_per_kb&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;41770&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;medium_fee_per_kb&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;low_fee_per_kb&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="blockstreaminfo-api"&gt;Blockstream.info API&lt;/h3&gt;
&lt;p&gt;Blockstream.info offers an API returning feerates for different confirmation targets in &lt;strong&gt;&lt;code&gt;sat/vByte&lt;/code&gt;&lt;/strong&gt; under &lt;a href="https://blockstream.info/api/fee-estimates"&gt;&lt;code&gt;https://blockstream.info/api/fee-estimates&lt;/code&gt;&lt;/a&gt;.
The API is documented &lt;a href="https://github.com/Blockstream/esplora/blob/master/API.md#fee-estimates"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;32.749&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;32.749&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;24.457&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;6&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;20.098&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;18.17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;20&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;144&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;504&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;1008&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="btccom-api"&gt;BTC.com API&lt;/h3&gt;
&lt;p&gt;BTC.com offers a feerate estimate for the next block in &lt;strong&gt;&lt;code&gt;sat/Byte&lt;/code&gt;&lt;/strong&gt; under &lt;a href="https://btc.com/service/fees/distribution"&gt;&lt;code&gt;https://btc.com/service/fees/distribution&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tx_size&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tx_size_count&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tx_size_divide_max_size&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tx_duration_time_rate&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fees_recommended&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;one_block_fee&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;update_time&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1563456789&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="earncom-api"&gt;earn.com API&lt;/h3&gt;
&lt;p&gt;The API behind &lt;a href="https://bitcoinfees.earn.com"&gt;bitcoinfees.earn.com&lt;/a&gt; is reachable under &lt;a href="https://bitcoinfees.earn.com/api/v1/fees/recommended"&gt;&lt;code&gt;https://bitcoinfees.earn.com/api/v1/fees/recommended&lt;/code&gt;&lt;/a&gt;.
Feerate estimates for the &lt;code&gt;fastest&lt;/code&gt; confirmation, a confirmation in &lt;code&gt;half an hour&lt;/code&gt; and &lt;code&gt;a hour&lt;/code&gt; are shown in &lt;strong&gt;&lt;code&gt;sat/Byte&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fastestFee&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;halfHourFee&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;hourFee&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;Please let me know if you know a feerate estimation API I should add to the list.&lt;/p&gt;</description></item><item><title>How Bitcoin Core works: LoadMempool() and DumpMempool()</title><link>https://b10c.me/talks/004-chaincode-how-core-works/</link><pubDate>Fri, 21 Jun 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/004-chaincode-how-core-works/</guid><description>&lt;p&gt;I&amp;rsquo;ve talked about the &lt;code&gt;LoadMempool()&lt;/code&gt; and &lt;code&gt;DumpMempool()&lt;/code&gt; functions of &lt;a href="https://github.com/bitcoin/bitcoin/"&gt;Bitcoin Core&lt;/a&gt; at the 2019 &lt;a href="https://residency.chaincode.com/"&gt;Chaincode Labs Summer Residency&lt;/a&gt; seminar.
These functions are used to write and read the &lt;code&gt;mempool.dat&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://docs.google.com/presentation/d/1F-kILuxeK-huIOSCyJ2D7PQCr_os_y2cIJSSvDAk35U' class="btn btn-outline-danger my-2" role="button"&gt;Slides (Google Slides)&lt;/a&gt;
&lt;a href='https://b10c.me/pdfs/loadMempool_dumpMempool.pdf' class="btn btn-outline-danger my-2" role="button"&gt;Slides (PDF)&lt;/a&gt;
&lt;/p&gt;</description></item><item><title>BIP-42: A finite monetary supply for Bitcoin</title><link>https://b10c.me/talks/003-chaincode-bip-42-finite-monetary-supply/</link><pubDate>Thu, 20 Jun 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/003-chaincode-bip-42-finite-monetary-supply/</guid><description>&lt;p&gt;I&amp;rsquo;ve talked about &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0042.mediawiki"&gt;BIP-42&lt;/a&gt; at the 2019 &lt;a href="https://residency.chaincode.com/"&gt;Chaincode Labs Summer Residency&lt;/a&gt; seminar.
With BIP-42 a fix for a bug in the block subsidy calculation of Bitcoin Core is described. This BIP is meant as an april-fools joke and will only become relevant in 2262.&lt;/p&gt;
&lt;a href='https://docs.google.com/presentation/d/1nY1KVy4WFhNM9AE92vwo4O1C6GUId7jJBJb1bp9dDUc' class="btn btn-outline-danger my-2" role="button"&gt;Slides (Google Slides)&lt;/a&gt;
&lt;a href='https://b10c.me/pdfs/bip42.pdf' class="btn btn-outline-danger my-2" role="button"&gt;Slides (PDF)&lt;/a&gt;</description></item><item><title>BIP-125: Opt-in Full Replace-by-Fee Signaling</title><link>https://b10c.me/talks/002-chaincode-bip-125-rbf/</link><pubDate>Fri, 14 Jun 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/002-chaincode-bip-125-rbf/</guid><description>&lt;p&gt;I&amp;rsquo;ve presented about BIP-125 Replace-by-Fee at the 2019 &lt;a href="https://residency.chaincode.com/"&gt;Chaincode Labs Summer Residency&lt;/a&gt; seminar.
This talk was about the motivation, the history, other proposals and the specification of BIP-125 Replace-by-Fee.&lt;/p&gt;
&lt;a href='https://docs.google.com/presentation/d/1n8iNlxb0uz4D9YPTMiIt_x9VoffR15h1HRrbelIibBU' class="btn btn-outline-danger my-2" role="button"&gt;Slides (Google Slides)&lt;/a&gt;
&lt;a href='https://b10c.me/pdfs/bip125.pdf' class="btn btn-outline-danger my-2" role="button"&gt;Slides (PDF)&lt;/a&gt;</description></item><item><title>Building mempool.observer - mempool stats and visualizations</title><link>https://b10c.me/talks/001-bitdevs-building-mempool-observer/</link><pubDate>Fri, 14 Jun 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/talks/001-bitdevs-building-mempool-observer/</guid><description>&lt;p&gt;I&amp;rsquo;ve presented my mempool.observer &lt;a href="https://b10c.me/projects/mempool-observer-2019-version/"&gt;project&lt;/a&gt; at the &lt;a href="https://bitdevs.org/2019-06-13-socratic-seminar-93"&gt;93rd NYC BitDevs Socratic Seminar&lt;/a&gt;.
This included past challenges, current todo&amp;rsquo;s and future ideas as well as a demo of the site.&lt;/p&gt;
&lt;a href='https://b10c.me/pdfs/memo-bitdevs-nyc.pdf' class="btn btn-outline-danger my-2" role="button"&gt;Slides (PDF)&lt;/a&gt;</description></item><item><title>mempool.observer (2019 version)</title><link>https://b10c.me/projects/mempool-observer-2019-version/</link><pubDate>Sat, 01 Jun 2019 23:53:07 +0000</pubDate><guid>https://b10c.me/projects/mempool-observer-2019-version/</guid><description>&lt;p&gt;The &lt;a href="https://mempool.observer"&gt;mempool.observer&lt;/a&gt; website displays visualizations about my Bitcoin mempool.
For example, a visualization of my current mempool and the historical mempool of my node is shown.
The idea is to provide information about the current mempool state to a Bitcoin user with a seemingly stuck and longtime-unconfirmed transaction.
Additionally, the site can be used for double-checking feerate estimates before sending a transaction.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/logo.png'
alt='mempool.observer logo'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I started working on the 2019 version in April 2019.
The 2019 version is a full rewrite of mempool.observer - only the idea and license remained.
The goal is to offer way more than the 2017 version did, but built on a foundation with performance and maintainability in mind as this was a problem in the 2017 version.
Timothy Lim &lt;a href="https://twitter.com/timothyylim"&gt;@timothyylim&lt;/a&gt; helped out by contributing to the frontend.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/2019-screenshot-current-mempool.png'
alt='current mempool card on mempoool.observer'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Screenshot: current mempool (on a sunday morning)&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;
&lt;a href='https://mempool.observer/' class="btn btn-outline-danger my-2" role="button"&gt;mempool.observer&lt;/a&gt;
&lt;a href='https://github.com/0xB10C/memo' class="btn btn-outline-danger my-2" role="button"&gt;Source Code on GitHub&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;In October 2020 I released the &lt;a href="https://mempool.observer/monitor"&gt;Bitcoin Transaction Monitor&lt;/a&gt; as a sub-project of mempool.observer.
This project is tracked on its &lt;a href="https://b10c.me/projects/bitcoin-transaction-monitor"&gt;own page&lt;/a&gt;.&lt;/p&gt;
&lt;!--
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;
--&gt;
&lt;p&gt;&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/2019-screenshot-historical-mempool.png'
alt='historical mempool card on mempoool.observer'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Screenshot: The mempool over the last two hours&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/2019-screenshot-recent-blocks.png'
alt='recent blocks card on mempoool.observer'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Screenshot: recent blocks and the time between them&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description></item><item><title>mempool-dat</title><link>https://b10c.me/projects/library-mempool-dat/</link><pubDate>Thu, 16 May 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/library-mempool-dat/</guid><description>&lt;p&gt;A Golang package that can deserialize Bitcoin Core&amp;rsquo;s &lt;code&gt;mempool.dat&lt;/code&gt; files. This is a toy project. I developed this to learn more about Golang and the &lt;code&gt;mempool.dat&lt;/code&gt; file format by Bitcoin Core.&lt;/p&gt;
&lt;p&gt;This Go package parses Bitcoin Core&amp;rsquo;s &lt;code&gt;mempool.dat&lt;/code&gt; files. These are automatically written since Bitcoin Core v0.14.0 on shutdown and can be written manually by calling the RPC &lt;code&gt;savemempool&lt;/code&gt; since Bitcoin Core v0.16.0.&lt;/p&gt;
&lt;p&gt;The package offers access to the mempool.dat&amp;rsquo;s&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;- header
- version
- number of transactions
- mempool entries
- raw transaction parsed as (https://godoc.org/github.com/btcsuite/btcd/wire#MsgTx)
- first seen timestamp
- the feeDelta
- and the not-parsed mapDeltas as byte slices
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
&lt;a href='https://github.com/0xB10C/mempool-dat' class="btn btn-outline-danger my-2" role="button"&gt;Source on GitHub&lt;/a&gt;
&lt;a href='https://godoc.org/github.com/0xB10C/mempool-dat/lib' class="btn btn-outline-danger my-2" role="button"&gt;Documentation on GoDoc&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve talked about the &lt;code&gt;LoadMempool()&lt;/code&gt; and &lt;code&gt;DumpMempool()&lt;/code&gt; functions of &lt;a href="https://github.com/bitcoin/bitcoin/"&gt;Bitcoin Core&lt;/a&gt; at the 2019 &lt;a href="https://residency.chaincode.com/"&gt;Chaincode Labs Summer Residency&lt;/a&gt; seminar.
These functions are used to write and read the &lt;code&gt;mempool.dat&lt;/code&gt; file.&lt;/p&gt;
&lt;a href='https://docs.google.com/presentation/d/1F-kILuxeK-huIOSCyJ2D7PQCr_os_y2cIJSSvDAk35U' class="btn btn-outline-danger my-2" role="button"&gt;Slides (Google Slides)&lt;/a&gt;</description></item><item><title>c-lightning plugin: csvexportpays</title><link>https://b10c.me/projects/c-lightning-plugin-csvexportpays/</link><pubDate>Sat, 02 Mar 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/c-lightning-plugin-csvexportpays/</guid><description>&lt;p&gt;A toy plugin for c-lightning to export all payments made with a c-lightning node to a &lt;code&gt;.csv&lt;/code&gt; file.
I build this a few days after Blockstream released the plugin support in c-lightning v0.7 to showcase how simple it is to build plugins.&lt;/p&gt;
&lt;a href='https://github.com/0xB10C/c-lightning-plugin-csvexportpays' class="btn btn-outline-danger my-2" role="button"&gt;Source on GitHub&lt;/a&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://raw.githubusercontent.com/0xB10C/c-lightning-plugin-csvexportpays/6461045b3dc1fe371b19045e4647eeb6c9e0ebaf/screenshot.png'
alt='Screenshot of the plugin in action'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;screenshot of the plugin in action&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;I&amp;#39;ve build a simple python plugin to show how easy it is to build a plugin for &lt;a href="https://twitter.com/Blockstream?ref_src=twsrc%5Etfw"&gt;@Blockstream&lt;/a&gt;&amp;#39;s v0.7 release of c-lightning.&lt;br&gt;&lt;br&gt;Export a your c-lightning payments into a CSV file directly from lightning-cli. ⚡&lt;br&gt;&lt;br&gt;Clone it at &lt;a href="https://t.co/h8XPm86APk"&gt;https://t.co/h8XPm86APk&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/clightning?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#clightning&lt;/a&gt; &lt;a href="https://t.co/QAaaZlXwxF"&gt;pic.twitter.com/QAaaZlXwxF&lt;/a&gt;&lt;/p&gt;&amp;mdash; b10c (@0xB10C) &lt;a href="https://twitter.com/0xB10C/status/1101867295712952320?ref_src=twsrc%5Etfw"&gt;March 2, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item><item><title>Support from Bitrefill for transactionfee.info</title><link>https://b10c.me/funding/2019-bitrefill-support/</link><pubDate>Tue, 01 Jan 2019 00:00:00 +0000</pubDate><guid>https://b10c.me/funding/2019-bitrefill-support/</guid><description>&lt;p&gt;&lt;a href="https://www.bitrefill.com/invite/0xb10c"&gt;Bitrefill&lt;/a&gt; supported my work on &lt;a href="https://b10c.me/projects/transactionfee-info-2020-version/"&gt;transactionfee.info&lt;/a&gt; (now mainnet.observer).&lt;/p&gt;</description></item><item><title>lnplays.com</title><link>https://b10c.me/projects/lnplays-com/</link><pubDate>Sat, 23 Jun 2018 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/lnplays-com/</guid><description>&lt;p&gt;Konstantin Nick (&lt;a href="https://twitter.com/sputn1ck"&gt;@sputn1ck&lt;/a&gt;) and I build lnplays.com for the Lighting Hackday in June 2018.
You could play Pokémon via the Lightning Network build.
Pressing the buttons of the GameBoy to move the player and to interact with the world would generate a lightning invoice.
Paying that invoice would send the pressed button to the game-backend and the user could see the action over the live stream.
The site not longer up, but parts of it can still be seen on &lt;a href="https://web.archive.org/web/20180625193114/https://lnplays.com/"&gt;archive.org&lt;/a&gt;.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/lnplays-com/header.png'
alt='lnplays.com screenshot'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;screenshot of the user interface of lnplays.com&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Thanks for the amazing &lt;a href="https://twitter.com/hashtag/LightningHackday?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#LightningHackday&lt;/a&gt; &lt;a href="https://twitter.com/fulmolightning?ref_src=twsrc%5Etfw"&gt;@fulmolightning&lt;/a&gt;. Now all of you should have time playing Pokemon on &lt;a href="https://t.co/IudNbyY3i9"&gt;https://t.co/IudNbyY3i9&lt;/a&gt; made by &lt;a href="https://twitter.com/0xB10C?ref_src=twsrc%5Etfw"&gt;@0xB10C&lt;/a&gt; and me&lt;/p&gt;&amp;mdash; kon (@sputn1ck) &lt;a href="https://twitter.com/sputn1ck/status/1011326983949647874?ref_src=twsrc%5Etfw"&gt;June 25, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item><item><title>Plotting the Bitcoin Feerate Distribution</title><link>https://b10c.me/blog/002-plotting-the-bitcoin-feerate-distribution/</link><pubDate>Sun, 18 Mar 2018 12:30:00 +0000</pubDate><guid>https://b10c.me/blog/002-plotting-the-bitcoin-feerate-distribution/</guid><description>&lt;p&gt;How did the median Bitcoin feerate evolve &lt;a href="https://transactionfee.info/charts/feerate/median?avg=7&amp;amp;step=false&amp;amp;start=2013-01-01&amp;amp;end=2015-01-01"&gt;from 2013 to 2015?&lt;/a&gt;
When were the &lt;a href="https://transactionfee.info/charts/feerate/percentiles?avg=7&amp;amp;step=false&amp;amp;start=2017-01-01&amp;amp;end=2018-01-01"&gt;feerate spikes in 2017?&lt;/a&gt;
Visualizing the Bitcoin feerate distribution per block was on my todo list since I&amp;rsquo;ve started working on the first version of my &lt;a href="https://b10c.me/projects/mempool-observer-2017-version/"&gt;mempool.observer project&lt;/a&gt; in mid-2017.
But acquiring the data wasn&amp;rsquo;t as easy as I first thought.&lt;/p&gt;
&lt;div class="alert alert-info" role="alert"&gt;
&lt;p class="my-0"&gt;Update January 2020: The feerate chart have are not included the 2020 version of transactionfee.info so some links in this post might be broken.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In this post, I describe my unsuccessful approaches and explain my final solution.
I&amp;rsquo;ll go into beginner to intermediate technical stuff on Bitcoin.
I hope this post might help people who are just starting out on small Bitcoin projects (i.e. me a few months ago).&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/002-plotting-feerate-distribution/header.png'
alt='feerate distribution'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;feerate distribution over time&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="motivation"&gt;Motivation&lt;/h4&gt;
&lt;p&gt;Our site &lt;a href="https://transactionfee.info"&gt;transactionfee.info&lt;/a&gt; lets a user check the fee efficiency of a bitcoin transaction they&amp;rsquo;ve send or received.
Besides SegWit and output batching we wanted to provide an indicator when a service drastically overpays the feerate.
We needed data on the feerate distribution per block to determine what a low, but reasonable feerate would have been to be included in the same block.
David Harding &lt;a href="https://twitter.com/hrdng/status/955489792141287429"&gt;suggested&lt;/a&gt; plotting the feerate across ten-percentile intervals to see which percentile is suitable.&lt;/p&gt;
&lt;h4 id="querying-feerate-data-over-the-rpc-interface"&gt;Querying feerate data over the RPC interface&lt;/h4&gt;
&lt;p&gt;My first idea was to query each block over the Bitcoin Core RPC interface.
The query &lt;a href="https://bitcoin-rpc.github.io/getblock.html"&gt;getblock &lt;em&gt;blockhash&lt;/em&gt; 2&lt;/a&gt; returns the corresponding block with all its transactions as JSON.
For the feerate distribution per block, I&amp;rsquo;d need to iterate over each transaction and calculate its feerate.
The feerate is specified as the transaction fee divided by the transaction vsize $$(2)$$.
The fee is the sum of all input amounts deducted by the sum of all output amounts $$(1)$$.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fee = sum(input amounts) - sum(output amounts)&lt;/code&gt;&lt;br&gt;
&lt;code&gt;feerate = fee / vsize&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;However, querying each block with its transactions is not enough.
An input only references a previous transaction output (TXO).
It doesn&amp;rsquo;t contain the actual input amount.
This can be solved by simply querying the TXO.
RPC-call batching brings huge performance improvements here.&lt;/p&gt;
&lt;p&gt;I coded a small python script and used a local testnet node to generate a first data set.
Everything worked fine and I quickly had a basic chart setup to share my initial proof of concept.
However, I didn&amp;rsquo;t think about the &lt;code&gt;testnet&lt;/code&gt; versus &lt;code&gt;mainnet&lt;/code&gt; performance of that script.
Since blocks on mainnet contain more transactions and more inputs per transaction the script ran ages querying data from my HDD.
I eventually stopped it and started looking for alternative approaches.&lt;/p&gt;
&lt;h4 id="alternatives"&gt;Alternatives&lt;/h4&gt;
&lt;h5 id="reading-the-raw-dat-files"&gt;Reading the raw .dat files&lt;/h5&gt;
&lt;p&gt;I looked at multiple blk*.dat-file parsers to build a TXO-index for quicker lookups.
This turned out to be a dead end.
I wanted to automatically update the data every few hours on a small VPS, but the output of the index would probably have been more than a hundred gigabytes.
This was not an option for me.&lt;/p&gt;
&lt;h5 id="googles-bigquery-bitcoin-blockchain-data-set"&gt;Google&amp;rsquo;s BigQuery Bitcoin Blockchain data set&lt;/h5&gt;
&lt;p&gt;When I stumbled over &lt;a href="https://cloud.google.com/blog/big-data/2018/02/bitcoin-in-bigquery-blockchain-analytics-on-public-data"&gt;Google&amp;rsquo;s BigQuery Bitcoin Blockchain data set&lt;/a&gt; I thought this would solve all my data problems, but was quickly disappointed with the limited data that&amp;rsquo;s available.
I miss the reference to the TXO in the input table.
The data set might be well suited for other projects and one plus point definitely is the ten-minute update interval, but I, at least for this project, have no use for it.&lt;/p&gt;
&lt;h5 id="blocksci"&gt;BlockSci&lt;/h5&gt;
&lt;p&gt;Then I found out about &lt;a href="https://github.com/citp/BlockSci"&gt;BlockSci&lt;/a&gt;: &amp;ldquo;A high-performance tool for blockchain science and exploration&amp;rdquo;.
It seems to do exactly what I wanted to do. At the time I looked into it, it didn&amp;rsquo;t use the &lt;code&gt;vsize&lt;/code&gt; for feerate calculation.
This would have resulted in an incorrect feerate for SegWit transactions.
However, a few days ago v0.4.0 was released which &lt;a href="https://github.com/citp/BlockSci/issues/43"&gt;uses the &lt;code&gt;vsize&lt;/code&gt; for feerate calculations&lt;/a&gt;.
If you are interested in working with data from the bitcoin blockchain I can highly recommend taking a look at their &lt;a href="https://arxiv.org/pdf/1709.02489.pdf"&gt;paper&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="patching-bitcoin-core-and--reindexing"&gt;Patching Bitcoin Core and -reindexing&lt;/h4&gt;
&lt;p&gt;As a final solution, I included a simple LogPrintf in validation.cpp of Bitcoin Core.
I print an identifier to grep for followed by the block height and the feerate for each non-coinbase transaction into the debug.log separated by a comma.
The &lt;a href="https://github.com/0xB10C/bitcoin/commit/6b2ef276ceeec7f1cf64f84b5d1cd9d5be9e6d10"&gt;patch diff&lt;/a&gt; is only a few lines.
After compiling and a full &lt;code&gt;-reindex&lt;/code&gt; of the blockchain my debug.log contained all blocks with the feerate of each transaction.
Grepping the blocks from the debug.log leaves me with about a gigabyte of raw data (around 100MB gziped).&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/002-plotting-feerate-distribution/feerate.png'
alt='Block 510851 and its feerates'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;log-file with the feerates for the transactions in block 510851&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="using-the-dataset"&gt;Using the dataset&lt;/h4&gt;
&lt;p&gt;I have a pruned node running on a small VPS and insert the percentile data into a database. I then automatically generate the .csv-files which are displayed on &lt;a href="https://transactionfee.info/charts?chart=feerateDetailed&amp;amp;rollavg=7"&gt;transactionfee.info/charts&lt;/a&gt;. In my next step, I&amp;rsquo;ll connect the database to an indicator on &lt;a href="https://transactionfee.info/"&gt;transactionfee.info&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I guess I&amp;rsquo;ll be using the raw data to experiment a bit with Gnuplot in the future. I have a neat idea for that. And retrospectively rating feerate estimation algorithms might be an idea, too. Feel free to &lt;a href="https://b10c.me/about/"&gt;contact me&lt;/a&gt; if you are interested in the data set.&lt;/p&gt;</description></item><item><title>transactionfee.info (2018 version)</title><link>https://b10c.me/projects/transactionfee-info-2018-version/</link><pubDate>Mon, 22 Jan 2018 00:00:00 +0000</pubDate><guid>https://b10c.me/projects/transactionfee-info-2018-version/</guid><description>&lt;p&gt;We build transactionfee.info in 2018 to raise awareness about the inefficient use of block space by exchanges, services, and wallets.
The project is a joint effort with &lt;a href="https://www.bitrefill.com/?utm_source=b10c_me"&gt;Bitrefill&lt;/a&gt; CEO &lt;a href="https://twitter.com/ziggamon"&gt;@ziggamon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In early 2018 the price of bitcoin surged to 20.000 USD.
At that time we saw more than &lt;a href="https://transactionfee.info/charts/transactions-per-day/?start=2017-08-02&amp;amp;end=2018-04-15"&gt;400.000 daily transactions&lt;/a&gt; on the Bitcoin network which caused a steep increase in transaction fees.
A lot of exchanges, services and wallets did not use the block space efficiently.
We build &lt;a href="https://transactionfee.info"&gt;transactionfee.info&lt;/a&gt; to raise community awareness.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/transactionfee-info/old-txid-input.png'
alt='Header and input field for the old transactionfee.info site'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;input field of the 2018 version transactionfee.info site&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Only a few exchanges and services made use of payment batching or spend SegWit outputs in 2018.
This meant, for example, that every withdrawal was costly for exchanges as the withdrawal transaction takes up a lot of space in the blockchain.
Often the transaction fees or even a higher fixed fee was deducted from the user&amp;rsquo;s withdrawal amount.
Our site allowed users to check the fee efficiency of their transactions and their wallets.
It calculated how much fees could have been saved by using, for example, payment batching or by spending SegWit outputs.
Green checkmarks for fee- and space-saving behavior and red crosses for inefficient and wasteful behavior.&lt;/p&gt;
&lt;a href='https://web.archive.org/web/20180826025416/https://transactionfee.info/' class="btn btn-outline-danger my-2" role="button"&gt;WayBackMachine: 26th August 2018&lt;/a&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Check out &lt;a href="https://t.co/H6LeHNaZax"&gt;https://t.co/H6LeHNaZax&lt;/a&gt;.&lt;br&gt;&lt;br&gt;It’s a new tool I’ve helped create (code by &lt;a href="https://twitter.com/0xB10C?ref_src=twsrc%5Etfw"&gt;@0xB10C&lt;/a&gt;). Helps you see how well optimized bitcoin transactions are. &lt;br&gt;&lt;br&gt;Just paste a tx id and see if you’re overpaying for your bitcoin transactions and withdrawals.&lt;br&gt;&lt;br&gt;Feedback welcome! &lt;a href="https://t.co/4jUMGky7SS"&gt;pic.twitter.com/4jUMGky7SS&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sergej Kotliar (@ziggamon) &lt;a href="https://twitter.com/ziggamon/status/955457158392397825?ref_src=twsrc%5Etfw"&gt;January 22, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Samourai users are paying the lowest transaction fees according to &lt;a href="https://t.co/ntB5Sz3mww"&gt;https://t.co/ntB5Sz3mww&lt;/a&gt; - Don&amp;#39;t use wallets that abuse their users. No Android device? Use &lt;a href="https://twitter.com/GreenAddress?ref_src=twsrc%5Etfw"&gt;@GreenAddress&lt;/a&gt; - If you don&amp;#39;t want to use Samourai install &lt;a href="https://twitter.com/ElectrumWallet?ref_src=twsrc%5Etfw"&gt;@ElectrumWallet&lt;/a&gt; - This isn&amp;#39;t marketing, this is common sense. &lt;a href="https://t.co/vGhSnf9shv"&gt;pic.twitter.com/vGhSnf9shv&lt;/a&gt;&lt;/p&gt;&amp;mdash; Samourai Wallet (@SamouraiWallet) &lt;a href="https://twitter.com/SamouraiWallet/status/960152335799504901?ref_src=twsrc%5Etfw"&gt;February 4, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;
&lt;p&gt;Later, we added a subsection with charts about payment and feerate metrics to the site.
We offered all data under a &lt;a href="https://creativecommons.org/share-your-work/public-domain/cc0/"&gt;CC0 &amp;ldquo;No Rights Reserved&amp;rdquo; license&lt;/a&gt;.
The transactionfee.info website has appeared in numerous online news articles since then, one, for example, being &lt;a href="https://thenextweb.com/hardfork/2018/05/28/bitcoins-median-transaction-fee-some-of-the-lowest-since-2011/"&gt;thenextweb.com: Bitcoin’s median transaction fee lowest since 2011 — nearing BCH&lt;/a&gt;.
The transactionfee.info data even &lt;a href="https://scholar.google.de/scholar?q=%22transactionfee.info%22"&gt;made its way into academia&lt;/a&gt;.&lt;/p&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Presenting “Payments” per day, a more accurate representation of real economic activity on the Bitcoin network.&lt;br&gt;&lt;br&gt;(It’s a count of all outputs that are not change outputs back to the sender)&lt;a href="https://t.co/kyuTMXz09A"&gt;https://t.co/kyuTMXz09A&lt;/a&gt;&lt;br&gt;&lt;br&gt;Props &lt;a href="https://twitter.com/0xB10C?ref_src=twsrc%5Etfw"&gt;@0xB10C&lt;/a&gt; &lt;a href="https://t.co/5Z1d5PQ0gQ"&gt;pic.twitter.com/5Z1d5PQ0gQ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sergej Kotliar (@ziggamon) &lt;a href="https://twitter.com/ziggamon/status/962691031702552577?ref_src=twsrc%5Etfw"&gt;February 11, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;The potential block space efficiency gains from transaction batching are even greater than from implementing SegWit. Thus far the services that have implemented batching are imperceptible in the grand scheme of things. Plenty of room for improvement! &lt;a href="https://t.co/uPSWiaXdUT"&gt;https://t.co/uPSWiaXdUT&lt;/a&gt; &lt;a href="https://t.co/bDj85dmXQ7"&gt;pic.twitter.com/bDj85dmXQ7&lt;/a&gt;&lt;/p&gt;&amp;mdash; Jameson Lopp (@lopp) &lt;a href="https://twitter.com/lopp/status/966730277111324674?ref_src=twsrc%5Etfw"&gt;February 22, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;New feature on &lt;a href="https://t.co/2G9sgi1yL5"&gt;https://t.co/2G9sgi1yL5&lt;/a&gt;: Bitcoin transaction fees distribution by percentile. &lt;br&gt;&lt;br&gt;You can see the fee rates rising and and falling and which fees are “high” vs “low” at any given time.&lt;a href="https://t.co/usCSbZZQIH"&gt;https://t.co/usCSbZZQIH&lt;/a&gt; &lt;a href="https://t.co/L9ntWw0oNv"&gt;pic.twitter.com/L9ntWw0oNv&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sergej Kotliar (@ziggamon) &lt;a href="https://twitter.com/ziggamon/status/980124381283266562?ref_src=twsrc%5Etfw"&gt;March 31, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;
&lt;p&gt;Due to low usage of the fee efficiency checker over 2019 and us reaching our goal to raise awareness we decided to overhaul the site and released a second iteration in 2020.&lt;/p&gt;
&lt;a href='https://b10c.me/projects/transactionfee-info-2020-version/' class="btn btn-outline-danger my-2" role="button"&gt;Read more about the 2020 version&lt;/a&gt;</description></item><item><title>The 300 MB default maxmempool Problem</title><link>https://b10c.me/blog/001-the-300mb-default-maxmempool-problem/</link><pubDate>Mon, 18 Dec 2017 16:16:16 +0000</pubDate><guid>https://b10c.me/blog/001-the-300mb-default-maxmempool-problem/</guid><description>&lt;p&gt;Unconfirmed transactions are quite a hassle for bitcoin users.
I recently came across an interesting problem which is not the usual &amp;ldquo;my transaction is stuck&amp;rdquo; problem.&lt;/p&gt;
&lt;p&gt;In the last weeks of 2017, the number of unconfirmed transactions has again reached all-time highs.
Periods with over 100k transactions in my mempool were not unusual.
Bitcoin Core limits the system memory allocated for storing unconfirmed transactions to 300 MB by default.
This serves as an anti-DoS feature.
Fully used 300 MB of RAM equal about 110 MB of actual raw transaction data.
This default value &lt;a href="https://en.bitcoin.it/wiki/Running_Bitcoin"&gt;can be changed&lt;/a&gt; with the &lt;code&gt;-maxmempool &amp;lt;n&amp;gt;&lt;/code&gt; option where &lt;code&gt;n&lt;/code&gt; is the number of megabytes allocated to store unconfirmed transactions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; $ bitcoin-cli getmempoolinfo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;size&amp;#34;&lt;/span&gt;: 123803, &lt;span class="c1"&gt;# amount of tx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;bytes&amp;#34;&lt;/span&gt;: 107840125, &lt;span class="c1"&gt;# raw tx bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;usage&amp;#34;&lt;/span&gt;: 298526272, &lt;span class="c1"&gt;# tx in ram bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;maxmempool&amp;#34;&lt;/span&gt;: 300000000, &lt;span class="c1"&gt;# maxmempool in bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;mempoolminfee&amp;#34;&lt;/span&gt;: 0.00016486 &lt;span class="c1"&gt;# min tx-fee to get accepted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once 300 MB of system memory is filled, Bitcoin Core starts dropping low-feerate transactions from the mempool for high-feerate transactions.
Additionally, a &lt;code&gt;mempoolminfee&lt;/code&gt;-threshold is set to prevent new low fee transactions from entering the mempool.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/001-300MB-default-problem/transactions-dropping-from-mempool.png'
alt='transactions dropping from the mempool'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A node operator can increase or decrease the &lt;code&gt;maxmempool&lt;/code&gt; option to better fit their system.
I assume that for example, some block-explorers have increased their nodes &lt;code&gt;maxmempool&lt;/code&gt;.
I know that Jochen Hoenicke, for example, has a node running with a &lt;a href="https://www.reddit.com/r/Bitcoin/comments/7i6rnu/why_is_no_one_talking_about_the_178000/dqx5osf/"&gt;&lt;code&gt;maxmempool&lt;/code&gt; of 2GB&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="the-problem"&gt;The Problem&lt;/h4&gt;
&lt;p&gt;Small differences in the mempool are common.
However, with an increased &lt;code&gt;maxmempool&lt;/code&gt; a nodes mempool can differ vastly from a majority of other nodes on the network.
These nodes, especially when they power a block-explorer, still many keep unconfirmed transactions the majority of the network has already dropped.
This can be irritating for users.&lt;/p&gt;
&lt;p&gt;I came across a particular form of this problem, when a friend asked me, why he can see a certain low-fee transaction on &lt;a href="https://bitaps.com"&gt;bitaps.com&lt;/a&gt;, but not on &lt;a href="https://blockchain.info"&gt;blockchain.info&lt;/a&gt;.
My local node, with a default 300 MB &lt;code&gt;maxmempool&lt;/code&gt;, responded with an error on calling the &lt;code&gt;getrawtransaction&lt;/code&gt; RPC with the txid.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; $ bitcoin-cli getrawtransaction &amp;lt;txid&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error code: -5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error message:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; No such mempool transaction.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, using the &lt;a href="https://bitaps.com"&gt;bitaps.com&lt;/a&gt; API to query the raw transaction worked fine.
I tried rebroadcasting the raw transaction with &lt;code&gt;sendrawtransaction&lt;/code&gt;.
This failed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; $ bitcoin-cli sendrawtransaction &amp;lt;rawtx&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error code: -25
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error message:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Missing inputs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Looking at the missing input I noticed that it references an unconfirmed change output from a transaction with equally low fees.
However, I again wasn&amp;rsquo;t able to find the parent transaction for the input in my local mempool or on &lt;a href="https://blockchain.info"&gt;blockchain.info&lt;/a&gt;.
Only &lt;a href="https://bitaps.com"&gt;bitaps.com&lt;/a&gt; had it.
And this transaction again referenced an unconfirmed low-fee output as an input.&lt;/p&gt;
&lt;p&gt;With the context of the &lt;code&gt;maxmempool&lt;/code&gt;-limit this started to make sense.&lt;/p&gt;
&lt;h4 id="my-take-on-what-happened"&gt;My take on what happened&lt;/h4&gt;
&lt;p&gt;My take on what happened is, that &lt;a href="https://bitaps.com"&gt;bitaps.com&lt;/a&gt; has a higher &lt;code&gt;maxmempool&lt;/code&gt;-limit than e.g. &lt;a href="https://blockchain.info"&gt;blockchain.info&lt;/a&gt; and therefore does not drop the transactions.
A user checking &lt;a href="https://bitaps.com"&gt;bitaps.com&lt;/a&gt; sees the transaction, a user checking &lt;a href="https://blockchain.info"&gt;blockchain.info&lt;/a&gt; doesn&amp;rsquo;t.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/blog/001-300MB-default-problem/chained-mempool-tx.png'
alt='chained mempool transactions'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The real issue appears when the original sender is not aware of this and has a peer with a larger-than-default mempool.
He unknowingly continues to chain transactions to an unconfirmed parent transaction that the vast majority of the network rejects.
The broadcasted transaction spends an output that references a dropped transaction and therefore is invalid.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;David Harding &lt;a href="https://twitter.com/hrdng/status/955492510998069249"&gt;pointed out&lt;/a&gt; that the sender could use &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki"&gt;BIP-125 Replace-By-Fee&lt;/a&gt; to update an unconfirmed transaction with new outputs:&lt;/p&gt;</description></item><item><title>mempool.observer (2017 version)</title><link>https://b10c.me/projects/mempool-observer-2017-version/</link><pubDate>Sat, 07 Oct 2017 23:53:07 +0000</pubDate><guid>https://b10c.me/projects/mempool-observer-2017-version/</guid><description>&lt;p&gt;The &lt;a href="https://mempool.observer"&gt;mempool.observer&lt;/a&gt; website displays statistics about my Bitcoin mempool.
This covers the 2017 version which I iterated on in 2019.&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/2017-header-icon.png'
alt='mempool.observer logo of the 2017 version'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The mempool.observer website was my first open-source Bitcoin project.
I started working on it in mid-2017 and officially released it on the 7th of October 2017.
At the end of 2017, the Bitcoin transaction fees sky-rocketed which resulted in a lot of traffic to my site.
The high transaction fees were caused by a flood of transactions as the price of bitcoin rose to $20.000.
I regularly ran into problems with long-running Python scripts due to querying and processing mempool on a low-end VPS.
Due to time constraints, I wasn&amp;rsquo;t able to improve the backend performance.
This resulted in version 2017 of mempool.observer dying down in 2018 due to not being maintained.
I started working on a &lt;a href="https://b10c.me/projects/mempool-observer-2019-version/"&gt;next version&lt;/a&gt; of the site in 2020 with performance and maintainability as key goals.&lt;/p&gt;
&lt;p&gt;
&lt;a href='https://github.com/0xB10C/memo/tree/master-v1' class="btn btn-outline-danger btn-sm my-2" role="button"&gt;Source Code on GitHub for 2017 version&lt;/a&gt;
&lt;a href='https://archive.ph/nNtsC' class="btn btn-outline-danger btn-sm my-2" role="button"&gt;2017 version on archive.ph&lt;/a&gt;
&lt;/p&gt;
&lt;figure class="m-3"&gt;
&lt;img class="img img-fluid figure-center-img rounded mx-auto m-1 d-block" src='https://b10c.me/data/projects/mempool-observer/2017-screenshot-chart.jpg'
alt='screenshot of the 2017 mempool chart'&gt;
&lt;figcaption&gt;&lt;center class="text-muted"&gt;Screenshot: Low-feerate transactions are evicted from my mempool during the high-fee event in 2017. My mempool contains more that 100k transactions.&lt;/center&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="my-5 mx-auto" style="max-width: 600px;"&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;version 1.0.0 of memo is live! 👓&lt;a href="https://t.co/9mQp1L4CzF"&gt;https://t.co/9mQp1L4CzF&lt;/a&gt;&lt;a href="https://t.co/NMBMD3gMUX"&gt;https://t.co/NMBMD3gMUX&lt;/a&gt;&lt;/p&gt;&amp;mdash; mempool.observer (@mempoolobserver) &lt;a href="https://twitter.com/mempoolobserver/status/916463652131295233?ref_src=twsrc%5Etfw"&gt;October 7, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;style&gt;
twitter-widget {
margin-left: auto!important;
margin-right: auto!important;
}
blockquote.twitter-tweet p {
text-align: start;
}
&lt;/style&gt;</description></item></channel></rss>