Complete hybrid full block SPV mode #9483
Open
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
340e363
Pass CBlockRequest blocks through SyncTransaction signal
jonasschnelli ba99197
CBlockRequest: make SyncTransaction() optional
jonasschnelli df6e5b8
Add requestblocks - request out-of-band blocks download - RPC call
jonasschnelli 3a6364d
Add CAuxiliaryBlockRequest, a class to handle auxiliary blocks downloads
jonasschnelli 32770b8
[Wallet] don't consume non-validated transactions
jonasschnelli 567055e
Add -autorequestblocks debug option and setautorequestblocks hidden R…
jonasschnelli b7ef93d
[QA] Add auxiliary block request test
jonasschnelli a86c66d
Allow CheckFinalTx() without validation using the headers chain
jonasschnelli 7ca1a87
Add FindTransaction signal: allows to inv transactions that are not i…
jonasschnelli 1687a0e
Add CChain object for headers-only chain
jonasschnelli 4cbaf6c
Track the validation state of a transaction (CMerkleTx::fValidated)
jonasschnelli 1cc9293
Add basic non-validation mode to the wallet
jonasschnelli cfe9921
Add UpdateBlockHeaderTip signal
jonasschnelli 1d3011e
Keep track of the headers chain tip to detect forks
jonasschnelli 48c4c3c
Little CAuxiliaryBlockRequest refactor
jonasschnelli 5585662
Add full working SPV mode to the wallet
jonasschnelli 5826a5d
[Qt] add basic SPV support for the transaction table
jonasschnelli e783bae
[Qt] Add status bar icon to indicate SPV mode
jonasschnelli 3756ad3
Add RPC call to enabled/disabled SPV mode
jonasschnelli cb1ff09
[Qt] Show auxiliary-block-request/SPV progress in the UI
jonasschnelli 3f7aed9
Bump default tx fee from 0.0002 to 0.0005 btc/kb
jonasschnelli 346a917
[Qt] Avoid in-between animation state of ModalOverlay
jonasschnelli 749b937
[Qt] Hide ModalOverlay by default when SPV is enabled
jonasschnelli 87fb7fb
[Qt] Show more significant warning if we fall back to the default fee
jonasschnelli 9c4055d
Don't fetch blocks during headers-sync when SPV is enabled
jonasschnelli 254c94c
[Qt] Update the transaction table based on changes of the header-chain
jonasschnelli
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,81 @@ | ||
| +#!/usr/bin/env python3 | ||
| +# Copyright (c) 2016 The Bitcoin Core developers | ||
| +# Distributed under the MIT software license, see the accompanying | ||
| +# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
| + | ||
| + | ||
| +from test_framework.test_framework import BitcoinTestFramework | ||
| +from test_framework.util import * | ||
| + | ||
| +class AuxiliaryBlockRequestTest (BitcoinTestFramework): | ||
| + def __init__(self): | ||
| + super().__init__() | ||
| + self.setup_clean_chain = True | ||
| + self.num_nodes = 2 | ||
| + | ||
| + def setup_network(self): | ||
| + self.nodes = [] | ||
| + self.nodes.append(start_node(0, self.options.tmpdir, [])) | ||
| + self.nodes.append(start_node(1, self.options.tmpdir, ["-autorequestblocks=0"])) | ||
| + connect_nodes(self.nodes[0], 1) | ||
| + | ||
| + def run_test(self): | ||
| + print("Mining blocks...") | ||
| + self.nodes[0].generate(101) | ||
| + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) | ||
| + time.sleep(5) | ||
| + ctps = self.nodes[1].getchaintips() | ||
| + headersheight = -1 | ||
| + chaintipheight = -1 | ||
| + for ct in ctps: | ||
| + if ct['status'] == "headers-only": | ||
| + headersheight = ct['height'] | ||
| + if ct['status'] == "active": | ||
| + chaintipheight = ct['height'] | ||
| + assert(headersheight == 101) | ||
| + assert(chaintipheight == 0) | ||
| + | ||
| + node0bbhash = self.nodes[0].getbestblockhash() | ||
| + # best block should not be validated, header must be available | ||
| + bh = self.nodes[1].getblockheader(node0bbhash, True) | ||
| + assert(bh['validated'] == False) | ||
| + # block must not be available | ||
| + try: | ||
| + bh = self.nodes[1].getblock(node0bbhash, True) | ||
| + raise AssertionError('Block must not be available') | ||
| + except JSONRPCException as e: | ||
| + assert(e.error['code']==-32603) | ||
| + | ||
| + # request best block (auxiliary) | ||
| + self.nodes[1].requestblocks("start", [node0bbhash]) | ||
| + timeout = 20 | ||
| + while timeout > 0: | ||
| + if self.nodes[1].requestblocks("status")['request_present'] == 0: | ||
| + break; | ||
| + time.sleep(1) | ||
| + timeout-=1 | ||
| + assert(timeout>0) | ||
| + | ||
| + # block must now be available | ||
| + block = self.nodes[1].getblock(node0bbhash, True) | ||
| + assert(block['hash'] == node0bbhash) | ||
| + assert(block['validated'] == False) | ||
| + | ||
| + # enable auto-request of blocks | ||
| + self.nodes[1].setautorequestblocks(True) | ||
| + sync_blocks(self.nodes) | ||
| + | ||
| + ctps = self.nodes[1].getchaintips() | ||
| + # same block must now be available with mode validated=true | ||
| + block = self.nodes[1].getblock(node0bbhash, True) | ||
| + assert(block['hash'] == node0bbhash) | ||
| + assert(block['validated'] == True) | ||
| + | ||
| + chaintipheight = -1 | ||
| + for ct in ctps: | ||
| + if ct['status'] == "active": | ||
| + chaintipheight = ct['height'] | ||
| + assert(chaintipheight == 101) | ||
| + | ||
| +if __name__ == '__main__': | ||
| + AuxiliaryBlockRequestTest ().main () |
| @@ -0,0 +1,122 @@ | ||
| +#!/usr/bin/env python3 | ||
| +# Copyright (c) 2016 The Bitcoin Core developers | ||
| +# Distributed under the MIT software license, see the accompanying | ||
| +# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
| + | ||
| + | ||
| +from test_framework.test_framework import BitcoinTestFramework | ||
| +from test_framework.util import * | ||
| +from pprint import pprint | ||
| + | ||
| +class SPVTest (BitcoinTestFramework): | ||
| + def __init__(self): | ||
| + super().__init__() | ||
| + self.setup_clean_chain = True | ||
| + self.num_nodes = 2 | ||
| + | ||
| + def setup_network(self): | ||
| + self.nodes = [] | ||
| + self.nodes.append(start_node(0, self.options.tmpdir, [])) | ||
| + #connect to a local machine for debugging | ||
| + # url = "http://test:test@%s:%d" % ('127.0.0.1', 18332) | ||
| + # proxy = AuthServiceProxy(url) | ||
| + # proxy.url = url # store URL on proxy for info | ||
| + # self.nodes.append(proxy) | ||
| + | ||
| + self.nodes.append(start_node(1, self.options.tmpdir, ["-autorequestblocks=0", "-spv=1"])) | ||
| + connect_nodes_bi(self.nodes, 0, 1) | ||
| + | ||
| + def header_sync(self, node, height): | ||
| + timeout = 20 | ||
| + while timeout > 0: | ||
| + for ct in node.getchaintips(): | ||
| + if ct['status'] == "headers-only": | ||
| + if ct['height'] == height: | ||
| + return; | ||
| + time.sleep(1) | ||
| + timeout-=1 | ||
| + assert(timeout>0) | ||
| + | ||
| + def active_chain_height(self, node): | ||
| + for ct in node.getchaintips(): | ||
| + if ct['status'] == "active": | ||
| + return ct['height'] | ||
| + return -1 | ||
| + | ||
| + def wait_wallet_spv_sync(self, node, hash): | ||
| + timeout = 20 | ||
| + while timeout > 0: | ||
| + if node.getwalletinfo()['spv_bestblock_hash'] == hash: | ||
| + return | ||
| + assert(timeout>0) | ||
| + | ||
| + def run_test(self): | ||
| + print("Mining blocks...") | ||
| + self.nodes[0].setmocktime(int(time.time()) - 10600) | ||
| + self.nodes[0].generate(31) | ||
| + self.nodes[0].setmocktime(0) | ||
| + self.nodes[0].generate(70) | ||
| + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 2) | ||
| + self.nodes[0].generate(1) | ||
| + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 3) | ||
| + self.nodes[0].generate(1) | ||
| + node0bbhash = self.nodes[0].getbestblockhash() | ||
| + self.header_sync(self.nodes[1], 103) | ||
| + assert(self.active_chain_height(self.nodes[1]) == 0) | ||
| + | ||
| + # spv sync must be possible due to the set -spv mode | ||
| + self.wait_wallet_spv_sync(self.nodes[1], node0bbhash) | ||
| + | ||
| + # best block should be available at this point due to spv sync | ||
| + bh = self.nodes[1].getblockheader(node0bbhash, True) | ||
| + assert(bh['validated'] == False) | ||
| + bh = self.nodes[1].getblock(node0bbhash, True) | ||
| + | ||
| + # confirmations must be -1 because we haven't validated the block | ||
| + assert(bh['confirmations'] == -1) | ||
| + | ||
| + # fork | ||
| + self.nodes[0].invalidateblock(node0bbhash) | ||
| + newblocks = self.nodes[0].generate(2) | ||
| + node0bbhash = self.nodes[0].getbestblockhash() | ||
| + | ||
| + # spy sync | ||
| + self.wait_wallet_spv_sync(self.nodes[1], node0bbhash) | ||
| + | ||
| + # make sure the transactions are still available | ||
| + lt = self.nodes[1].listtransactions() | ||
| + assert(len(lt) == 2) | ||
| + confForked = -1 | ||
| + confNonForked = -1 | ||
| + for t in lt: | ||
| + if t['amount'] == Decimal('3.00000000'): | ||
| + confForked = t['confirmations'] | ||
| + if t['amount'] == Decimal('2.00000000'): | ||
| + confNonForked = t['confirmations'] | ||
| + | ||
| + assert(confForked == 2) | ||
| + assert(confNonForked == 3) | ||
| + | ||
| + # enable auto-request of blocks (normal IBD) | ||
| + print("Restart node1...", end="", flush=True) | ||
| + stop_node(self.nodes[1], 1) | ||
| + self.nodes[1] = start_node(1, self.options.tmpdir, ["-autorequestblocks=0", "-spv=1"]) | ||
| + connect_nodes_bi(self.nodes, 0, 1) | ||
| + print("done") | ||
| + self.nodes[1].setautorequestblocks(True) | ||
| + sync_blocks(self.nodes) | ||
| + lt = self.nodes[1].listtransactions() | ||
| + confForked = -1 | ||
| + confNonForked = -1 | ||
| + for t in lt: | ||
| + assert(t['validated']) | ||
| + if t['amount'] == Decimal('3.00000000'): | ||
| + confForked = t['confirmations'] | ||
| + if t['amount'] == Decimal('2.00000000'): | ||
| + confNonForked = t['confirmations'] | ||
| + | ||
| + assert(confForked == 2) | ||
| + assert(confNonForked == 3) | ||
| + | ||
| +if __name__ == '__main__': | ||
| + SPVTest ().main () |
Oops, something went wrong.