-
Notifications
You must be signed in to change notification settings - Fork 38.7k
test: Add more tests for orphan tx handling #19393
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
Conversation
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.
-
Could say in the PR description what led you to see the need for this additional coverage.
-
Testing debug log output is convenient but less robust; better to test for actual change, if possible.
-
Readability nit: for variable names when the rest of the name is snakecased:
s/blockA/block_Aands/blockB/block_B/ -
It would be great for the logging to state clearly what the test is asserting, the expected outcome, and why. ISTM
infologging can be good at the beginning of a subtest as well as just before the key assertion(s). -
If a subtest does not need the context from the previous ones, it can be good to separate it to an independent method.
test/functional/p2p_invalid_tx.py
Outdated
| with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]): | ||
| node.p2p.send_txs_and_test([rejected_parent], node, success=False) | ||
|
|
||
| self.log.info('Test erase of orphan tx from peer') |
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.
| self.log.info('Test erase of orphan tx from peer') | |
| self.log.info('Test disconnecting a peer logs erasure of its orphan transactions') | |
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.
I'd rather leave log message as is, as working logging implies working erase a tx from the orphan pool.
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.
test/functional/p2p_invalid_tx.py
Outdated
| with node.assert_debug_log(['Erased 100 orphan tx from peer=24']): | ||
| self.reconnect_p2p(num_connections=1) | ||
|
|
||
| self.log.info('Test erase of orphan tx included by block') |
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.
What is being asserted in this subtest?
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.
What is being asserted in this subtest?
If a transaction from a new tip block is found in the orphan pool, it should be excluded from it.
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.
Could add that info to the log message. Same with #19393 (comment): state the action and expected result under test: "Test (action) causes (result)". Just my opinion, of course.
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.
test/functional/p2p_invalid_tx.py
Outdated
| node.p2p.send_txs_and_test([rejected_parent], node, success=False) | ||
|
|
||
| self.log.info('Test erase of orphan tx from peer') | ||
| with node.assert_debug_log(['Erased 100 orphan tx from peer=24']): |
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.
This test seems dependent on the context/test order. Is it possible to make it order-independent and to test for the change itself? (Ignore if not.)
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.
This variant, i.e., the previous test state reuse, just suggests the minimum diff.
The OP has been updated.
Agree. But we have neither RPC calls nor the public interface for the orphan transaction pool. |
10f7f75 to
a95a6d9
Compare
|
Updated 10f7f75 -> a95a6d9 (pr19393.01 -> pr19393.02, diff):
|
a95a6d9 to
3a310fb
Compare
|
Updated a95a6d9 -> 3a310fb (pr19393.02 -> pr19393.03, diff):
|
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
3a310fb to
9979e8b
Compare
9979e8b to
8e6c427
Compare
|
Rebased 3a310fb -> 8e6c427 (pr19393.03 -> pr19393.05) due to the conflict with #19804. |
|
Concept ACK
|
leonardojobim
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. It expands the test cases that cause the removal of the orphan transactions.
I also agree it would be better to test against the actual changes, but as far I know there's no way in these tests to access the orphan pool maintained in the mapOrphanTransactionsdata structure to perform this validation.
I ran successfully this test on Ubuntu 20.04.
aarmoa
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.
To validate the new verification work as expected I did the following temporary change while running the test locally:
Line 168:
- If assertion is done for 99 number of orphans instead of 100, test fails.
- If assertion is done for node 23 instead of 24, test fails.
Line 190:
- Added a node.assert_debug_log() with the message "Erased 1 orphan tx included or conflicted by block" as unexpected, to ensure it is logged only when block_A is sent. The test runs without failures.
Line 219:
- Added a node.assert_debug_log() with the message "Erased 1 orphan tx included or conflicted by block" as unexpected, to ensure it is logged only when block_B is sent. The test runs without failures.
Tested in Linux Mint 20 64 bits.
|
Concept ACK |
ShubhamPalriwala
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.
tACK 73278e3!
Suggestion: the log.info() messages across the PR can be framed in a more clear and concise way.
I completely agree with @leonardojobim here!
Concept ACK. It expands the test cases that cause the removal of the orphan transactions.
I also agree it would be better to test against the actual changes, but as far I know there's no way in these tests to access the orphan pool maintained in the
mapOrphanTransactionsdatastructure to perform this validation.I ran successfully this test on Ubuntu 20.04.
Tested on Ubuntu 21.04 works fine
| tx_withhold = CTransaction() | ||
| tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) | ||
| tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | ||
| tx_withhold.vout = [CTxOut(nValue=25 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2 |
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: Having append() here would maintain consistency across the entire file
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.
See the current code in the master branch
bitcoin/test/functional/p2p_invalid_tx.py
Line 102 in dd097c4
| tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 |
| tx_orphan_1 = CTransaction() | ||
| tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) | ||
| tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 | ||
| tx_orphan_1.vout = [CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 |
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: Having append() here would maintain consistency across the entire file
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.
See the current code in the master branch
bitcoin/test/functional/p2p_invalid_tx.py
Line 102 in dd097c4
| tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 |
| self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool') | ||
| tx_withhold_until_block_A = CTransaction() | ||
| tx_withhold_until_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 1))) | ||
| tx_withhold_until_block_A.vout = [CTxOut(nValue=12 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2 |
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: Having append() here would maintain consistency across the entire file
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.
See the current code in the master branch
bitcoin/test/functional/p2p_invalid_tx.py
Line 102 in dd097c4
| tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 |
|
|
Needs rebase. Tests don't pass for me on the first commit. |
After a rebase, that is. |
8e6c427 to
c0a5fce
Compare
Rebased 8e6c427 -> c0a5fce (pr19393.05 -> pr19393.06). |
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.
Reviewed to c0a5fce. Concept ACK. Agree due to the lack of RPC calls to inspect orphan pool, using assert_debug_log to match strings in log is a reasonable way to test.
Tested successfully by running make check and test/functional/test_runner.py.
If I understand correctly, 5c04978 tests 100 orphan transactions sent by a peer to the node are removed after the peer is disconnected. (The peer initially sent 101 orphan transactions to the node. 1 orphan tx is removed because the default max orphan pool size -maxorphantx is 100.) This makes sense.
In c0a5fce, an orphan transaction is sent first to node, then a block with a conflicting transaction is sent to node, resulting in removing the orphan transaction. This is also reasonable.
Questions and suggestions are in below.
| node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False) | ||
|
|
||
| self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool') | ||
| with node.assert_debug_log(['Erased 100 orphan tx from peer=25']): |
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.
Let me know if this kind of suggestions is not what you are looking for in terms of reviews at this stage? Feel free to ignore.
How about this to remove the magic number 25 to possibly make it a bit more readable and robust (to me)?
peer_node_id = node.getpeerinfo()[0]['id']
with node.assert_debug_log([f'Erased 100 orphan tx from peer={peer_node_id}']):
self.reconnect_p2p(num_connections=1)And the number 100 is the default max orphan pool size -maxorphantx, which may also be changed to a readable variable (if necessary) by something like this (untested). Also it is safer in case of default size change.
def set_test_params(self):
max_orphan_tx = 100
self.extra_args = [[f'-maxorphantx={max_orphan_tx}']]Then use max_orphan_tx in place of 100.
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.
For the 2 suggested changes of this comment:
- I agree with the magic number change as the peer_node_id will change on potential future structural changes of this test. Saying that, I believe that potential confusion my the number 25 is more of an issue (although minor). Another solution could be
| with node.assert_debug_log(['Erased 100 orphan tx from peer=25']): | |
| with node.assert_debug_log(['Erased 100 orphan tx from peer']): |
- If the default size change, this will test non-default behavior & it will still need a change. However, I agree that the addition of a
DEFAULT_MAX_ORPHAN_TRANSACTIONS(similar to other tests) with additional usage on L151 could increase readability.
| tip = int(node.getbestblockhash(), 16) | ||
| height = node.getblockcount() + 1 | ||
| block_A = create_block(tip, create_coinbase(height)) | ||
| block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A, tx_orphan_include_by_block_A]) |
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.
Please let me know if any of this is incorrect, thanks.
For the test, there are 3 transactions: parent -> child -> grand child, the peer only sends grand child to node, and then sends a block of (parent, child, grand child) to node, resulting in removing of grand child in the orphan pool.
If the block has only (child, grand child) instead, the test fails as expected, because grand child is still considered an orphan.
block_A.vtx.extend([tx_withhold_until_block_A, tx_orphan_include_by_block_A])But I don't understand why sending a block of (parent, child) also fails the test? As grand child now receives child and parent in the new block, it can be verified and removed from orphan pool?
block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A])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.
When the grandchild was added it was missing inputs thus added to the orphan pool, but those orphans are not reevaluated at each newly connected block.
Taking a look into the codebase, when a new orphan tx is added, there are a few indexes to the OrhanMap, one of them is from the parents' COutPoint to the tx. On a newly connected block, that index is used to check if the included transaction's parents have connection to any other txns from the orphan pool, and those are evicted.
So in you example, when child (tx_withhold_until_block_A) is evaluated, eviction looks for txns in the pool also connected to the parent (tx_withhold) and not grandchildren (tx_orphan_include_by_block_A) connected to the child.
|
tACK c0a5fce ( I carefully read and understood the functions being tested: Lines 88 to 103 in c0a5fce
Lines 56 to 86 in c0a5fce
I commented out locally nit: I would agree with pg156, magic numbers should be changed to variables for increased readability and better comprehension. nit: I found it a bit confusing the use of |
|
ACK c0a5fce with a nit per #19393 (comment). The description of the flow in #19393 (comment) might be useful for reviewers to understand the behavior tested on fa45bb2 & c0a5fce. |
c0a5fce test: Add test for erase orphan tx conflicted by block (Hennadii Stepanov) fa45bb2 test: Add test for erase orphan tx included by block (Hennadii Stepanov) 5c04978 test: Add test for erase orphan tx from peer (Hennadii Stepanov) Pull request description: This PR adds test coverage for the following cases: - erase orphan transactions when a peer is disconnected - erase an orphan transaction when it is included in a new tip block - erase an orphan transaction when it is conflicted with other transactions included in a new tip block Found useful while working on bitcoin#19374. ACKs for top commit: aureleoules: tACK c0a5fce (`make check` and `test/functional/test_runner.py`). kouloumos: ACK c0a5fce with a nit per bitcoin#19393 (comment). pg156: Reviewed to bitcoin@c0a5fce. Concept ACK. Agree due to the lack of RPC calls to inspect orphan pool, using `assert_debug_log` to match strings in log is a reasonable way to test. Tree-SHA512: 98f8deeee2d1c588c7e28a82e513d4a18655084198369db33fe2710458251eeaffed030626940072d7576f57fcbf7d856d761990129e2ca9e372d2ccbd86d07d
This PR adds test coverage for the following cases:
Found useful while working on #19374.