Repository navigation
Conversation
|
Interesting approach - doesn't this require the destination host to have twice the storage of the final datadir though? I think to actually land this feature, need a way for the receiving node to import the store without needing significantly more storage than the final store size. Maybe even a v2 protocol extension that lets an rbitcoin node serve structured hot data to another rbitcoin node for quick sync or something. Open to other ideas too, or to you contradicting me. |
|
(rebase to get green coverage) |
|
There are trade-offs with any approach. This approach provides atomic changes, needs ~2x disk space. The in-place approach would require decode all things that encrypted with the original I can can do one or another, or both :) |
|
Will continue thinking about what I think the most rbitcoiny thing here is. Thank you! Won't merge this feature until after 0.9 sometime in the next week. |
There was a problem hiding this comment.
REQUEST_CHANGES. 2 bugs, 2 nits.
- Connect-time consensus is not re-run, so a store's own
--hot-digeststill imports an invalid chain. Seeimport_hot/hot_import_check_block. - Opening the source can rewrite that datadir. See
import_hot_store.
Nits: a later start that still passes --assumehot* exits because store/ is not empty (the operator note already says to drop the flags). assumehot_import_then_follow extends with an empty block, so it never spends a coin that exists only in the imported store.
The extra disk copy is already the open question on the thread.
| hash: *hash, | ||
| }); | ||
| } | ||
| self.confirm_blocks_run(&prepared)?; |
There was a problem hiding this comment.
import_hot stops at confirm_blocks_run plus annotate_spends_for_confirmed. Connect's structural phase never runs, so a second spend of one output is stored as a multi-spender, and an immature coinbase, a BIP30 overwrite, a BIP68 failure, or a coinbase above subsidy plus fees is kept. hot_import_check_block calls ValidationContext::archive_structure, so BIP34 and the pre-segwit witness rule are skipped too. --hot-digest hashes the store as it sits, so that store's own digest matches. The new node then serves the tip with no background validation and no RPC warning. Run those structural checks during the import (scripts can stay off) and cover a bad store whose expected digest is its own --hot-digest.
| seal.tip_height | ||
| ))); | ||
| } | ||
| let src = Query::open_or_create_layout(src)?; |
There was a problem hiding this comment.
Query::open_or_create_layout revalidates the tip window and can shrink the source and rewrite tip_seal before the match check on the next lines. HeaderHead::open also rewrites header.head when the file has fewer slots than the open target. assumehot_import builds that target from the destination layout (..dst.clone()), so the destination --head-scale and RBITCOIN_HEAD_SLOTS_HEADER apply to the source. A refused import, or a source whose header head is smaller than that target, changes the datadir being copied. Open the source without repair and without a head rewrite.
There was a problem hiding this comment.
COMMENT. Both bugs from a8b21fb are fixed. Rebase onto current master: the coverage job failed on this head, and a rebase is what cleared it last time.
Fixed: import_hot connects through confirm_wire_run (genesis through accept_and_connect_block) under a height-only milestone, so a bad store's own digest is refused for an immature coinbase, a double spend, a coinbase above subsidy, and a missing BIP34 push. The source opens through open_import_source / HeaderHead::open_source; a tiny header.head stays at its own slots, and a tip past tip_seal is refused without being shrunk.
Nits still open: a later start that still passes --assumehot* exits because store/ is not empty. assumehot_import_then_follow still extends with an empty block, so it never spends an imported coin.
| if dst.dir.exists() { | ||
| let mut entries = std::fs::read_dir(&dst.dir).map_err(|e| StoreError::io(&dst.dir, e))?; | ||
| if entries.next().is_some() { | ||
| return Err(HotImportError::NotEmpty); |
There was a problem hiding this comment.
Nit: a second start that still passes --assumehot* hits this and exits. The imported store is already the one just written. Ignore the flags when store/ is non-empty and its digest matches, and fail only on a mismatch.
There was a problem hiding this comment.
Fixed in b559984, but not by re-checking the digest. Once the node follows a block, the store's digest changes, because it covers spenders as of the tip. Recomputing it is also a full walk on every restart.
Instead, the import writes store/assumehot (height, tip hash, digest) into store.import/ before the rename, so it lands with the store. On start, kept_import reads it before the source lock is taken:
- same digest: log "already imported; flags ignored" and open the store, also past the imported tip and with the source deleted;
- another digest: refused, naming both digests;
- no record but
store/is not empty: refused as before.
Covered by assumehot_restart_finds_its_own_import, and by the journey restarting with the flags after A's datadir is deleted.
| assert!(!b.join("store.import").exists()); | ||
|
|
||
| let last = blocks.last().unwrap(); | ||
| let missed = mine_regtest_block(last.block_hash(), last.header.time + 1, height + 1, vec![]); |
There was a problem hiding this comment.
Nit: this block has no inputs, and extend_one does not spend an imported output either. The follow only shows that an empty block connects. Spend a coin that exists only because the import recomputed it.
There was a problem hiding this comment.
Fixed in b559984. The block B missed now spends script B's coin (the fixture's 0x52 output, which B has only from the import) to 0x53. B fetches it from the miner and connects it, and the test checks that the 0x53 history holds the spend. extend_one then follows one more block as before.
Fixes #590
Start a node from another node's store instead of running IBD.
Usage
What it does
store.secret, orphans or reorg history.run_node, before the store opens, while holding the source's datadir lock.--datadir-colddestination;--datadir-cold, on an old schema, not cleanly stopped, or pruned.store.import/and renames it tostore/only if the digest matches.Trust
The digest names the chain, and you trust whoever gave it to you. There is no background validation, unlike Core's assumeutxo.
Tests
rbitcoin-queryhot_digestsuite: the digest, the import, each refusal, and tampered blocks and headers.node_cli_and_surface_smoke: CLI exit codes, wrong hash, locked source, import.end_of_ibd::assumehot_import_then_follow: node B imports node A's store with no peer, serves Electrum history, then syncs and follows over P2P.Not covered
--sh-indexoff.