Skip to content

Commit cfb4272

Browse files
committed
wallet: Date a new wallet by this computer's clock
Bitcoin Core resolves an import timestamp of "now" to its tip's median time. On an offline signer that never synced, that is the genesis block, so every new wallet was born in 2009, and the watch-only copy Bails exports from it asks the online node to rescan from genesis, which a pruned node cannot do. A new wallet now takes this computer's clock, a day early like a recorded creation date, so a clock running up to a day fast cannot skip its first payments. Restores keep the recorded date. On a Core 32.1 regtest signer at genesis, the descriptors were dated 1296688602 (genesis) before and the clock less a day after; the signer still found a payment from blocks it synced later. Claude-Session: https://claude.ai/code/session_01N8jLK61uohaW6W7Y8AHnMb
1 parent 6c64411 commit cfb4272

2 files changed

Lines changed: 8 additions & 2 deletions

File tree

‎src/codex32/_bitcoin_core.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,16 @@ def initialize(
449449
continue
450450
# Checked last, after any wait above: a pruning node keeps pruning while it waits.
451451
self.check_history(timestamp)
452+
# Core's "now" is its tip's time: the genesis block's on an offline signer that never
453+
# synced, and watch-only exports carry it. A new wallet takes this computer's clock,
454+
# a day early like a record's date, so a fast clock cannot skip its first payments.
455+
start = max(0, int(time.time()) - 86400) if timestamp == "now" else timestamp
452456
records = core_descriptors(
453457
secret,
454458
account=account,
455459
testnet=self.chain != "main",
456460
private=True,
457-
timestamp=timestamp,
461+
timestamp=start,
458462
)
459463
imported = self._rpc(
460464
"importdescriptors",

‎tests/test_bitcoin_core.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ def test_encrypted_import_retries_without_a_passphrase_verifies_and_relocks(
391391
"_rpc",
392392
lambda client, *args, wallet=None, stdin=None: rpc(client, *args, wallet=wallet, stdin=stdin),
393393
)
394+
monkeypatch.setattr("codex32._bitcoin_core.time.time", lambda: 1_700_000_000.5)
394395
client = BitcoinCore("bitcoin-cli", "main", 300000)
395396
messages: list[str] = []
396397
delays: list[int] = []
@@ -411,7 +412,8 @@ def unlock(seconds: int) -> None:
411412
assert arguments == ("importdescriptors",) and wallet == "signer"
412413
assert private_stdin is not None and private_stdin.endswith("\n")
413414
records = json.loads(private_stdin)
414-
assert len(records) == 4 and all(record["timestamp"] == "now" for record in records)
415+
# A new wallet's "now" is this computer's clock less a day, not Core's tip (genesis offline).
416+
assert len(records) == 4 and all(record["timestamp"] == 1_700_000_000 - 86400 for record in records)
415417
assert all("xprv" in record["desc"] for record in records)
416418
assert all("xprv" not in " ".join((*args, selected or "")) for args, selected, _data in rpc.calls)
417419
assert sum(args[:2] == ("-named", "derivehdkey") for args, _wallet, _stdin in rpc.calls) == 4

0 commit comments

Comments
 (0)