-
Notifications
You must be signed in to change notification settings - Fork 38.7k
rpc: Return accurate results for scanblocks #26325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rpc: Return accurate results for scanblocks #26325
Conversation
|
I'm wondering whether the Also, is there a way to reproduce false positives? It would be nice to add a test but I haven't figured out how? |
ad075c6 to
ca139ab
Compare
w0xlt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK.
w0xlt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK ca139ab
a22ce27 to
f2174f7
Compare
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
Also, is there a way to reproduce false positives? It would be nice to add a test but I haven't figured out how?
Discussed the same question this with @furszy just a few days ago talking about #26322, and came to the conclusion that the easiest way to find false-positives is to only use a single block with some random outputs, but increase the needle set (e.g. a ranged descriptor with a large range like 0-10000), until the RPC call returns a block. The found result can then be used in the test. Will tinker a bit around and see if I can come up with a commit that you can include (or in a follow-up).
|
Thanks for the tips @theStack. Testdiff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py
index 0ba5f12bd..e7d26a5ef 100755
--- a/test/functional/rpc_scanblocks.py
+++ b/test/functional/rpc_scanblocks.py
@@ -102,6 +102,42 @@ class ScanblocksTest(BitcoinTestFramework):
# test invalid command
assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar")
+ # generate block with a lot of random outputs
+ print("Generate block with random transactions")
+ for i in range(500): # should be deterministic and give false positives
+ _, spk, _ = getnewdestination()
+ wallet.send_to(from_node=self.nodes[1], scriptPubKey=spk, amount=400)
+
+ blockhash = self.generate(self.nodes[1], 1)[0]
+ print(blockhash)
+
+ # scan the block with the ranged descriptor
+ height = node.getblockheader(blockhash)['height']
+ desc = f"pkh({parent_key}/*)"
+
+ # begin debug
+ blocks = node.scanblocks(
+ action="start",
+ scanobjects=[{"desc": desc, "range": [0, 200000]}],
+ start_height=height,
+ accurate=False)['relevant_blocks']
+
+ print(blocks)
+ # end debug
+
+ for v in [False, True]:
+ blocks = node.scanblocks(
+ action="start",
+ scanobjects=[{"desc": desc, "range": [0, 200000]}],
+ start_height=height,
+ accurate=v)['relevant_blocks']
+
+ if v:
+ assert_equal(len(blocks), 0)
+ else:
+ assert_equal(len(blocks), 1)
+ assert_equal(blocks[0], blockhash)
+
if __name__ == '__main__':
ScanblocksTest().main()The only part that isn't deterministic is: for i in range(500): # should be deterministic and give false positives
_, spk, _ = getnewdestination()
wallet.send_to(from_node=self.nodes[1], scriptPubKey=spk, amount=400)Maybe we could use |
|
I'm able to generate false positives consistently enough with this test by increasing Detailsdiff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py
index 0ba5f12bd..05779973e 100755
--- a/test/functional/rpc_scanblocks.py
+++ b/test/functional/rpc_scanblocks.py
@@ -102,6 +102,34 @@ class ScanblocksTest(BitcoinTestFramework):
# test invalid command
assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar")
+ # generate block with a lot of random outputs
+ print("Generate block with random transactions")
+ height = node.getblockcount() + 1
+ nblocks = 10
+ for i in range(nblocks):
+ for _ in range(550):
+ _, spk, _ = getnewdestination()
+ wallet.send_to(from_node=self.nodes[1], scriptPubKey=spk, amount=400)
+
+ print(f"Generate block {i}/{nblocks}")
+ self.generate(self.nodes[1], 1)[0]
+
+ # scan the block with the ranged descriptor
+ desc = f"pkh({parent_key}/*)"
+ print(desc)
+
+ for v in [False, True]:
+ blocks = node.scanblocks(
+ action="start",
+ scanobjects=[{"desc": desc, "range": [0, 200000]}],
+ start_height=height,
+ accurate=v)['relevant_blocks']
+
+ if v:
+ assert_equal(len(blocks), 0)
+ else:
+ assert len(blocks) > 1
+
if __name__ == '__main__':
ScanblocksTest().main() |
|
I'm not sure
With this feature enabled Bitcoin Core will be reading from disk and scanning both the block and undo files for each block, which would then be read again when fetched and then scanned by the user. I don't think it would be worth this extra effort unless the user's network latency or scanning code is very slow so that a single false positive block getting to them would be slower than having Bitcoin Core do it for every block. So IMO it should be disabled by default. |
|
I believe Also see #26316. |
I digged a bit deeper into the rabbit-hole and ended up opening #26341, adding a testing for a fixed false-positive for the genesis block and also verifying it with the ranged hashes described in BIP158. Feel free to base you PR on that (though I think the PR is already in a pretty good state and it's also fine if the test is add in a follow-up). With the newly introduced helpers, it would be possible to calculate false-positives for newly created blocks at runtime, but at least in my impression that takes too much time for a functional test -- about ~800k scriptPubKeys have to be tried out on average until one of them corresponds to a scriptPubKey already on the block. |
7c95821 to
33793d6
Compare
|
Thank you @andrewtoth for the review.
|
7801a5e to
f0b27d2
Compare
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-review ACK f0b27d2d8bd76e329ef3b90473b14fe57e079c4e
f0b27d2 to
5fb8f8d
Compare
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-ACK 5fb8f8dc49d00b9e2eb0d6a968783a61449ac683 📔
ac5c944 to
8da8bee
Compare
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 8da8bee
w0xlt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reACK 8da8bee
8da8bee to
1a0a2ed
Compare
|
Rebased to remove the LOCK on |
1a0a2ed to
c8d5ee3
Compare
w0xlt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reACK c8d5ee3
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-ACK c8d5ee3f3f60d50968c7d48f8fdfd678ea2a1fbc
c8d5ee3 to
0b2f0f3
Compare
|
Thanks @achow101 I added your suggestion. |
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-ACK 0b2f0f394248c67a36303c09f7519a9a2d5d29df
I agree that this loop order (as suggested by achow101 in #26325 (comment)) is the superior approach.
0b2f0f3 to
8a7040f
Compare
This makes use of undo data to accurately verify results from blockfilters.
8a7040f to
5ca7a7b
Compare
|
Rebased. |
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-ACK 5ca7a7b
|
ACK 5ca7a7b |
furszy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK 5ca7a7b
| if (filter.GetFilter().MatchAny(needle_set)) { | ||
| if (filter_false_positives) { | ||
| // Double check the filter matches by scanning the block | ||
| const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash()))); | |
| const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash()))); |
Implements #26322.
Adds a
filter_false_positivesmode toscanblocksto accurately verify results from blockfilters.If the option is enabled, pre-results given by blockfilters will be filtered out again by checking vouts and vins of all transactions of the relevant blocks against the given descriptors.
Master
PR (without
filter_false_positivesmode)Same as master
PR (with
filter_false_positivesmode)Also adds a test to check that the blockhash of a transaction will be included in the
relevant_blockswhether thefilter_false_positivesmode is enabled or not.