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
bugfix, Change up submitpackage results to return results for all transactions #28848
bugfix, Change up submitpackage results to return results for all transactions #28848
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
9e0c54e
to
25689d0
Compare
8dd3531
to
e77b702
Compare
5b3d54d
to
ddd9224
Compare
ddd9224
to
d72aab3
Compare
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.
Mmm, excuse my ignorance, but shouldn't the minimum relay fee be ignored for packages? (Update: I see this is documented: https://github.com/bitcoin/bitcoin/blob/master/doc/policy/packages.md#package-fees-and-feerate, perhaps we could introduce a (test-only) startup argument to allow it? I'll bring this up in #26933)
I created two transactions while offline, wiping the mempool afterwards. To simulate a busy mempool, I set the -minrelaytxfee
to 100, giving the parent a little bit less, and the child a little more than the minimum.
src/bitcoind -signet -minrelaytxfee=0.00100
src/bitcoin-cli -signet submitpackage '["02000000000101f7307b92590b1a7d82e878258a62ffaad05d0db510e947c3c331fe7400469cac0000000000fdffffff029d0c05020000000016001453dcf718842726f5236d0c05b1a5d43e12097731a086010000000000160014acdaaab857365e45724557f835694bdbb0c81231014009e5f00ec66eb80f497b771e2eb10cff222c60dc6b1fe865e5371142b1a02af7fbef913b0bc3bd49be679240e3c87bcde58532e53f657f9805352a43da036c965d9a0200", "02000000000101806ee5976b955c424940808f784fe92f746915a4d875dce994f3f72d8c8529aa0000000000fdffffff021027000000000000160014acdaaab857365e45724557f835694bdbb0c812317da7040200000000160014ef9af76b063593e215f4b861b61944b2e27624f60247304402200ac3df7c7e29df622a487ff075b8d224843e06f0848e16b2f02c55f9210eff9c0220133366f78a0191a74efbc348d2269326b6e27e11b885dd5b77a011de8a357fa00121031526c3e43061b7c9259695e75c512f5456b2dcb66ee475ece29b36688b1c35785d9a0200"]'
{
"package_msg": "transaction failed",
"tx-results": {
"43bb567b00ba2d4424441af6702f841f0953e7aadd18adb475a84d5986186199": {
"txid": "aa29858c2df7f394e9dc75d8a41569742fe94f788f804049425c956b97e56e80",
"error": "min relay fee not met, 12838 < 13000"
},
"dff806dff6456a11d8cd2318615ae5234a4e4d60662731fc39db2857472af4c9": {
"txid": "930455a47e5339e702cab0c435172a5497ff3af6d160d16224c9e9ad08cd8592",
"error": "bad-txns-inputs-missingorspent"
}
},
"replaced-transactions": [
]
}
Also why is tx-results
not an array like the input? (also to make it easier to sanity check that I didn't submit them the wrong way around)
(I can see how, for a script that submits transaction packages, this dictionary makes it easier to get feedback for specific transactions. For manual submission and debugging it's easier to keep the same ordering, but this may not be a common use case.)
src/rpc/mempool.cpp
Outdated
@@ -822,7 +822,7 @@ static RPCHelpMan submitpackage() | |||
return RPCHelpMan{"submitpackage", | |||
"Submit a package of raw transactions (serialized, hex-encoded) to local node.\n" | |||
"The package must consist of a child with its parents, and none of the parents may depend on one another.\n" | |||
"The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n" | |||
"The package will be validated according to consensus and mempool policy rules. If a transaction passes, it will be accepted to mempool.\n" |
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.
a -> any ?
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.
Maybe add: "A transaction that's already in the mempool is [ignored, rebroadcast?]."
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.
a -> any ?
taken
Maybe add: "A transaction that's already in the mempool is [ignored, rebroadcast?]."
Do we want to promise that? I'm ambivalent for now.
@Sjors I think you can set |
That doesn't simulate a full mempool though. I wrote a commit that lets my example go through: Sjors@03baacd However, not sure if it's worth the extra complexity. |
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.
Anyway, my two main issues are about pre-existing behavior: tx-results
was already a dictionary and -minrelaytxfee
already behaved that way.
d72aab3 looks good, but I'm (obviously) not familiar enough with mempool stuff to know for sure.
test/functional/mempool_limit.py
Outdated
@@ -205,7 +206,7 @@ def test_mid_package_eviction(self): | |||
|
|||
# Package should be submitted, temporarily exceeding maxmempool, and then evicted. | |||
with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]): | |||
assert_raises_rpc_error(-26, "mempool full", node.submitpackage, package_hex) | |||
node.submitpackage(package_hex) |
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.
Do you still want to check "mempool full" like below?
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.
the check itself is a little weird, so I just double-checked that the package failed. The later checks should check invariants.
d72aab3
to
4fb3340
Compare
If they're submitted "backwards" they'll be rejected.
Right, I think this is 99%+ of the use-cases. "typical" usage of manual transaction creation should generate transactions with sufficient fees for an attempt at inclusion in a block. |
all feedback is resolved |
ACK 4fb3340 |
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
Currently we don't actually broadcast a transaction, even if it was entered into our mempool, if a subsequent transaction causes PKG_TX failure.
Let's have a test for this?
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 6507f6267b..5ae805ccf0 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -340,6 +340,20 @@ class RPCPackagesTest(BitcoinTestFramework):
assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
assert_equal(legacy_pool, node.getrawmempool())
+ # Create a transaction chain such as only the parent gets accepted (by making the child's
+ # version non-standard). Make sure the parent does get broadcast.
+ self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")
+ peer = node.add_p2p_connection(P2PTxInvStore())
+ txs = self.wallet.create_self_transfer_chain(chain_length=2)
+ hex_partial_acceptance = [txs[0]["hex"], "f" + txs[1]["hex"][1:]]
+ res = node.submitpackage(hex_partial_acceptance)
+ assert_equal(res["package_msg"], "transaction failed")
+ first_wtxid = txs[0]["tx"].getwtxid()
+ assert "error" not in res["tx-results"][first_wtxid]
+ sec_wtxid = next(wtxid for wtxid in res["tx-results"] if wtxid != first_wtxid)
+ assert_equal(res["tx-results"][sec_wtxid]["error"], "version")
+ peer.wait_for_broadcast([first_wtxid])
+
if __name__ == "__main__":
RPCPackagesTest().main()
The equivalent on master wouldn't pass:
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 5644a9f5a8..d64c30bfe0 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -337,6 +337,12 @@ class RPCPackagesTest(BitcoinTestFramework):
chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)]
assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
+ peer = node.add_p2p_connection(P2PTxInvStore())
+ txs = self.wallet.create_self_transfer_chain(chain_length=2)
+ hex_partial_acceptance = [txs[0]["hex"], "f" + txs[1]["hex"][1:]]
+ assert_raises_rpc_error(-26, "version", node.submitpackage, hex_partial_acceptance)
+ peer.wait_for_broadcast([txs[0]["tx"].getwtxid()])
+
if __name__ == "__main__":
RPCPackagesTest().main()
test/functional/rpc_packages.py
Outdated
# Chain of 3 transactions has too many generations | ||
legacy_pool = node.getrawmempool() | ||
chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=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.
Side note: should it be a chain of 3 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.
it means "at least 3", but you're right this is probably too permissive a check. Not touching for now but can if people want me to.
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.
would be nice to fix, maybe in a followup
ACK 4fb3340 I considered suggesting to backport this at first, in light of #27609. But i don't think the fix for relaying the parent transaction if the child fails validation matters much for this usecase (mining pool calling |
@darosior thanks, took the test with minor modifications to not do hex-surgery :) |
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.
much-less-fun ACK 24c345f
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.
4fb3340 interface LGTM, some small code errors
src/rpc/mempool.cpp
Outdated
@@ -959,6 +977,8 @@ static RPCHelpMan submitpackage() | |||
replaced_txids.insert(ptx->GetHash()); | |||
} | |||
} | |||
} else { | |||
NONFATAL_UNREACHABLE(); |
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/followup: This could instead be a compile-time check if the if/else blocks are converted to a switch statement on result type
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.
done
test/functional/mempool_limit.py
Outdated
assert_raises_rpc_error(-26, "mempool full", node.submitpackage, [tx_parent_just_below["hex"], tx_child_just_above["hex"]]) | ||
res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]]) | ||
for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]: | ||
res["tx-results"][wtxid]["error"] == "mempool full" |
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.
Are we trying to assert that this is true? Pretty sure this just keeps going if the expression is False
res["tx-results"][wtxid]["error"] == "mempool full" | |
assert_equal(res["tx-results"][wtxid]["error"], "mempool full") |
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.
oops, taken
// If a PCKG_TX error was returned, there must have been an invalid transaction. | ||
NONFATAL_UNREACHABLE(); | ||
// Package-wide error we want to return, but we also want to return individual responses | ||
package_msg = package_result.m_state.GetRejectReason(); |
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: can also CHECK_NONFATAL(package_result.m_tx_results.size() == txns.size());
here
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.
taken
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.
actually this is incorrect; PCKG_POLICY
is grouped here, which means it's size of txns or 0. Updated the check
test/functional/rpc_packages.py
Outdated
assert_equal(res["package_msg"], "transaction failed") | ||
first_wtxid = txs[0]["tx"].getwtxid() | ||
assert "error" not in res["tx-results"][first_wtxid] | ||
sec_wtxid = next(wtxid for wtxid in res["tx-results"] if wtxid != first_wtxid) |
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.
can't you just do sec_wtxid = bad_child.getwtxid()
?
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.
taken
src/rpc/mempool.cpp
Outdated
result_inner.pushKV("error", "unevaluated"); | ||
continue; | ||
} | ||
CHECK_NONFATAL(it != package_result.m_tx_results.end()); |
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 check is useless? It exits if untrue right before
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.
removed
test/functional/rpc_packages.py
Outdated
# Chain of 3 transactions has too many generations | ||
legacy_pool = node.getrawmempool() | ||
chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=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.
would be nice to fix, maybe in a followup
24c345f
to
2495312
Compare
took the liberty of doing now @glozow |
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 2495312
Behavior prior to this commit allows some transactions to enter into the local mempool but not be reported to the user when encountering a PackageValidationResult::PCKG_TX result. This is further compounded with the fact that any transactions submitted to the mempool during this call would also not be relayed to peers, resulting in unexpected behavior. Fix this by, if encountering a package error, reporting all wtxids, along with a new error field, and broadcasting every transaction that was found in the mempool after submission. Note that this also changes fees and vsize to optional, which should also remove an issue with other-wtxid cases.
2495312
to
f23ba24
Compare
fixed CI fuzz rpc corpus issue with relaxed check |
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.
utACK f23ba24, thanks for taking the suggestions
Light re-utACK f23ba24 |
ACK f23ba24 |
Behavior prior to this commit allows some transactions to enter into the local mempool but not be reported to the user when encountering a PackageValidationResult::PCKG_TX result. This is further compounded with the fact that any transactions submitted to the mempool during this call would also not be relayed to peers, resulting in unexpected behavior. Fix this by, if encountering a package error, reporting all wtxids, along with a new error field, and broadcasting every transaction that was found in the mempool after submission. Note that this also changes fees and vsize to optional, which should also remove an issue with other-wtxid cases. Github-Pull: bitcoin#28848 Rebased-From: b67db52
Github-Pull: bitcoin#28848 Rebased-From: e67a345
Github-Pull: bitcoin#28848 Rebased-From: f23ba24
This was prompted by errors being returned that didn't "make any sense" to me, because it would for example return a "fee too low" error, when the "real" error was the child had something invalid, which disallowed CPFP evaluation. Rather than make judgment calls on what error is important(which is currently just return the "first"!), we simply return all errors and let the callers determine what's best.
Added a top level
package_msg
for quick eye-balling of general success of the package.This PR also fixes a couple bugs:
PKG_TX
failure.