Skip to content

validation: enforce BIP94 timewarp rule on mainnet from the fork height - #4

Open
mblowes wants to merge 135 commits into
ecash-com:masterfrom
mbdrivechains:mainnet-timewarp
Open

mblowes wants to merge 135 commits into
ecash-com:masterfrom
mbdrivechains:mainnet-timewarp

Conversation

@mblowes

@mblowes mblowes commented Oct 12, 2026 •

Copy link
Copy Markdown

Enforces the existing BIP94 timewarp check on mainnet from EcashHeight. One condition in ContextualCheckBlockHeader; nothing else changes in consensus.

Correction (2026-10-12, after review): miners' templates need a fix before this can activate. The first version of this description said block templates already comply. That is true only of Core's own template. eCash's getblocktemplate refuses callers that do not claim the bip300301 rule and sends miners to bip300301_enforcer (src/rpc/mining.cpp), and the enforcer's template server does not apply the rule: it sets mintime to MTP + 1 and curtime to the wall clock (cusf-enforcer-mempool lib/server.rs, under a // TODO: calculate this correctly; the commented-out alternative there, max(MTP + 1, now), does not include the timewarp floor either). Read from the code; the enforcer was not run against this PR.

  • What goes wrong without the fix: the last block of a 2016-block window may be stamped up to 2 hours ahead (MAX_FUTURE_BLOCK_TIME). The next block must then be no earlier than that time minus 600 s. Miners on enforcer templates stamp it with the wall clock, so their retarget blocks are rejected (time-timewarp-attack) for up to about 110 minutes. Anyone who wins that one block can cause this, at every retarget; so can a miner whose clock runs more than 10 minutes fast. No chain split and no lost funds, but wasted work and a possible stall.
  • The fix, in the template server: at heights divisible by 2016, mintime = max(MTP + 1, prev.time - 600), otherwise MTP + 1; and curtime = max(now, mintime). It has to ship before this rule activates (block 973,728 if merged before launch). Reported as Block template ignores the BIP94 timewarp limit at retarget heights LayerTwo-Labs/cusf-enforcer-mempool#132.

Why. Bitcoin measures each 2016-block window from its own first block. A miner with majority hash can stamp each window's first block far in the past, make every window look slow, and cut difficulty up to 4× per retarget while mining fast. On Bitcoin that needs most of the world's SHA-256 hash; on eCash a majority can be rented. Mainnet has the check off (enforce_BIP94 = false, as in Core). Before launch it costs nothing to turn on; afterwards it is a coordinated soft fork.

Why not just set enforce_BIP94 = true. The check would then run from genesis, and Bitcoin block 203,616 is stamped 1,127 s before block 203,615, so the node would reject Bitcoin's history at that block. The flag also switches pow.cpp to testnet4's first-block nBits rule, which is not needed here.

Checked on a fully validated Bitcoin Core 29.0 node (read-only RPC): block 203,616 is stamped 1,127 s before its parent; it is the only one of the 481 retarget blocks from 2,016 to 969,696 stamped more than 600 s early (the next worst is 320,544, at −491 s); all 481 are within 7,200 s. On betanet the fork block 967,680 (+116 s) and the retargets 969,696 (+562 s) and 971,712 (+16 s) would all pass.

Scope. Only mainnet sets EcashHeight, so testnet3, testnet4, signet and regtest are unchanged. A network with its own genesis and EcashHeight = 0 gets no check from this path and needs enforce_BIP94; the comment in consensus/params.h now says so (it said 0 means "from genesis"). With ASERT (#2), the check still fires at every 2016th height; it is harmless there but no longer guards the difficulty after the anchor. #2 and #4 merge without conflict.

Limit. 600 s, matching MAX_TIMEWARP and Core's template code. BIP54 (Great Consensus Cleanup) proposes 7,200 s for Bitcoin. Every Bitcoin retarget block passes 7,200 s, so a version with that limit could start at genesis instead.

Tested (2026-10-12, Release build at -j2): pow_tests and validation_tests pass; the full unit suite shows the same three failures as master (bip324_tests, net_peer_connection_tests, script_tests, all from the fork itself); mining_basic.py passes; master + #2 + #4 also passes pow_tests, feature_asert.py and mining_basic.py. This PR has no test of its own: none can reach the new branch, because only mainnet sets EcashHeight. During review we wrote a functional test that gives regtest a fork height (a regtest-only switch like #2's -testasertanchor); it passes on this PR and fails on master and on two mutants (> for >=, and the EcashHeight > 0 guard removed). It needs three test-framework fixes for this fork that every functional test needs anyway (the ecash.conf file name, the bip300301 rule in getblocktemplate requests, eCash's regtest network magic). We can open it as a follow-up PR if you want it.

The second commit changes comments only.

achow101 and others added 30 commits March 10, 2026 18:58
c5ae5f0 doc: Point release notes to 31.0 wiki draft (Ava Chow)
88e21e5 examples: Generate example bitcoin.conf for 31.0rc1 (Ava Chow)
62716a8 doc: Generate manpages for 31.0rc1 (Ava Chow)
2a5b96e build: bump to 31.0rc1 (Ava Chow)

Pull request description:

ACKs for top commit:
  hebasto:
    ACK c5ae5f0.
  willcl-ark:
    ACK c5ae5f0
  w0xlt:
    ACK c5ae5f0

Tree-SHA512: 4aa6484be421276d07daa58394569ef667defc19de50e0622f8754acc83ebbc7cfac7a3afcdeacc3a4f72b3787e603b28df154ebaba883d22312ad7d4f377de3
the test silently passes on master because SetupAddressRelay
isn't called by default for inbound connections.

Github-Pull: bitcoin#34750
Rebased-From: ecb5ce6
since we're bumping mocktime more than CHAIN_SYNC_TIMEOUT = 20 * 60,
it's possible for disconnections like this to happen in the test:

$ test/functional/p2p_addr_relay.py --randomseed=7758649581790797022
...
TestFramework (INFO): Check that we answer getaddr messages only once per connection
TestFramework.p2p (WARNING): Connection lost to 127.0.0.1:58829 due to [Errno 54] Connection reset by peer
...

Github-Pull: bitcoin#34750
Rebased-From: 7ee8c0a
we don't need to send GETADDR for initial self announcement
anymore + can construct addr_receivers using
AddrReceiver(send_getaddr=False).

however we would need to send an empty ADDR message to each
of the addr_receivers to initialise addr relay for inbound
connections. so current code is simpler and we can just
clarify the comment.

Github-Pull: bitcoin#34750
Rebased-From: 57bfa86
* Show preview and ask for confirmation before git clean unless used with "--force"
* Error out when trying to pass args such as "guix-clean --help"

Github-Pull: bitcoin#34776
Rebased-From: be6d24e
Calling shutil.make_archive(), does not preserve symlinks when using the
zip format, see python/cpython#139679.

Call `zip` using subprocess instead. This code is only run when using a
macos machine, and I think it's safe to assume that zip is available, same
as codesign, and all other tools we call in this script.

Github-Pull: bitcoin#34787
Rebased-From: ab137cb
Rather than looking for /translations, which might not exist (it doesn't
in a recent brew installed qt on macOS). i.e:
```bash
ls /opt/homebrew/opt/qtbase/share/qt
doc
libexec
metatypes
mkspecs
modules
plugins
sbom
```

Github-Pull: bitcoin#34787
Rebased-From: 66d80d5
Replace the hardcoded x86_64 binary name with $(uname --machine) so the
correct binary is downloaded when building the lint container, where at
all possible.

Github-Pull: bitcoin#34832
Rebased-From: 5518753
This should fix bitcoin#34367

I am not familiar with Windows sockets thread-safety, but creating the
event loop on the main thread, and running it in the network thread
could lead to a fast abort in Python on Windows (without any stderr):

```
77/276 - wallet_txn_clone.py failed, Duration: 1 s

stdout:
2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117

stderr:

Combine the logs and print the last 99999999 lines ...
============
Combined log for D:\a\_temp/test_runner_₿_🏃_20251210_075632/wallet_txn_clone_196:
============
 test  2025-12-10T08:04:27.500134Z TestFramework (INFO): PRNG seed is: 4018092284830106117
 test  2025-12-10T08:04:27.500433Z TestFramework (DEBUG): Setting up network thread
```

Also, I couldn't find any docs that require the loop must be created on
the thread that runs them:

* https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.new_event_loop
* https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_forever

However, the patch seems trivial to review, harmless, and easy to
revert, so it may be a good try to fix the intermittent Windows Python
crash.

Github-Pull: bitcoin#34820
Rebased-From: fa050da
This is less confusing than the verbose=0 alias.

Github-Pull: bitcoin#34852
Rebased-From: faf71d6
CMake version 4.3 deprecated the imported target `Sqlite::Sqlite3`.
Use the preferred name `Sqlite3::Sqlite3` instead and provide an
alias for older versions of CMake. Also define the same alias when
using vcpkg.

Github-Pull: bitcoin#34848
Rebased-From: 498b6eb
Fix an assertion failure in gettxoutsetinfo (issue bitcoin#34263) caused by
capturing the best block before releasing cs_main, then checking it
against a potentially newer best block in GetUTXOStats().

Remove the early pindex capture since ComputeUTXOStats() independently
fetches the current best block under lock. Use stats.hashBlock and
stats.nHeight (the actual computed values) instead of the potentially
stale pindex when building the response.

Github-Pull: bitcoin#34451
Rebased-From: 5e77072
Acquire the cursor and block index under the same cs_main lock to
eliminate a potential race where a new block could be connected
between capturing the block info and acquiring the cursor, causing
the reported stats to reference a different block than the one
being iterated.

Github-Pull: bitcoin#34451
Rebased-From: f3bf63e
Making sure that if the privacy mode is activaded during
the wallet selection, the transaction view is not shown.

Github-Pull: #gui815
Rebased-From: 0dc337f
1. Use `build_os` instead of `host_os` for native packages.
2. `XCODE_VERSION` is available only for `darwin` hosts. Therefore,
   simply disable the Xcode version check for `native_qt`.

Github-Pull: bitcoin#34878
Rebased-From: 7a9304f
Crypt-iQ and others added 28 commits June 19, 2026 18:47
This way callers will not forget to set it.

Github-Pull: bitcoin#35410
Rebased-From: bf0d257
This is no-longer a proper subtree, because of direct cherry-picks.
In WriteSettings(), verify that writing to the stream and closing it
succeeded before returning true. This prevents RenameOver() from replacing
a valid settings.json with a corrupted or zero-byte file when write limits
or a full disk are encountered.

Additionally, update the ReadSettings() parse failure message to mention
power loss, full disk, or storage error as possible causes.

Fixes bitcoin#35373

Github-Pull: bitcoin#35384
Rebased-From: 0654511
Add `CDBWrapper::GetProperty()` and expose it through `CCoinsViewDB::GetDBProperty()` so coins tests can inspect LevelDB runtime properties through the coins view.
Use it in a coins DB flush baseline that records the LevelDB layout after flushing while keeping readback coverage for the flushed coin and best block.

Co-authored-by: Andrew Toth <andrewstoth@gmail.com>

Github-Pull: bitcoin#35465
Rebased-From: b10889d
Full chainstate flushes are convenient maintenance points for long-term LevelDB cleanup because the chainstate was just written.
Randomize the trigger so nodes that flush near the same height do not compact together.

Add blocking chainstate compaction through `CCoinsViewDB::CompactFull()` and give each post-IBD full flush on the normal chainstate a 1/320 chance to start compaction.
With hourly flushes this averages roughly every two weeks and makes a six-month miss about one in a million.
This keeps the schedule stateless and leaves last-compaction height or timestamp bookkeeping out of chainstate metadata.

Co-authored-by: Andrew Toth <andrewstoth@gmail.com>

Github-Pull: bitcoin#35465
Rebased-From: aa021b2
Full chainstate compaction can take minutes on large databases.
Move `CCoinsViewDB::CompactFull()` to a named `utxocompact` one-shot background thread so validation only schedules the work.

When validation selects compaction after a full flush, the chainstate was just written and another write is less likely to be needed immediately.
The coins view destructor waits for completion, and a mutex prevents compaction from using `m_db` while `ResizeCache()` replaces it.

Co-authored-by: Andrew Toth <andrewstoth@gmail.com>

Github-Pull: bitcoin#35465
Rebased-From: 394e473
c058c29 doc: update manual pages for v31.1rc1 (fanquake)
1c9d24f build: bump version to v31.1rc1 (fanquake)
d813722 doc: update release notes for v31.1rc1 (fanquake)
ea3b318 coins: compact chainstate in background (Lőrinc)
711065a validation: randomly compact chainstate (Lőrinc)
fef6c8a coins: test chainstate flush baseline (Lőrinc)
ca00827 util: Check write failures before renaming settings.json (Shrey)
1ee11d8 lint: disable leveldb subtree check (fanquake)
ccb9912 net: un-default the OpenNetworkConnection()'s proxy_override argument (Eugene Siegel)
13df77d test: add a regression test for private broadcast v1 retries (Vasil Dimov)
6c08cb7 test: make reusable filling of a node's addrman (Vasil Dimov)
70a8687 test: make reusable starting a standalone P2P listener (Vasil Dimov)
ef20249 test: make reusable SOCKS5 server starting (Vasil Dimov)
66377c3 net: ensure no direct private broadcast connections (Vasil Dimov)
2c7986b net: use the proxy if overriden when doing v2->v1 reconnections (Vasil Dimov)
70000a5 ci: use Warp cache for Docker layers (will)
25506ed ci: Add dynamic cache switching to warp cache (will)
39f8e07 ci: use ubuntu-latest instead of ubuntu-24.04 (fanquake)
1f55b3e doc: remove reference to cirrus (fanquake)
8f13bb1 crypto: disable ASan instrumentation of SSE4 SHA256 for GCC (deadmanoz)
6caf6de ci: switch runners from cirrus to warpbuild (will)
78714f6 Disable seek compaction (Andrew Toth)
3440027 ci: switch to GitHub cache for all runners (willcl-ark)
d61687a musig: Reject empty pubkey list in GetMuSig2KeyAggCache (nervana21)
671e6c2 wallet: use outpoint when estimating input size (Lőrinc)
1010717 psbt, test: remove address type restrictions in test (rkrux)

Pull request description:

  Backports:
  * bitcoin#34953
  * bitcoin#35228
  * bitcoin#35279
  * bitcoin#35313 (only bitcoin-core/leveldb-subtree#61)
  * bitcoin#35316
  * bitcoin#35378
  * bitcoin#35348
  * bitcoin#35384
  * bitcoin#35408
  * bitcoin#35410
  * bitcoin#35430
  * bitcoin#35447
  * bitcoin#35465

ACKs for top commit:
  marcofleon:
    ACK c058c29
  sedited:
    ACK c058c29

Tree-SHA512: a04909c1ce82d6f3412655ee7b52f4c482f5a175f9ec4e1468a84a4d488f935ab0ab333a3dc4d75f68fe2a9e7bae169d624c9061308435527b29b5a014c43dc3
adc5dc5 doc: update manual pages for v31.1 (fanquake)
919d74e doc: update release notes for v31.1 (fanquake)
4219fd4 build: bump version to v31.1 (fanquake)

Pull request description:

  The [`v31.1rc1` bins](https://bitcoincore.org/bin/bitcoin-core-31.1/test.rc1/) have been up for nearly 2 weeks. There doesn't seem to be anything else that needs backporting, and no issues have been reported. Finalise `v31.1`.

ACKs for top commit:
  hebasto:
    ACK adc5dc5.
  sedited:
    ACK adc5dc5

Tree-SHA512: 4bf7e59a010a08952a60bbb318c514e411d85092086006134521d873c498d544e42cf6880d5f26f02ac1a6bcd0fc155528f2858a81ae8d1b11a3c378f3d11cf9
Repurpose OP_NOP5 (0xb4) as OP_DRIVECHAIN and give it its own TxoutType
so a 4-byte OP_DRIVECHAIN scriptPubKey is classified by Solver() rather
than falling through to NONSTANDARD.
…height

Bitcoin measures each 2016-block window from its own first block, so a
miner with majority hash can stamp each window's first block far in the
past and make every window look slow. Difficulty then falls up to 4x per
retarget while blocks come fast. On Bitcoin that takes most of the
world's SHA-256 hash; on eCash a majority can be rented.

Testnet4 closes this with BIP94: the first block of a window may not be
stamped more than MAX_TIMEWARP (600 s) before its parent. This applies
the same check on mainnet from EcashHeight.

It cannot start at genesis. Bitcoin block 203,616 is stamped 1,127 s
before block 203,615, so a node enforcing the rule from genesis would
reject Bitcoin's history there. No other Bitcoin retarget block up to
969,696 is more than 600 s early.

Block templates already honour the rule on every network
(node::GetMinimumTime), so miners using getblocktemplate are unaffected.
Networks that do not set EcashHeight (testnet3, testnet4, signet,
regtest) behave as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2Cirh9q7r4aViTi5Zwxdb
Comments only. Regtest enforces BIP94 from genesis only with -test=bip94. The check also applies at
every 2016th height once ASERT sets the difficulty. A template source must keep mintime at or above
prev - MAX_TIMEWARP at those heights, as node::GetMinimumTime does; the enforcer's template server
does not yet. EcashHeight = 0 means no fork from Bitcoin, not "from genesis": such a network gets no
timewarp check from that field and needs enforce_BIP94.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.