Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

413 changes: 413 additions & 0 deletions bin/bitcoin-rs/tests/support/policy_cases.rs

Large diffs are not rendered by default.

24 changes: 22 additions & 2 deletions crates/mempool/src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ pub(crate) enum AdmissionMode {
SingleSubmit,
PackageTest,
PackageSubmit,
PackageRpcSubmit(Option<u64>),
}

#[allow(
Expand All @@ -216,6 +217,19 @@ enum ReplacementStage {
}

impl PreparedAdmission {
pub(crate) fn check_package_max_feerate(&mut self, maximum: Option<u64>) {
if self.rejection.is_none()
&& self.fact.modified_fee.is_some_and(|fee| {
crate::standardness::exceeds_max_feerate(fee, self.fact.vsize, maximum)
})
{
self.reject(
AdmitError::Policy(crate::standardness::AcceptanceRejectReason::MaxFeeExceeded),
RejectScope::Transaction,
);
}
}

fn reject(&mut self, error: AdmitError, scope: RejectScope) {
self.replacement = ReplacementStage::Rejected;
self.fact.allowed = Some(false);
Expand Down Expand Up @@ -1128,7 +1142,11 @@ impl MempoolGateway {
_ => LimitEnforcement::Full,
};
let deferred = enforcement == LimitEnforcement::Deferred;
let floor = if deferred || mode == AdmissionMode::PackageSubmit {
let floor = if deferred
|| matches!(
mode,
AdmissionMode::PackageSubmit | AdmissionMode::PackageRpcSubmit(_)
) {
None
} else {
Some(pool.min_fee_sat_per_kvb(bitcoin_rs_primitives::unix_time_secs()))
Expand Down Expand Up @@ -1183,7 +1201,9 @@ impl MempoolGateway {
}
if matches!(
mode,
AdmissionMode::PackageTest | AdmissionMode::PackageSubmit
AdmissionMode::PackageTest
| AdmissionMode::PackageSubmit
| AdmissionMode::PackageRpcSubmit(_)
) {
// Package previews and native submissions forbid replacement;
// an already resident tx retains its duplicate result here.
Expand Down
1 change: 1 addition & 0 deletions crates/mempool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ pub use rbf::{RbfError, required_fee};
pub use rbf::{ReplacementCandidate, ReplacementPlan};
pub use reader::MempoolReader;
pub use standardness::{StandardnessError, StandardnessPolicy, is_standard_tx};
pub use submit_package::{PackageSubmission, PackageTxResult};
pub use truc::TrucError;
35 changes: 26 additions & 9 deletions crates/mempool/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub(crate) fn unfinished(tx: &Tx) -> TxAcceptanceFact {
sigop_cost: 0,
base_fee: None,
effective_fee_rate: None,
modified_fee: None,
reject_reason: None,
}
}
Expand Down Expand Up @@ -182,6 +183,14 @@ pub(crate) fn finish_package(
};
for (index, (job, request)) in prepared.iter_mut().zip(requests).enumerate() {
job.verify_policy(request);
if let crate::gateway::AdmissionMode::PackageRpcSubmit(maximum) = mode
// Core's singleton PreChecks applies its own floor before the
// client cap. Multi-transaction caps precede aggregate fee checks.
&& (txs.len() > 1
|| aggregate_error != Some(AcceptanceRejectReason::MinRelayFeeNotMet))
{
job.check_package_max_feerate(maximum);
Comment thread
metaphoric-dev marked this conversation as resolved.
}
if job.fact.allowed == Some(false) {
facts.results[index] = job.fact.clone();
return facts;
Expand All @@ -194,6 +203,12 @@ pub(crate) fn finish_package(
facts.package_error = Some(error.into());
return facts;
}
// Core runs the combined package fee check before package RBF, cluster
// limits, and ephemeral-spend checks (validation.cpp `PreChecks` order).
if let Some(error) = aggregate_error {
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
facts.package_error = Some(error);
return facts;
}
match checks
.graph
.and_then(|graph| graph.check_limits(checks.limits))
Expand All @@ -208,11 +223,17 @@ pub(crate) fn finish_package(
Err(error) => {
// Preserve the existing preview error surface. Native submission
// reports pool/graph failures without pretending to run replacement.
facts.package_error = Some(if mode == crate::gateway::AdmissionMode::PackageSubmit {
AcceptanceRejectReason::Mempool(error.into_pool_error())
} else {
AcceptanceRejectReason::Replacement(error)
});
facts.package_error = Some(
if matches!(
mode,
crate::gateway::AdmissionMode::PackageSubmit
| crate::gateway::AdmissionMode::PackageRpcSubmit(_)
) {
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
AcceptanceRejectReason::Mempool(error.into_pool_error())
} else {
AcceptanceRejectReason::Replacement(error)
},
);
return facts;
}
}
Expand All @@ -221,10 +242,6 @@ pub(crate) fn finish_package(
facts.results[index].reject_reason = Some(AcceptanceRejectReason::MissingEphemeralSpends);
return facts;
}
if let Some(error) = aggregate_error {
facts.package_error = Some(error);
return facts;
}
for (index, (job, request)) in prepared.iter_mut().zip(requests).enumerate() {
job.verify_scripts(request, script_cache);
facts.results[index] = job.fact.clone();
Expand Down
Loading
Loading