From 38789d20e6ff9c85c71991711746a25ca98d2d35 Mon Sep 17 00:00:00 2001 From: Valera <50830352+ValeraFinebits@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:16:20 +0300 Subject: [PATCH] fix: Harden Payjoin fee policy and clarify reservations --- depends/packages/curl.mk | 2 +- doc/payjoin-integration.md | 21 +- doc/payjoin-rpc.md | 9 +- src/wallet/payjoin/sender.cpp | 12 +- src/wallet/payjoin/sender.h | 2 +- src/wallet/rpc/payjoin.cpp | 4 +- src/wallet/test/payjoin_integration_tests.cpp | 231 ++++++++++++++++-- src/wallet/test/payjoin_manager_tests.cpp | 12 +- src/wallet/test/payjoin_sender_fixture.cpp | 2 +- src/wallet/test/payjoin_sender_tests.cpp | 195 +++++++-------- test/functional/wallet_payjoin.py | 15 +- 11 files changed, 363 insertions(+), 142 deletions(-) diff --git a/depends/packages/curl.mk b/depends/packages/curl.mk index f531abbd5a19..d40901037501 100644 --- a/depends/packages/curl.mk +++ b/depends/packages/curl.mk @@ -22,7 +22,7 @@ define $(package)_set_vars $(package)_config_opts+=-DUSE_NGTCP2=OFF -DUSE_QUICHE=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF $(package)_config_opts+=-DCURL_USE_GSSAPI=OFF -DCURL_USE_GSASL=OFF -DCURL_USE_LIBUV=OFF -DCURL_USE_LIBBACKTRACE=OFF $(package)_config_opts+=-DCURL_USE_MBEDTLS=OFF -DCURL_USE_WOLFSSL=OFF -DCURL_USE_GNUTLS=OFF -DCURL_USE_RUSTLS=OFF - $(package)_config_opts+=-DCURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt -DCURL_CA_PATH=none -DCURL_CA_FALLBACK=OFF + $(package)_config_opts+=-DCURL_CA_BUNDLE=none -DCURL_CA_PATH=none -DCURL_CA_FALLBACK=ON $(package)_config_opts+=-DCURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG=ON endef diff --git a/doc/payjoin-integration.md b/doc/payjoin-integration.md index d471b8242194..632b4158a967 100644 --- a/doc/payjoin-integration.md +++ b/doc/payjoin-integration.md @@ -15,6 +15,19 @@ RUSTUP_TOOLCHAIN=1.85.1 rustc --version RUSTUP_TOOLCHAIN=1.85.1 cargo --version ``` +## HTTPS trust + +HTTPS CA certificates are provided by the operating system; the archive does not +bundle them. The depends OpenSSL build uses `/etc/ssl/cert.pem` and the hashed +`/etc/ssl/certs` directory. Set `SSL_CERT_FILE` and `SSL_CERT_DIR` in the node's +environment for nonstandard locations. These variables do not guarantee exclusive +trust in one CA file: other default trust sources can remain active. Restarting +the node is recommended after changes to trust settings or certificates. + +Release-archive HTTPS checks covered Debian 12 with the default paths and Fedora +44 with `SSL_CERT_DIR=/etc/pki/ca-trust/extracted/pem/directory-hash` and no legacy +CA bundle files. These transport checks do not establish a completed Payjoin payment. + ## Production FFI build and unit tests These Core builds use Debug for testing. Keep `source_dir` and `depends_prefix` in one @@ -80,6 +93,12 @@ It checks the receiver input, publication, confirmation, reorg, idempotency, cancellation and explicit fallback. Successful CLI exit is checked before mining; `--timeout-factor` scales bounded waits. +`reservations.unreleased` means the service acknowledged registration but has not +acknowledged release; commands recheck ownership before acting. It is not a fresh +owner-record or wallet-lock check. With storage uncertainty, `false` can coexist with +a durable registration whose acknowledgement was lost, and `true` can outlive a +durable release whose acknowledgement was lost. + ## BTCPay Server regtest smoke test Prerequisites: both nodes share the same regtest chain; the Core sender wallet is @@ -94,7 +113,7 @@ Use the [RPC workflow](payjoin-rpc.md#minimal-workflow). | 2 | Call `sendpayjoin` with the URI, relay, unique `request_id` and fee cap. | A `payment_id` is returned. | | 3 | Repeat the request with identical parameters. | Same `payment_id`, `duplicate=true`. | | 4 | Poll `getpayjoin` and inspect the selected transaction. | `published`; `selected.txid` differs from `original.txid`; a receiver-owned input is present; total fee respects the cap; original is absent from the mempool. | -| 5 | Mine enough blocks for the store's confirmation policy, poll again and check BTCPay. | `confirmed`, settlement kind `selected`, `reservations.owned=false`; invoice is Settled with the invoice amount credited and a "Settled with Payjoin" event. | +| 5 | Mine enough blocks for the store's confirmation policy, poll again and check BTCPay. | `confirmed`, settlement kind `selected`, `reservations.unreleased=false`; invoice is Settled with the invoice amount credited and a "Settled with Payjoin" event. | Save the invoice ID, `payment_id`, selected txid, fee and a BTCPay status screenshot. diff --git a/doc/payjoin-rpc.md b/doc/payjoin-rpc.md index 6e20e73f8fca..d7c690269a6c 100644 --- a/doc/payjoin-rpc.md +++ b/doc/payjoin-rpc.md @@ -91,10 +91,17 @@ and `available_actions`. `published` and `rejected` record publication results; | --- | --- | | `original`, `selected` | Signed original and publication choice; fallback selects original; no PSBTs returned | | `observed_spend`, `settlement` | Known spend of original inputs; settlement denotes confirmation. A spend matching both transactions has kind `original` | -| `reservations.owned`, `reservations.locks_verified` | Ownership and current in-memory locks; no proof of database persistence | +| `reservations.unreleased` | The service acknowledged reservation registration but has not acknowledged its release; not a fresh owner or lock check | +| `reservations.locks_verified` | Current in-memory locks were verified; no proof of database persistence | | `possibly_exposed` | Receiver may hold and broadcast the signed original | | `storage_uncertain` | Uncertain storage result blocks unsafe actions | +Commands recheck ownership before acting. `reservations.unreleased` records the service's +known reservation state, not a fresh check of durable owner records or wallet locks. +With storage uncertainty, `false` does not prove absence of a durable reservation if +registration acknowledgement was lost; `true` does not prove a durable reservation +remains if release succeeded but its acknowledgement was lost. + ## Control commands and problems | Command | Effect | diff --git a/src/wallet/payjoin/sender.cpp b/src/wallet/payjoin/sender.cpp index 271824f83f54..65f486e3bea0 100644 --- a/src/wallet/payjoin/sender.cpp +++ b/src/wallet/payjoin/sender.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -316,7 +317,7 @@ struct SenderService::State : std::enable_shared_from_this { view.original_saved = payment.storage.registered; view.selection_saved = payment.storage.selection_saved; view.disclosure_saved = payment.storage.disclosure_saved; - view.owns_inputs = payment.storage.registered && !payment.storage.released; + view.has_unreleased_reservation = payment.storage.registered && !payment.storage.released; view.storage_uncertain = payment.storage.uncertain; view.locks_verified = payment.locks_verified; view.possibly_exposed = payment.exposed; @@ -1094,7 +1095,14 @@ struct SenderService::State : std::enable_shared_from_this { return; } - Publish(payment, MakeTransactionRef(std::move(finalized))); + const auto final_tx = MakeTransactionRef(std::move(finalized)); + const CAmount fee = total - *output_value; + if (fee < payment.intent.fee_rate.GetFee(GetVirtualTransactionSize(*final_tx))) { + Fail(payment, PaymentIssue::Signing, "final proposal fee rate is below payment policy"); + return; + } + + Publish(payment, final_tx); } void PublishChecked(Payment& payment, CTransactionRef tx) EXCLUSIVE_LOCKS_REQUIRED(m_wallet.cs_wallet) EXCLUSIVE_LOCKS_REQUIRED(!m_views_mutex) diff --git a/src/wallet/payjoin/sender.h b/src/wallet/payjoin/sender.h index adb227de2306..a8944f5d8d93 100644 --- a/src/wallet/payjoin/sender.h +++ b/src/wallet/payjoin/sender.h @@ -99,7 +99,7 @@ struct PaymentSnapshot { CTransactionRef selected; bool original_saved{false}; - bool owns_inputs{false}; + bool has_unreleased_reservation{false}; /** Verification of current in-memory wallet locks, not their database persistence. */ bool locks_verified{false}; diff --git a/src/wallet/rpc/payjoin.cpp b/src/wallet/rpc/payjoin.cpp index c37a0beec4a0..6fc88047b164 100644 --- a/src/wallet/rpc/payjoin.cpp +++ b/src/wallet/rpc/payjoin.cpp @@ -188,7 +188,7 @@ UniValue PaymentJSON(const PaymentView& view) if (payment.settlement) result.pushKV("settlement", Observation(*payment.settlement)); UniValue reservations{UniValue::VOBJ}; - reservations.pushKV("owned", payment.owns_inputs); + reservations.pushKV("unreleased", payment.has_unreleased_reservation); reservations.pushKV("locks_verified", payment.locks_verified); result.pushKV("reservations", std::move(reservations)); result.pushKV("possibly_exposed", payment.possibly_exposed); @@ -265,7 +265,7 @@ RPCResult PaymentResult(std::string key) "reservations", "Reservation is independent of phase or transaction presence", { - {RPCResult::Type::BOOL, "owned", "Sender still owns the reserved inputs"}, + {RPCResult::Type::BOOL, "unreleased", "The service acknowledged reservation registration but not its release; not a fresh owner or lock check. Commands recheck ownership. With storage uncertainty, false may hide a durable registration and true may outlive a durable release with a lost acknowledgement"}, {RPCResult::Type::BOOL, "locks_verified", "Current wallet in-memory locks were verified"}, }, }, diff --git a/src/wallet/test/payjoin_integration_tests.cpp b/src/wallet/test/payjoin_integration_tests.cpp index 1c8a9e988243..f69fd8edfc2f 100644 --- a/src/wallet/test/payjoin_integration_tests.cpp +++ b/src/wallet/test/payjoin_integration_tests.cpp @@ -326,7 +326,8 @@ struct IntegrationFixture : SenderFixture { } auto BuildProposal(const std::shared_ptr<::payjoin::UncheckedOriginalPayload>& unchecked, - std::function modify_psbt = {}) + std::function modify_psbt = {}, + std::optional expected_input_weight = std::nullopt) { auto owned = unchecked->check_broadcast_suitability(std::nullopt, std::make_shared(*m_node.chainman))->save(m_log); auto seen = owned->check_inputs_not_owned(std::make_shared(*m_receiver))->save(m_log); @@ -338,7 +339,9 @@ struct IntegrationFixture : SenderFixture { const auto& utxo = coin->vout[0]; ::payjoin::TxIn input{std::make_shared<::payjoin::OutPoint>(::payjoin::OutPoint{coin->GetHash().ToString(), 0}), {}, 0xfffffffe, {}}; ::payjoin::PsbtInput psbt_input{std::make_shared<::payjoin::TxOut>(::payjoin::TxOut{static_cast(utxo.nValue), {utxo.scriptPubKey.begin(), utxo.scriptPubKey.end()}}), std::nullopt, std::nullopt}; - auto fees = inputs->contribute_inputs({::payjoin::InputPair::init(input, psbt_input, std::nullopt)})->commit_inputs()->save(m_log); + std::optional> weight; + if (expected_input_weight) weight = std::make_shared<::payjoin::Weight>(::payjoin::Weight{*expected_input_weight}); + auto fees = inputs->contribute_inputs({::payjoin::InputPair::init(input, psbt_input, weight)})->commit_inputs()->save(m_log); auto provisional = fees->apply_fee_range(1, 10)->save(m_log); auto signer = std::make_shared(m_receiver_input_override ? *m_sender : *m_receiver, bool(m_receiver_input_override)); @@ -402,7 +405,7 @@ struct AsyncIntegrationFixture : IntegrationFixture { BOOST_CHECK(view.original_saved); BOOST_CHECK(view.disclosure_saved); BOOST_CHECK(view.possibly_exposed); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); LOCK(m_sender->cs_wallet); const auto stored = ReadStoredPayment(id); @@ -413,6 +416,176 @@ struct AsyncIntegrationFixture : IntegrationFixture { } }; +CAmount ProposalInputValue(const PartiallySignedTransaction& proposal) +{ + CAmount total{0}; + for (const auto& input : proposal.inputs) { + CTxOut utxo; + BOOST_REQUIRE(input.GetUTXO(utxo)); + total += utxo.nValue; + } + return total; +} + +CTransactionRef SignProposalCopy(IntegrationFixture& fixture, PartiallySignedTransaction proposal) +{ + bool complete{false}; + { + LOCK(fixture.m_receiver->cs_wallet); + BOOST_REQUIRE(!fixture.m_receiver->FillPSBT(proposal, {}, complete)); + } + { + LOCK(fixture.m_sender->cs_wallet); + BOOST_REQUIRE(!fixture.m_sender->FillPSBT(proposal, {}, complete)); + } + BOOST_REQUIRE(complete); + CMutableTransaction tx; + BOOST_REQUIRE(FinalizeAndExtractPSBT(proposal, tx)); + for (size_t i = 0; i < proposal.inputs.size(); ++i) { + CTxOut utxo; + BOOST_REQUIRE(proposal.inputs[i].GetUTXO(utxo)); + BOOST_REQUIRE(VerifyScript(tx.vin[i].scriptSig, utxo.scriptPubKey, &tx.vin[i].scriptWitness, + STANDARD_SCRIPT_VERIFY_FLAGS, + MutableTransactionSignatureChecker{&tx, static_cast(i), utxo.nValue, MissingDataBehavior::FAIL})); + } + return MakeTransactionRef(std::move(tx)); +} + +void CheckFinalProposalFeeRate(IntegrationFixture& fixture, bool sufficient) +{ + const auto receiver_script = GetScriptForDestination(WitnessV0ScriptHash{GetScriptForRawPubKey(fixture.m_receiver_key.GetPubKey())}); + CMutableTransaction funding; + funding.vin.emplace_back(COutPoint{fixture.m_receiver_coin->GetHash(), 0}); + funding.vout.emplace_back(fixture.m_receiver_coin->vout[0].nValue - 1000, receiver_script); + PartiallySignedTransaction funding_psbt{funding}; + bool complete{false}; + { + LOCK(fixture.m_receiver->cs_wallet); + CreateDescriptor(*fixture.m_receiver, "wsh(pk(" + EncodeSecret(fixture.m_receiver_key) + "))", true); + BOOST_REQUIRE(!fixture.m_receiver->FillPSBT(funding_psbt, {}, complete)); + } + BOOST_REQUIRE(complete); + BOOST_REQUIRE(FinalizeAndExtractPSBT(funding_psbt, funding)); + fixture.m_receiver_coin = MakeTransactionRef(std::move(funding)); + const auto block = fixture.Confirm(fixture.m_receiver_coin); + { + LOCK(fixture.m_receiver->cs_wallet); + const int height = WITH_LOCK(cs_main, return fixture.m_node.chainman->ActiveChain().Height()); + BOOST_REQUIRE(fixture.m_receiver->AddToWallet(fixture.m_receiver_coin, TxStateConfirmed{block.GetHash(), height, 1})); + fixture.m_receiver->SetLastBlockProcessed(height, block.GetHash()); + } + + const auto intent = fixture.Intent(); + const auto id = fixture.m_service->Start(intent); + fixture.Flush(); + const auto original = fixture.m_service->Snapshot(id)->original; + BOOST_REQUIRE(original); + CAmount original_input_value{0}; + { + LOCK(fixture.m_sender->cs_wallet); + for (const auto& input : original->vin) { + const auto* parent = fixture.m_sender->GetWalletTx(input.prevout.hash); + BOOST_REQUIRE(parent); + original_input_value += parent->GetTx()->vout.at(input.prevout.n).nValue; + } + } + const CAmount original_fee = original_input_value - original->GetValueOut(); + BOOST_REQUIRE_GE(original_fee, intent.fee_rate.GetFee(GetVirtualTransactionSize(*original))); + fixture.Deliver(); + + const auto unchecked = fixture.ReceiverOriginal(); + const auto history = std::make_shared(*fixture.m_log); + auto [receiver_proposal, proposal] = fixture.BuildProposal(unchecked, {}, MAX_STANDARD_TX_WEIGHT); + const auto reference = SignProposalCopy(fixture, proposal); + + auto partial_tx = proposal.GetUnsignedTx(); + BOOST_REQUIRE(partial_tx); + for (size_t i = 0; i < proposal.inputs.size(); ++i) { + partial_tx->vin[i].scriptSig = proposal.inputs[i].final_script_sig; + partial_tx->vin[i].scriptWitness = proposal.inputs[i].final_script_witness; + } + const CAmount partial_minimum = intent.fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{*partial_tx})); + const CAmount final_minimum = intent.fee_rate.GetFee(GetVirtualTransactionSize(*reference)); + BOOST_REQUIRE_LT(partial_minimum, final_minimum); + const CAmount target_fee = sufficient ? final_minimum + intent.fee_rate.GetFee(1) : (partial_minimum + final_minimum) / 2; + BOOST_REQUIRE_GE(target_fee, original_fee); + + uint64_t lower{1}, upper{MAX_STANDARD_TX_WEIGHT}; + while (lower <= upper) { + const uint64_t estimate = lower + (upper - lower) / 2; + fixture.m_log = std::make_shared(*history); + auto next = fixture.BuildProposal(unchecked, {}, estimate); + receiver_proposal = std::move(next.first); + proposal = std::move(next.second); + const CAmount fee = ProposalInputValue(proposal) - CTransaction{*proposal.GetUnsignedTx()}.GetValueOut(); + if (fee == target_fee) break; + if (fee < target_fee) { + lower = estimate + 1; + } else { + upper = estimate - 1; + } + } + + const auto candidate = SignProposalCopy(fixture, proposal); + const CAmount fee = ProposalInputValue(proposal) - candidate->GetValueOut(); + BOOST_REQUIRE_EQUAL(fee, target_fee); + BOOST_REQUIRE_LE(fee, intent.max_fee); + BOOST_REQUIRE_EQUAL(fee >= intent.fee_rate.GetFee(GetVirtualTransactionSize(*candidate)), sufficient); + auto post = receiver_proposal->create_post_request(fixture.m_relay); + auto response = Post(fixture.m_receiver_http, {post.request->url, post.request->content_type, post.request->body}); + BOOST_REQUIRE_MESSAGE(response.delivery == Delivery::Response, response.diagnostic); + BOOST_REQUIRE(receiver_proposal->process_response(response.body, post.client_response)->save(fixture.m_log)); + fixture.Tick(); + fixture.Deliver(); + + const auto journal = fixture.Journal(id); + BOOST_REQUIRE(journal.second); + BOOST_REQUIRE_EQUAL(CountEvent(journal.first, "Closed"), 1); + UniValue closed; + BOOST_REQUIRE(closed.read(journal.first.back())); + BOOST_REQUIRE(closed["Closed"].isObject()); + BOOST_REQUIRE(closed["Closed"]["Success"].isObject()); + + const auto view = *fixture.m_service->Snapshot(id); + BOOST_CHECK(!view.storage_uncertain); + BOOST_CHECK(view.has_unreleased_reservation); + if (sufficient) { + BOOST_REQUIRE_MESSAGE(view.node_accepted, view.diagnostic); + BOOST_REQUIRE(view.selected); + BOOST_CHECK(view.selected->GetHash() == candidate->GetHash()); + BOOST_CHECK_GE(ProposalInputValue(proposal) - view.selected->GetValueOut(), + intent.fee_rate.GetFee(GetVirtualTransactionSize(*view.selected))); + BOOST_CHECK(fixture.m_node.mempool->exists(view.selected->GetHash())); + return; + } + + BOOST_CHECK(view.issue == PaymentIssue::Signing); + BOOST_CHECK_EQUAL(view.diagnostic, "final proposal fee rate is below payment policy"); + BOOST_CHECK(!view.selected); + BOOST_CHECK(!view.selection_saved); + BOOST_CHECK(!view.wallet_recorded); + BOOST_CHECK(!view.node_accepted); + BOOST_CHECK(view.fallback_available); + BOOST_CHECK(fixture.ReadStoredPayment(id).selected.empty()); + BOOST_CHECK(!fixture.ReadStoredPayment(id).released); + BOOST_CHECK(!fixture.m_node.mempool->exists(candidate->GetHash())); + BOOST_CHECK(!fixture.m_node.mempool->exists(original->GetHash())); + { + LOCK(fixture.m_sender->cs_wallet); + BOOST_CHECK(!fixture.m_sender->mapWallet.contains(candidate->GetHash())); + BOOST_CHECK(!fixture.m_sender->mapWallet.contains(original->GetHash())); + fixture.CheckOwnedInputs(id, *original); + } + + fixture.m_service->PublishFallback(id); + fixture.Flush(); + const auto fallback = *fixture.m_service->Snapshot(id); + BOOST_REQUIRE_MESSAGE(fallback.node_accepted, fallback.diagnostic); + BOOST_REQUIRE(fallback.selected); + BOOST_CHECK(fallback.selected->Equals(*original)); + BOOST_CHECK(fixture.m_node.mempool->exists(original->GetHash())); +} + enum class UtxoFields { WitnessOnly, NonWitnessOnly, @@ -571,7 +744,7 @@ void CheckReceiverUtxoFields(IntegrationFixture& fixture, UtxoFields fields) BOOST_CHECK(fixture.m_node.mempool->exists(view.selected->GetHash())); } - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); } void CheckQueuedProposal(IntegrationFixture& fixture, bool cancel) @@ -620,13 +793,13 @@ void CheckQueuedProposal(IntegrationFixture& fixture, bool cancel) BOOST_REQUIRE(after_cancel->phase == PaymentPhase::Cancelled); BOOST_REQUIRE(!after_cancel->selected); BOOST_REQUIRE(after_cancel->possibly_exposed); - BOOST_REQUIRE(after_cancel->owns_inputs); + BOOST_REQUIRE(after_cancel->has_unreleased_reservation); const auto view = *fixture.m_service->Snapshot(id); BOOST_CHECK(view.phase == after_cancel->phase); BOOST_CHECK(!view.selected); BOOST_CHECK(view.possibly_exposed == after_cancel->possibly_exposed); - BOOST_CHECK(view.owns_inputs == after_cancel->owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation == after_cancel->has_unreleased_reservation); BOOST_CHECK(view.issue == after_cancel->issue); BOOST_CHECK_EQUAL(view.diagnostic, after_cancel->diagnostic); @@ -693,7 +866,7 @@ void CheckProposalDeadline(IntegrationFixture& fixture, std::chrono::millisecond BOOST_CHECK(!fixture.m_sender->mapWallet.contains(view.original->GetHash())); fixture.CheckOwnedInputs(id, *view.original); } - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.possibly_exposed); BOOST_CHECK(!fixture.m_node.mempool->exists(view.original->GetHash())); @@ -845,7 +1018,7 @@ BOOST_FIXTURE_TEST_CASE(sender_retry_signing_after_network_deadline, Integration BOOST_REQUIRE(unsigned_view.issue == PaymentIssue::Signing); BOOST_CHECK_EQUAL(unsigned_view.diagnostic, "Payjoin requires an unlocked wallet with local private keys"); BOOST_REQUIRE(!unsigned_view.selected); - BOOST_CHECK(unsigned_view.owns_inputs); + BOOST_CHECK(unsigned_view.has_unreleased_reservation); m_now = SenderService::Clock::time_point{} + intent.timeout + std::chrono::milliseconds{1}; while (!m_timers.empty() && m_timers.begin()->first <= m_now) { @@ -937,7 +1110,7 @@ BOOST_FIXTURE_TEST_CASE(sender_signs_receiver_proposal_and_publishes, Integratio BOOST_REQUIRE(prepared.original); BOOST_CHECK(prepared.original->HasWitness()); BOOST_CHECK(prepared.original_saved); - BOOST_CHECK(prepared.owns_inputs); + BOOST_CHECK(prepared.has_unreleased_reservation); BOOST_CHECK(prepared.locks_verified); BOOST_CHECK(!prepared.possibly_exposed); BOOST_CHECK(m_transport->m_pending.empty()); @@ -953,7 +1126,7 @@ BOOST_FIXTURE_TEST_CASE(sender_signs_receiver_proposal_and_publishes, Integratio m_transport->m_on_dispatch = [&] { const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.locks_verified); BOOST_CHECK(view.possibly_exposed); BOOST_CHECK(view.disclosure_saved); @@ -1034,7 +1207,7 @@ BOOST_FIXTURE_TEST_CASE(sender_lost_initial_response_requires_attention, Integra const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Delivery); BOOST_CHECK(view.possibly_exposed); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.selected); Tick(); @@ -1122,7 +1295,7 @@ BOOST_FIXTURE_TEST_CASE(sender_poll_trusts_persistent_memory_lock, IntegrationFi const auto view = *m_service->Snapshot(id); BOOST_CHECK(!view.issue); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.locks_verified); BOOST_CHECK(view.transport_accepted); BOOST_CHECK(!view.selected); @@ -1183,7 +1356,7 @@ BOOST_FIXTURE_TEST_CASE(sender_manual_unlock_blocks_publication_without_releasin Tick(); BOOST_CHECK(m_service->Snapshot(id)->issue == PaymentIssue::Reservation); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(!m_service->Snapshot(id)->selected); const auto other = m_service->Start(Intent()); @@ -1243,7 +1416,7 @@ BOOST_FIXTURE_TEST_CASE(sender_mempool_fallback_ignores_late_proposal, Integrati BOOST_CHECK(after.selected->GetWitnessHash() == original->GetWitnessHash()); BOOST_CHECK(after.phase == PaymentPhase::Published); BOOST_CHECK(after.node_accepted); - BOOST_CHECK(after.owns_inputs); + BOOST_CHECK(after.has_unreleased_reservation); BOOST_CHECK(!after.issue); BOOST_CHECK(after.diagnostic.empty()); @@ -1289,7 +1462,7 @@ BOOST_FIXTURE_TEST_CASE(sender_signing_failure_allows_explicit_choice_or_retry, BOOST_CHECK(m_service->Snapshot(id)->issue == PaymentIssue::Signing); BOOST_CHECK(!m_service->Snapshot(id)->selected); BOOST_CHECK(m_service->Snapshot(id)->fallback_available); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); m_service->RetrySigning(id); Flush(); @@ -1400,7 +1573,7 @@ BOOST_FIXTURE_TEST_CASE(sender_publication_retry_and_confirmed_cleanup, Integrat Flush(); BOOST_CHECK(m_service->Snapshot(id)->phase == PaymentPhase::Confirmed); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); m_service->RetryPublication(id); Flush(); @@ -1419,7 +1592,7 @@ BOOST_FIXTURE_TEST_CASE(sender_publication_retry_and_confirmed_cleanup, Integrat const auto view = *m_service->Snapshot(id); BOOST_CHECK(!view.settlement); BOOST_CHECK(view.phase == PaymentPhase::Attention); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(view.selected_presence == TransactionPresence::Inactive); } @@ -1449,7 +1622,7 @@ BOOST_FIXTURE_TEST_CASE(sender_selection_write_failure_forbids_publication, Inte BOOST_CHECK(!view.wallet_recorded); BOOST_CHECK(!view.node_accepted); BOOST_CHECK(!view.fallback_available); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); m_service->PublishFallback(id); Flush(); @@ -1521,7 +1694,7 @@ BOOST_FIXTURE_TEST_CASE(sender_original_settles_after_proposal_selection, Integr BOOST_REQUIRE(view.settlement); BOOST_CHECK(view.settlement->kind == SpendKind::Original); BOOST_CHECK(view.selected->GetWitnessHash() == selected->GetWitnessHash()); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); } BOOST_FIXTURE_TEST_CASE(sender_wallet_change_before_signing_wins, IntegrationFixture) @@ -1559,6 +1732,16 @@ BOOST_FIXTURE_TEST_CASE(sender_wallet_change_before_signing_wins, IntegrationFix BOOST_CHECK(!m_service->Snapshot(id)->selected); } +BOOST_FIXTURE_TEST_CASE(sender_rejects_insufficient_final_fee_rate, IntegrationFixture) +{ + CheckFinalProposalFeeRate(*this, false); +} + +BOOST_FIXTURE_TEST_CASE(sender_accepts_sufficient_final_fee_rate, IntegrationFixture) +{ + CheckFinalProposalFeeRate(*this, true); +} + BOOST_FIXTURE_TEST_CASE(sender_total_fee_policy_boundary, IntegrationFixture) { CreateAndProcessBlock({}, GetScriptForDestination(WitnessV0KeyHash{m_sender_key.GetPubKey()})); @@ -1758,7 +1941,7 @@ BOOST_FIXTURE_TEST_CASE(sender_poll_checks_only_original_reservations, Integrati BOOST_CHECK(refused.issue == PaymentIssue::Reservation); BOOST_CHECK_EQUAL(refused.diagnostic, "payment inputs changed; reconciliation required"); BOOST_CHECK(!refused.locks_verified); - BOOST_CHECK(refused.owns_inputs); + BOOST_CHECK(refused.has_unreleased_reservation); BOOST_CHECK(!refused.selected); BOOST_CHECK(!refused.node_accepted); BOOST_CHECK_EQUAL(m_transport->m_attempts, attempts); @@ -1904,7 +2087,7 @@ BOOST_FIXTURE_TEST_CASE(sender_poll_retryable_not_sent_and_terminal_delivery, In BOOST_CHECK(published.node_accepted); BOOST_CHECK(!published.issue); BOOST_CHECK(published.diagnostic.empty()); - BOOST_CHECK(published.owns_inputs); + BOOST_CHECK(published.has_unreleased_reservation); const auto journal = Journal(id); BOOST_CHECK(journal.second); @@ -1954,14 +2137,14 @@ BOOST_FIXTURE_TEST_CASE(sender_not_sent_poll_preserves_previous_disclosure, Inte BOOST_CHECK(failed.possibly_exposed); BOOST_CHECK(failed.disclosure_saved); BOOST_CHECK(ReadStoredPayment(id).exposed); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); m_service->Cancel(id); Flush(); const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); - BOOST_CHECK(cancelled.owns_inputs); + BOOST_CHECK(cancelled.has_unreleased_reservation); BOOST_CHECK(!cancelled.selected); BOOST_CHECK(!cancelled.node_accepted); @@ -1970,7 +2153,7 @@ BOOST_FIXTURE_TEST_CASE(sender_not_sent_poll_preserves_previous_disclosure, Inte const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(stopped.possibly_exposed); - BOOST_CHECK(stopped.owns_inputs); + BOOST_CHECK(stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).exposed); } diff --git a/src/wallet/test/payjoin_manager_tests.cpp b/src/wallet/test/payjoin_manager_tests.cpp index 6cd49dde86e3..6bfd2fc03ff3 100644 --- a/src/wallet/test/payjoin_manager_tests.cpp +++ b/src/wallet/test/payjoin_manager_tests.cpp @@ -353,7 +353,7 @@ BOOST_AUTO_TEST_CASE(manager_rechecks_network_policy_before_http_handoff) BOOST_CHECK(payment.issue == PaymentIssue::Delivery); BOOST_CHECK(!payment.possibly_exposed); BOOST_CHECK(!payment.storage_uncertain); - BOOST_CHECK(payment.owns_inputs); + BOOST_CHECK(payment.has_unreleased_reservation); BOOST_CHECK(!ReadStoredPayment(id).exposed); const auto cancelled = Await(m_manager.Execute(m_loaded, id, SenderCommand::Cancel)); @@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(manager_remove_wallet_releases_managed_wallet) const auto prepared = Await(context.payjoin->Read(wallet, id)); BOOST_REQUIRE_EQUAL(prepared.payments.size(), 1); BOOST_REQUIRE(prepared.payments.front().payment.original); - BOOST_REQUIRE(prepared.payments.front().payment.owns_inputs); + BOOST_REQUIRE(prepared.payments.front().payment.has_unreleased_reservation); std::weak_ptr released = wallet; BOOST_REQUIRE(RemoveWallet(context, wallet, std::nullopt)); @@ -723,7 +723,7 @@ BOOST_AUTO_TEST_CASE(manager_preparation_barrier_does_not_delay_stop_or_touch_de BOOST_REQUIRE_EQUAL(archived.payments.size(), 1); BOOST_CHECK(archived.payments.front().payment.phase == PaymentPhase::Stopped); BOOST_CHECK(!archived.payments.front().payment.original); - BOOST_CHECK(!archived.payments.front().payment.owns_inputs); + BOOST_CHECK(!archived.payments.front().payment.has_unreleased_reservation); barrier->Open(); m_node.validation_signals->SyncWithValidationInterfaceQueue(); @@ -753,7 +753,7 @@ BOOST_AUTO_TEST_CASE(manager_read_barrier_does_not_delay_stop_or_touch_destroyed BOOST_CHECK(final.status == CommandStatus::Stopped); BOOST_REQUIRE_EQUAL(final.payments.size(), 1); BOOST_CHECK(final.payments.front().payment.phase == PaymentPhase::Stopped); - BOOST_CHECK(!final.payments.front().payment.owns_inputs); + BOOST_CHECK(!final.payments.front().payment.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); BOOST_CHECK_EQUAL(m_loaded.use_count(), 1); const auto writes = m_database->WriteCount(); @@ -765,7 +765,7 @@ BOOST_AUTO_TEST_CASE(manager_read_barrier_does_not_delay_stop_or_touch_destroyed BOOST_CHECK(archived.status == CommandStatus::Stopped); BOOST_REQUIRE_EQUAL(archived.payments.size(), 1); BOOST_CHECK(archived.payments.front().payment.original->Equals(*final.payments.front().payment.original)); - BOOST_CHECK(!archived.payments.front().payment.owns_inputs); + BOOST_CHECK(!archived.payments.front().payment.has_unreleased_reservation); } BOOST_AUTO_TEST_CASE(manager_read_accounts_for_mempool_changes_without_external_barrier) @@ -791,7 +791,7 @@ BOOST_AUTO_TEST_CASE(manager_read_accounts_for_mempool_changes_without_external_ BOOST_REQUIRE_EQUAL(read.payments.size(), 1); BOOST_CHECK(read.payments.front().payment.selected_presence == TransactionPresence::Inactive); BOOST_CHECK(read.payments.front().payment.node_accepted); - BOOST_CHECK(read.payments.front().payment.owns_inputs); + BOOST_CHECK(read.payments.front().payment.has_unreleased_reservation); BOOST_CHECK(!read.payments.front().payment.settlement); BOOST_CHECK(!m_node.mempool->exists(selected->GetHash())); BOOST_CHECK(!ReadStoredPayment(id).released); diff --git a/src/wallet/test/payjoin_sender_fixture.cpp b/src/wallet/test/payjoin_sender_fixture.cpp index 123878d8f179..702a8f7faa65 100644 --- a/src/wallet/test/payjoin_sender_fixture.cpp +++ b/src/wallet/test/payjoin_sender_fixture.cpp @@ -419,7 +419,7 @@ void SenderFixture::CheckReleased(const uint256& id) const auto view = *m_service->Snapshot(id); BOOST_REQUIRE(view.original); BOOST_REQUIRE(view.settlement); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.storage_uncertain); BOOST_CHECK(!view.issue); diff --git a/src/wallet/test/payjoin_sender_tests.cpp b/src/wallet/test/payjoin_sender_tests.cpp index 99c7377dcddd..81f6dc3c8923 100644 --- a/src/wallet/test/payjoin_sender_tests.cpp +++ b/src/wallet/test/payjoin_sender_tests.cpp @@ -342,7 +342,7 @@ void CheckRetainedReservations(SenderFixture& fixture, const PaymentSnapshot& vi { BOOST_REQUIRE(view.original); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!fixture.ReadStoredPayment(view.id).released); @@ -452,7 +452,7 @@ void CheckPreparationWithPendingBlock(SenderFixture& fixture, bool send) BOOST_CHECK(failed.phase == PaymentPhase::Attention); BOOST_CHECK(failed.issue == PaymentIssue::Preparation); BOOST_CHECK(!failed.original); - BOOST_CHECK(!failed.owns_inputs); + BOOST_CHECK(!failed.has_unreleased_reservation); BOOST_CHECK(!failed.possibly_exposed); BOOST_CHECK(!fixture.HasStoredPayment(id)); BOOST_CHECK_EQUAL(fixture.m_transport->m_attempts, 0); @@ -532,7 +532,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_before_preparation_barrier_prevents_wallet_wo const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); BOOST_CHECK(!cancelled.original); - BOOST_CHECK(!cancelled.owns_inputs); + BOOST_CHECK(!cancelled.has_unreleased_reservation); BOOST_CHECK(!cancelled.possibly_exposed); BOOST_CHECK(!HasStoredPayment(id)); BOOST_CHECK_EQUAL(m_database->WriteCount(), writes); @@ -555,7 +555,7 @@ BOOST_AUTO_TEST_CASE(sender_preparation_barrier_wait_counts_towards_deadline) BOOST_CHECK(expired.phase == PaymentPhase::Attention); BOOST_CHECK(expired.issue == PaymentIssue::Deadline); BOOST_CHECK(!expired.original); - BOOST_CHECK(!expired.owns_inputs); + BOOST_CHECK(!expired.has_unreleased_reservation); BOOST_CHECK(!expired.possibly_exposed); BOOST_CHECK(!HasStoredPayment(id)); BOOST_CHECK_EQUAL(m_database->WriteCount(), writes); @@ -588,7 +588,7 @@ BOOST_AUTO_TEST_CASE(sender_command_results_belong_to_each_invocation) const auto& payment = second.payments.front().payment; BOOST_CHECK(payment.phase == PaymentPhase::Cancelled); BOOST_CHECK(!second.refusal); - BOOST_CHECK(!payment.owns_inputs); + BOOST_CHECK(!payment.has_unreleased_reservation); BOOST_REQUIRE(payment.original); BOOST_CHECK(ReadStoredPayment(id).released); for (const auto& input : payment.original->vin) { @@ -700,7 +700,7 @@ BOOST_AUTO_TEST_CASE(sender_command_reports_partial_storage_failure) BOOST_CHECK(result.status == CommandStatus::Failed); BOOST_REQUIRE_EQUAL(result.payments.size(), 1); BOOST_CHECK(result.payments.front().payment.storage_uncertain); - BOOST_CHECK(result.payments.front().payment.owns_inputs); + BOOST_CHECK(result.payments.front().payment.has_unreleased_reservation); BOOST_CHECK(result.payments.front().payment.issue == PaymentIssue::Storage); } @@ -746,7 +746,7 @@ BOOST_AUTO_TEST_CASE(sender_api_registration_does_not_wait_for_wallet_execution) BOOST_CHECK(result.status == CommandStatus::Stopped); BOOST_REQUIRE_EQUAL(result.payments.size(), 1); BOOST_CHECK(!result.payments.front().payment.possibly_exposed); - BOOST_CHECK(!result.payments.front().payment.owns_inputs); + BOOST_CHECK(!result.payments.front().payment.has_unreleased_reservation); } const auto final = scheduled->Snapshot(id); BOOST_REQUIRE(final && final->original); @@ -876,7 +876,7 @@ BOOST_AUTO_TEST_CASE(sender_operations_during_transport_stop_receive_final_deliv BOOST_REQUIRE_EQUAL(result.payments.size(), 1); BOOST_CHECK(result.payments.front().payment.id == id); BOOST_CHECK(!result.payments.front().payment.possibly_exposed); - BOOST_CHECK(!result.payments.front().payment.owns_inputs); + BOOST_CHECK(!result.payments.front().payment.has_unreleased_reservation); } const auto final = m_service->Snapshot(id); BOOST_REQUIRE(final && final->original); @@ -1133,7 +1133,7 @@ BOOST_AUTO_TEST_CASE(sender_retains_exposed_original_until_explicit_fallback) const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); BOOST_CHECK(cancelled.possibly_exposed); - BOOST_CHECK(cancelled.owns_inputs); + BOOST_CHECK(cancelled.has_unreleased_reservation); BOOST_CHECK(!cancelled.selected); BOOST_REQUIRE(cancelled.original->HasWitness()); @@ -1194,7 +1194,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_uses_not_sent_without_draining_executor) const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(!stopped.possibly_exposed); - BOOST_CHECK(!stopped.owns_inputs); + BOOST_CHECK(!stopped.has_unreleased_reservation); BOOST_CHECK(!stopped.storage_uncertain); BOOST_CHECK(!ReadStoredPayment(id).exposed); BOOST_CHECK(ReadStoredPayment(id).released); @@ -1231,7 +1231,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_uses_not_sent_queued_before_stop) const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(!stopped.possibly_exposed); - BOOST_CHECK(!stopped.owns_inputs); + BOOST_CHECK(!stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); } @@ -1251,7 +1251,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_uses_not_sent_after_deadline) const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(!stopped.possibly_exposed); - BOOST_CHECK(!stopped.owns_inputs); + BOOST_CHECK(!stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); } @@ -1267,14 +1267,14 @@ BOOST_AUTO_TEST_CASE(sender_stop_uses_not_sent_after_cancel) BOOST_REQUIRE(m_service->Snapshot(id)->phase == PaymentPhase::Cancelled); BOOST_REQUIRE(m_service->Snapshot(id)->possibly_exposed); - BOOST_REQUIRE(m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(m_service->Snapshot(id)->has_unreleased_reservation); m_service->Stop(); const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(!stopped.possibly_exposed); - BOOST_CHECK(!stopped.owns_inputs); + BOOST_CHECK(!stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); } @@ -1294,7 +1294,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_not_sent_save_failure_keeps_reservation) BOOST_CHECK(stopped.issue == PaymentIssue::Storage); BOOST_CHECK(stopped.storage_uncertain); BOOST_CHECK(stopped.possibly_exposed); - BOOST_CHECK(stopped.owns_inputs); + BOOST_CHECK(stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).exposed); BOOST_CHECK(!ReadStoredPayment(id).released); } @@ -1311,7 +1311,7 @@ BOOST_FIXTURE_TEST_CASE(sender_stop_uncertain_keeps_reservation, UncertainStopFi const auto stopped = *m_service->Snapshot(id); BOOST_CHECK(stopped.phase == PaymentPhase::Stopped); BOOST_CHECK(stopped.possibly_exposed); - BOOST_CHECK(stopped.owns_inputs); + BOOST_CHECK(stopped.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).exposed); BOOST_CHECK(!ReadStoredPayment(id).released); } @@ -1363,7 +1363,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_before_dispatch_releases_only_its_inputs) BOOST_REQUIRE_MESSAGE(active.transport_accepted, active.diagnostic); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); BOOST_CHECK(!cancelled.possibly_exposed); - BOOST_CHECK(!cancelled.owns_inputs); + BOOST_CHECK(!cancelled.has_unreleased_reservation); BOOST_REQUIRE_EQUAL(m_transport->m_pending.size(), 1); LOCK(m_sender->cs_wallet); @@ -1382,7 +1382,7 @@ BOOST_AUTO_TEST_CASE(sender_cancelled_payment_stays_cancelled_after_coin_reuse) const auto cancelled = *m_service->Snapshot(id); BOOST_REQUIRE(cancelled.phase == PaymentPhase::Cancelled); BOOST_REQUIRE(cancelled.original); - BOOST_REQUIRE(!cancelled.owns_inputs); + BOOST_REQUIRE(!cancelled.has_unreleased_reservation); BOOST_REQUIRE(!cancelled.possibly_exposed); auto next_intent = Intent(); @@ -1391,7 +1391,7 @@ BOOST_AUTO_TEST_CASE(sender_cancelled_payment_stays_cancelled_after_coin_reuse) const auto next = m_service->Start(next_intent); Flush(); - BOOST_REQUIRE(m_service->Snapshot(next)->owns_inputs); + BOOST_REQUIRE(m_service->Snapshot(next)->has_unreleased_reservation); Confirm(m_service->Snapshot(next)->original); const auto input = cancelled.original->vin[0].prevout; const bool locked = WITH_LOCK(m_sender->cs_wallet, return m_sender->IsLockedCoin(input)); @@ -1404,7 +1404,7 @@ BOOST_AUTO_TEST_CASE(sender_cancelled_payment_stays_cancelled_after_coin_reuse) const auto after = *m_service->Snapshot(id); BOOST_CHECK(after.phase == PaymentPhase::Cancelled); BOOST_CHECK(!after.settlement); - BOOST_CHECK(!after.owns_inputs); + BOOST_CHECK(!after.has_unreleased_reservation); LOCK(m_sender->cs_wallet); auto owner = ReadOwner(input); @@ -1448,7 +1448,7 @@ BOOST_AUTO_TEST_CASE(sender_pending_request_deadline_preserves_original) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Deadline); BOOST_CHECK(view.possibly_exposed); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.selected); BOOST_CHECK_EQUAL(m_transport->m_cancelled.size(), 1); } @@ -1488,7 +1488,7 @@ BOOST_AUTO_TEST_CASE(sender_handoff_and_busy_retry_deadline_boundaries) BOOST_CHECK(!view.selected); BOOST_CHECK(!view.wallet_recorded); BOOST_CHECK(!view.node_accepted); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!m_node.mempool->exists(view.original->GetHash())); @@ -1523,7 +1523,7 @@ BOOST_AUTO_TEST_CASE(sender_handoff_and_busy_retry_deadline_boundaries) m_service->Cancel(id); Flush(); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } } } @@ -1563,7 +1563,7 @@ BOOST_AUTO_TEST_CASE(sender_registration_begin_failure_preserves_database_and_fo BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK_EQUAL(view.diagnostic, "registration transaction failed"); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.storage_uncertain); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.transport_accepted); @@ -1629,7 +1629,7 @@ BOOST_AUTO_TEST_CASE(sender_registration_commit_failure_rolls_back_written_recor BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK_EQUAL(view.diagnostic, "registration failed"); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.storage_uncertain); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.transport_accepted); @@ -1670,6 +1670,7 @@ BOOST_AUTO_TEST_CASE(sender_registration_lost_commit_acknowledgement_preserves_d BOOST_CHECK_EQUAL(view.diagnostic, "registration failed"); BOOST_CHECK(view.storage_uncertain); BOOST_CHECK(!view.original_saved); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.transport_accepted); BOOST_CHECK_EQUAL(m_transport->m_attempts, 0); @@ -1717,7 +1718,7 @@ BOOST_AUTO_TEST_CASE(sender_partial_lock_failure_forbids_dispatch) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.storage_uncertain); BOOST_CHECK(m_transport->m_pending.empty()); } @@ -1736,7 +1737,7 @@ void CheckUnregisteredForeignSpend(SenderFixture& fixture, bool collision, bool const auto before = *fixture.m_service->Snapshot(id); BOOST_REQUIRE(before.original); BOOST_REQUIRE(!before.original_saved); - BOOST_REQUIRE(!before.owns_inputs); + BOOST_REQUIRE(!before.has_unreleased_reservation); BOOST_REQUIRE_EQUAL(before.storage_uncertain, uncertain); BOOST_REQUIRE(before.issue); BOOST_REQUIRE(!before.diagnostic.empty()); @@ -1792,7 +1793,7 @@ void CheckUnregisteredForeignSpend(SenderFixture& fixture, bool collision, bool BOOST_CHECK_EQUAL(view.diagnostic, before.diagnostic); BOOST_CHECK_EQUAL(view.storage_uncertain, uncertain); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!fixture.HasStoredPayment(id)); for (const auto& input : before.original->vin) { auto owner = fixture.ReadOwner(input.prevout); @@ -1884,7 +1885,7 @@ BOOST_AUTO_TEST_CASE(sender_observation_uses_spending_index_before_original_is_r BOOST_CHECK(view.observed_spend->transaction->GetHash() == std::min(confirmed[0]->GetHash(), confirmed[1]->GetHash())); BOOST_REQUIRE(view.settlement); BOOST_CHECK(view.settlement->transaction == view.observed_spend->transaction); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); } @@ -1916,7 +1917,7 @@ BOOST_AUTO_TEST_CASE(sender_initial_dispatch_trusts_persistent_memory_lock) const auto view = *m_service->Snapshot(id); BOOST_CHECK(!view.issue); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.locks_verified); BOOST_CHECK(view.possibly_exposed); BOOST_CHECK(view.transport_accepted); @@ -1949,7 +1950,7 @@ BOOST_AUTO_TEST_CASE(sender_initial_dispatch_rechecks_manual_unlock) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Reservation); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.selected); @@ -1984,7 +1985,7 @@ BOOST_AUTO_TEST_CASE(sender_persistent_marker_without_memory_flag_forbids_dispat const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Reservation); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.selected); @@ -2198,7 +2199,7 @@ BOOST_AUTO_TEST_CASE(sender_created_journal_failure_forbids_dispatch) BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK(view.storage_uncertain); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(m_transport->m_pending.empty()); m_database->CheckWriteFailed(); @@ -2215,7 +2216,7 @@ BOOST_AUTO_TEST_CASE(sender_disclosure_write_failure_forbids_dispatch) BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK(view.storage_uncertain); BOOST_CHECK(view.possibly_exposed); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(m_transport->m_pending.empty()); } @@ -2231,7 +2232,7 @@ BOOST_AUTO_TEST_CASE(sender_invalid_intent_has_no_reservation) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::InvalidIntent); BOOST_CHECK(!view.original); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.original_saved); } @@ -2299,7 +2300,7 @@ BOOST_AUTO_TEST_CASE(sender_intent_policy_boundaries) BOOST_CHECK(!view.transport_accepted); BOOST_CHECK(!view.original); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!HasStoredPayment(id)); BOOST_CHECK(!m_database->MakeBatch()->Exists(std::pair{std::string{JOURNAL_RECORD}, id})); @@ -2311,7 +2312,7 @@ BOOST_AUTO_TEST_CASE(sender_intent_policy_boundaries) BOOST_REQUIRE(view.original); BOOST_CHECK_EQUAL(m_transport->m_attempts, attempts + 1); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(HasStoredPayment(id)); BOOST_CHECK(WriteCount(writes, PAYMENT_RECORD) > 0); @@ -2395,7 +2396,7 @@ BOOST_AUTO_TEST_CASE(sender_original_fee_budget_boundary) BOOST_CHECK_EQUAL(view.diagnostic, "original fee exceeds policy"); BOOST_CHECK_EQUAL(m_transport->m_attempts, attempts); BOOST_CHECK(!view.original); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!HasStoredPayment(id)); BOOST_CHECK(!m_database->MakeBatch()->Exists(std::pair{std::string{JOURNAL_RECORD}, id})); for (const auto& type : std::vector{PAYMENT_RECORD, JOURNAL_RECORD, OWNER_RECORD, DBKeys::LOCKED_UTXO}) { @@ -2421,7 +2422,7 @@ BOOST_AUTO_TEST_CASE(sender_original_fee_budget_boundary) } BOOST_CHECK_EQUAL(total - view.original->GetValueOut(), fee); BOOST_CHECK_EQUAL(m_transport->m_attempts, attempts + 1); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(HasStoredPayment(id)); } } @@ -2459,7 +2460,7 @@ BOOST_AUTO_TEST_CASE(sender_confirmed_conflict_releases_exposed_inputs) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.phase == PaymentPhase::Conflicted); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.selected); m_service->PublishFallback(id); @@ -2475,7 +2476,7 @@ BOOST_AUTO_TEST_CASE(sender_confirmed_conflict_releases_exposed_inputs) BOOST_CHECK(!m_service->Snapshot(id)->settlement); BOOST_CHECK(m_service->Snapshot(id)->phase == PaymentPhase::Attention); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(!m_service->Snapshot(id)->fallback_available); } @@ -2515,7 +2516,7 @@ BOOST_AUTO_TEST_CASE(sender_confirmed_original_survives_cancel_and_stop) BOOST_CHECK(view.phase == PaymentPhase::Confirmed); BOOST_REQUIRE(view.settlement); BOOST_CHECK(view.settlement->kind == SpendKind::Original); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.selected); BOOST_CHECK(Journal(id) == journal); @@ -2542,7 +2543,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_clears_actions_for_selected_unconfirmed_payment BOOST_REQUIRE(before.selected); BOOST_REQUIRE(before.selection_saved); BOOST_REQUIRE(before.wallet_recorded); - BOOST_REQUIRE(before.owns_inputs); + BOOST_REQUIRE(before.has_unreleased_reservation); BOOST_REQUIRE(!before.node_accepted); BOOST_REQUIRE(!before.settlement); BOOST_REQUIRE(before.publication_retry_available); @@ -2571,7 +2572,7 @@ BOOST_AUTO_TEST_CASE(sender_stop_clears_actions_for_selected_unconfirmed_payment BOOST_CHECK(view.selected->Equals(*before.selected)); BOOST_CHECK(view.selection_saved); BOOST_CHECK(view.wallet_recorded); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.node_accepted); BOOST_CHECK(!view.settlement); BOOST_CHECK(view.possibly_exposed == before.possibly_exposed); @@ -2647,7 +2648,7 @@ BOOST_AUTO_TEST_CASE(sender_broadcast_disabled_and_current_observation) Flush(); BOOST_CHECK(m_service->Snapshot(id)->selected_presence == TransactionPresence::Abandoned); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); m_service->RetryPublication(id); Flush(); @@ -2698,7 +2699,7 @@ BOOST_AUTO_TEST_CASE(sender_observes_receiver_original_in_mempool) BOOST_CHECK(view.original_presence == TransactionPresence::Mempool); BOOST_REQUIRE(view.observed_spend); BOOST_CHECK(view.observed_spend->kind == SpendKind::Original); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.settlement); BOOST_REQUIRE(!m_transport->m_pending.empty()); @@ -2713,7 +2714,7 @@ BOOST_AUTO_TEST_CASE(sender_observes_receiver_original_in_mempool) BOOST_CHECK(view.node_accepted); BOOST_CHECK(view.selection_saved); BOOST_CHECK(view.wallet_recorded); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.issue); BOOST_CHECK(!view.fallback_available); BOOST_REQUIRE_EQUAL(m_transport->m_cancelled.size(), 1); @@ -2734,7 +2735,7 @@ BOOST_AUTO_TEST_CASE(sender_observes_receiver_original_in_mempool) BOOST_REQUIRE(view.selected); BOOST_CHECK(view.selected->GetWitnessHash() == original->GetWitnessHash()); BOOST_CHECK(view.node_accepted); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.phase == PaymentPhase::Published); BOOST_CHECK(!view.issue); @@ -2774,7 +2775,7 @@ BOOST_AUTO_TEST_CASE(sender_mempool_fallback_reports_publication_failures) BOOST_CHECK(view.selected->GetWitnessHash() == original->GetWitnessHash()); BOOST_CHECK(view.phase != PaymentPhase::Published); BOOST_CHECK(!view.node_accepted); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.storage_uncertain == fail_save); BOOST_CHECK(view.selection_saved == !fail_save); BOOST_CHECK(view.issue == (fail_save ? PaymentIssue::Storage : PaymentIssue::BroadcastDisabled)); @@ -2916,7 +2917,7 @@ BOOST_AUTO_TEST_CASE(sender_release_failure_preserves_ownership) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_CHECK(view.phase == PaymentPhase::Attention); BOOST_CHECK(!view.possibly_exposed); @@ -2990,7 +2991,7 @@ BOOST_AUTO_TEST_CASE(sender_owner_collision_is_known_registration_failure) BOOST_CHECK(view.issue == PaymentIssue::Reservation); BOOST_CHECK(!view.storage_uncertain); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(m_transport->m_pending.empty()); BOOST_CHECK(!HasStoredPayment(id)); @@ -3123,7 +3124,7 @@ BOOST_AUTO_TEST_CASE(sender_refresh_separates_submission_from_mempool_presence) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.node_accepted); BOOST_CHECK(view.selected_presence == TransactionPresence::Inactive); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.settlement); } @@ -3151,7 +3152,7 @@ BOOST_AUTO_TEST_CASE(sender_confirmed_release_failure_survives_refresh) BOOST_CHECK(failed.phase == PaymentPhase::Confirmed); BOOST_CHECK(failed.issue == PaymentIssue::Storage); BOOST_CHECK(failed.storage_uncertain); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(!failed.locks_verified); BOOST_CHECK(!failed.diagnostic.empty()); const auto failed_result = failed_refresh.get(); @@ -3172,7 +3173,7 @@ BOOST_AUTO_TEST_CASE(sender_confirmed_release_failure_survives_refresh) BOOST_CHECK(repeated.issue == failed.issue); BOOST_CHECK_EQUAL(repeated.diagnostic, failed.diagnostic); BOOST_CHECK(repeated.storage_uncertain); - BOOST_CHECK(repeated.owns_inputs); + BOOST_CHECK(repeated.has_unreleased_reservation); BOOST_CHECK(!repeated.locks_verified); BOOST_CHECK_EQUAL(m_database->WriteCount(), writes); const auto refresh_result = refresh_future.get(); @@ -3222,7 +3223,7 @@ BOOST_AUTO_TEST_CASE(sender_observation_exception_reports_failure_per_invocation const auto& payment = result.payments.front().payment; BOOST_CHECK(payment.issue == PaymentIssue::Storage); BOOST_CHECK(payment.storage_uncertain); - BOOST_CHECK(payment.owns_inputs); + BOOST_CHECK(payment.has_unreleased_reservation); BOOST_CHECK(!result.diagnostic.empty()); BOOST_CHECK_EQUAL(result.diagnostic, payment.diagnostic); BOOST_CHECK(ReadRecords(*m_database, {PAYMENT_RECORD, OWNER_RECORD, DBKeys::LOCKED_UTXO}) == records); @@ -3262,7 +3263,7 @@ BOOST_AUTO_TEST_CASE(sender_read_failure_still_updates_other_payments) const auto& failed = result.payments.front().payment; BOOST_CHECK(failed.id == failed_id); BOOST_CHECK(failed.storage_uncertain); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(failed.issue == PaymentIssue::Storage); BOOST_CHECK(!result.diagnostic.empty()); BOOST_CHECK_EQUAL(result.diagnostic, failed.diagnostic); @@ -3313,7 +3314,7 @@ BOOST_AUTO_TEST_CASE(sender_notification_release_failure_preserves_uncertainty) BOOST_CHECK(failed.phase == PaymentPhase::Confirmed); BOOST_CHECK(failed.issue == PaymentIssue::Storage); BOOST_CHECK(failed.storage_uncertain); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(!failed.locks_verified); BOOST_CHECK(!failed.diagnostic.empty()); BOOST_CHECK(!ReadStoredPayment(id).released); @@ -3360,7 +3361,7 @@ BOOST_AUTO_TEST_CASE(sender_queued_notification_does_not_settle_during_stop) Confirm(original); m_node.chain->waitForNotifications(); BOOST_REQUIRE(!m_service->Snapshot(id)->settlement); - BOOST_REQUIRE(m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(m_service->Snapshot(id)->has_unreleased_reservation); cleanup.m_stopper = std::thread{[&] { m_service->Stop(); @@ -3371,7 +3372,7 @@ BOOST_AUTO_TEST_CASE(sender_queued_notification_does_not_settle_during_stop) const auto writes = m_database->WriteCount(); m_queue->flush(); BOOST_CHECK(!m_service->Snapshot(id)->settlement); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(m_service->Snapshot(id)->issue == PaymentIssue::Delivery); BOOST_CHECK_EQUAL(m_database->WriteCount(), writes); BOOST_CHECK(ReadRecords(*m_database, {PAYMENT_RECORD, JOURNAL_RECORD, OWNER_RECORD, DBKeys::LOCKED_UTXO}) == records); @@ -3450,7 +3451,7 @@ BOOST_AUTO_TEST_CASE(sender_busy_wait_is_cancelled_or_expires) BOOST_CHECK_EQUAL(m_transport->m_attempts, attempts); BOOST_CHECK(m_service->Snapshot(cancelled)->phase == PaymentPhase::Cancelled); - BOOST_CHECK(!m_service->Snapshot(cancelled)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(cancelled)->has_unreleased_reservation); BOOST_CHECK(m_service->Snapshot(expired)->issue == PaymentIssue::Deadline); BOOST_CHECK(m_transport->m_pending.empty()); } @@ -3479,7 +3480,7 @@ BOOST_AUTO_TEST_CASE(sender_dispatch_deadline_after_disclosure) m_service->Cancel(id); Flush(); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } BOOST_AUTO_TEST_CASE(sender_rejected_initial_submit_releases_on_cancel) @@ -3494,7 +3495,7 @@ BOOST_AUTO_TEST_CASE(sender_rejected_initial_submit_releases_on_cancel) const auto failed = *m_service->Snapshot(id); BOOST_CHECK(failed.issue == PaymentIssue::Delivery); BOOST_CHECK(failed.original_saved); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(!failed.possibly_exposed); BOOST_CHECK(!failed.disclosure_saved); BOOST_CHECK(!failed.transport_accepted); @@ -3506,7 +3507,7 @@ BOOST_AUTO_TEST_CASE(sender_rejected_initial_submit_releases_on_cancel) const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); - BOOST_CHECK(!cancelled.owns_inputs); + BOOST_CHECK(!cancelled.has_unreleased_reservation); BOOST_CHECK(!cancelled.selected); BOOST_CHECK(!cancelled.node_accepted); } @@ -3530,7 +3531,7 @@ BOOST_AUTO_TEST_CASE(sender_disclosure_rollback_failure_is_conservative) BOOST_CHECK(failed.storage_uncertain); BOOST_CHECK(failed.possibly_exposed); BOOST_CHECK(failed.disclosure_saved); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(!failed.transport_accepted); BOOST_CHECK(m_transport->m_pending.empty()); } @@ -3561,7 +3562,7 @@ BOOST_AUTO_TEST_CASE(sender_not_sent_initial_completion_releases_on_cancel) const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); - BOOST_CHECK(!cancelled.owns_inputs); + BOOST_CHECK(!cancelled.has_unreleased_reservation); BOOST_CHECK(!cancelled.selected); BOOST_CHECK(!cancelled.node_accepted); BOOST_REQUIRE(cancelled.original); @@ -3574,7 +3575,7 @@ BOOST_AUTO_TEST_CASE(sender_not_sent_initial_completion_releases_on_cancel) BOOST_CHECK_EQUAL(m_database->WriteCount(), writes); BOOST_CHECK(m_service->Snapshot(id)->phase == PaymentPhase::Cancelled); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_cancel_releases) @@ -3591,7 +3592,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_cancel_releases) BOOST_REQUIRE(m_service->Snapshot(id)->phase == PaymentPhase::Cancelled); BOOST_REQUIRE(m_service->Snapshot(id)->possibly_exposed); - BOOST_REQUIRE(m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(m_service->Snapshot(id)->has_unreleased_reservation); complete({Delivery::NotSent, {}, "cancelled request was not sent"}); Flush(); @@ -3599,7 +3600,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_cancel_releases) const auto cancelled = *m_service->Snapshot(id); BOOST_CHECK(cancelled.phase == PaymentPhase::Cancelled); BOOST_CHECK(!cancelled.possibly_exposed); - BOOST_CHECK(!cancelled.owns_inputs); + BOOST_CHECK(!cancelled.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); const auto writes = m_database->WriteCount(); @@ -3630,7 +3631,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_deadline_waits_for_cancel) Flush(); BOOST_REQUIRE(m_service->Snapshot(id)->phase == PaymentPhase::Cancelled); - BOOST_REQUIRE(m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(m_service->Snapshot(id)->has_unreleased_reservation); } complete({Delivery::NotSent, {}, "expired request was not sent"}); @@ -3643,7 +3644,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_deadline_waits_for_cancel) BOOST_CHECK(after_completion.phase == PaymentPhase::Attention); BOOST_CHECK(after_completion.issue == PaymentIssue::Deadline); BOOST_CHECK_EQUAL(after_completion.diagnostic, "payment deadline reached"); - BOOST_CHECK(after_completion.owns_inputs); + BOOST_CHECK(after_completion.has_unreleased_reservation); BOOST_CHECK(!ReadStoredPayment(id).released); m_service->Cancel(id); @@ -3651,7 +3652,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_after_deadline_waits_for_cancel) } BOOST_CHECK(m_service->Snapshot(id)->phase == PaymentPhase::Cancelled); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).released); } } @@ -3678,7 +3679,7 @@ BOOST_AUTO_TEST_CASE(sender_retired_not_sent_save_failure_keeps_reservation) BOOST_CHECK(failed.issue == PaymentIssue::Storage); BOOST_CHECK(failed.storage_uncertain); BOOST_CHECK(failed.possibly_exposed); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); BOOST_CHECK(ReadStoredPayment(id).exposed); } @@ -3700,12 +3701,12 @@ BOOST_AUTO_TEST_CASE(sender_not_sent_disclosure_rollback_failure_preserves_reser BOOST_CHECK(failed.possibly_exposed); BOOST_CHECK(failed.disclosure_saved); BOOST_CHECK(ReadStoredPayment(id).exposed); - BOOST_CHECK(failed.owns_inputs); + BOOST_CHECK(failed.has_unreleased_reservation); m_service->Cancel(id); Flush(); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(!m_service->Snapshot(id)->selected); BOOST_CHECK(!m_service->Snapshot(id)->node_accepted); } @@ -3812,7 +3813,7 @@ BOOST_AUTO_TEST_CASE(sender_owner_cursor_failure_prevents_selection) BOOST_CHECK(!m_service->Snapshot(id)->original); BOOST_CHECK(!m_service->Snapshot(id)->original_saved); BOOST_CHECK(!m_service->Snapshot(id)->storage_uncertain); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } } @@ -3832,7 +3833,7 @@ BOOST_AUTO_TEST_CASE(sender_invalid_uri_and_relay_can_be_cancelled) BOOST_CHECK(!view.diagnostic.empty()); BOOST_CHECK(view.diagnostic != "std::exception"); BOOST_CHECK(!view.original_saved); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.storage_uncertain); } @@ -3846,7 +3847,7 @@ BOOST_AUTO_TEST_CASE(sender_invalid_uri_and_relay_can_be_cancelled) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Protocol || view.issue == PaymentIssue::Delivery); BOOST_CHECK(view.original_saved); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.possibly_exposed); BOOST_CHECK(!view.disclosure_saved); BOOST_CHECK(!view.transport_accepted); @@ -3856,7 +3857,7 @@ BOOST_AUTO_TEST_CASE(sender_invalid_uri_and_relay_can_be_cancelled) m_service->Cancel(id); Flush(); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(!m_service->Snapshot(id)->selected); BOOST_CHECK(!m_service->Snapshot(id)->node_accepted); } @@ -3922,7 +3923,7 @@ BOOST_AUTO_TEST_CASE(sender_mempool_refresh_preserves_delivery_diagnostic_until_ BOOST_CHECK(published.node_accepted); BOOST_CHECK(!published.issue); BOOST_CHECK(published.diagnostic.empty()); - BOOST_CHECK(published.owns_inputs); + BOOST_CHECK(published.has_unreleased_reservation); BOOST_CHECK(Journal(id).second); BOOST_CHECK_EQUAL(m_transport->m_attempts, 1); @@ -3932,7 +3933,7 @@ BOOST_AUTO_TEST_CASE(sender_mempool_refresh_preserves_delivery_diagnostic_until_ Flush(); BOOST_CHECK(m_service->Snapshot(id)->phase == PaymentPhase::Confirmed); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } BOOST_AUTO_TEST_CASE(sender_reorg_original_does_not_restore_released_ownership) @@ -3946,7 +3947,7 @@ BOOST_AUTO_TEST_CASE(sender_reorg_original_does_not_restore_released_ownership) Flush(); BOOST_REQUIRE(m_service->Snapshot(id)->settlement); - BOOST_REQUIRE(!m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(!m_service->Snapshot(id)->has_unreleased_reservation); Disconnect(block); for (int i = 0; i < 2; ++i) { @@ -3960,7 +3961,7 @@ BOOST_AUTO_TEST_CASE(sender_reorg_original_does_not_restore_released_ownership) BOOST_CHECK(!view.settlement); BOOST_CHECK(view.phase == PaymentPhase::Attention); BOOST_CHECK(view.issue == PaymentIssue::ObservedSpend); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); BOOST_CHECK(!view.fallback_available); } @@ -3993,7 +3994,7 @@ BOOST_AUTO_TEST_CASE(sender_reorg_original_does_not_restore_released_ownership) const auto next = m_service->Start(next_intent); Flush(); - BOOST_REQUIRE_MESSAGE(m_service->Snapshot(next)->owns_inputs, m_service->Snapshot(next)->diagnostic); + BOOST_REQUIRE_MESSAGE(m_service->Snapshot(next)->has_unreleased_reservation, m_service->Snapshot(next)->diagnostic); const auto input = original->vin[0].prevout; m_service->Cancel(id); @@ -4005,7 +4006,7 @@ BOOST_AUTO_TEST_CASE(sender_reorg_original_does_not_restore_released_ownership) auto owner = ReadOwner(input); BOOST_CHECK(owner == next); BOOST_CHECK(m_sender->IsLockedCoin(input)); - BOOST_CHECK(!m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(!m_service->Snapshot(id)->has_unreleased_reservation); } Confirm(original); @@ -4048,7 +4049,7 @@ BOOST_AUTO_TEST_CASE(sender_reorg_selected_preserves_uncertain_release) BOOST_CHECK(view.phase == PaymentPhase::Attention); BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.fallback_available); } @@ -4079,7 +4080,7 @@ BOOST_AUTO_TEST_CASE(sender_unlock_failure_keeps_release_uncertain) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(view.issue == PaymentIssue::Storage); const auto input = original->vin[0].prevout; @@ -4095,7 +4096,7 @@ BOOST_AUTO_TEST_CASE(sender_unlock_failure_keeps_release_uncertain) Flush(); BOOST_CHECK(m_service->Snapshot(id)->storage_uncertain); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); const auto stored = ReadStoredPayment(id); BOOST_CHECK(!stored.released); @@ -4146,7 +4147,7 @@ void CheckStaleFallbackMarker(SenderFixture& fixture, bool persistent_failure) BOOST_REQUIRE(view.settlement); BOOST_CHECK(view.settlement->kind == SpendKind::Original); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK(!view.diagnostic.empty()); @@ -4210,7 +4211,7 @@ BOOST_AUTO_TEST_CASE(sender_release_checks_all_owners_before_any_unlock) const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.issue == PaymentIssue::Reservation); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.storage_uncertain); const auto stored = ReadStoredPayment(id); @@ -4241,7 +4242,7 @@ struct ReleaseFailureFixture : SenderFixture { { const auto view = *m_service->Snapshot(m_id); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_REQUIRE(view.original); auto owner = ReadOwner(view.original->vin[0].prevout); @@ -4276,7 +4277,7 @@ BOOST_AUTO_TEST_CASE(sender_release_lost_commit_acknowledgement_preserves_uncert Flush(); const auto prepared = *m_service->Snapshot(id); BOOST_REQUIRE(!prepared.possibly_exposed); - BOOST_REQUIRE(prepared.owns_inputs); + BOOST_REQUIRE(prepared.has_unreleased_reservation); { LOCK(m_sender->cs_wallet); CheckOwnedInputs(id, *prepared.original); @@ -4290,7 +4291,7 @@ BOOST_AUTO_TEST_CASE(sender_release_lost_commit_acknowledgement_preserves_uncert BOOST_REQUIRE(!m_database->HasActiveTxn()); const auto view = *m_service->Snapshot(id); BOOST_CHECK(view.storage_uncertain); - BOOST_CHECK(view.owns_inputs); + BOOST_CHECK(view.has_unreleased_reservation); BOOST_CHECK(!view.locks_verified); BOOST_CHECK(view.issue == PaymentIssue::Storage); BOOST_CHECK_EQUAL(view.diagnostic, "ownership release rollback failed"); @@ -4329,7 +4330,7 @@ BOOST_FIXTURE_TEST_CASE(sender_release_owner_erase_failure_does_not_claim_succes Flush(); BOOST_CHECK(m_service->Snapshot(m_id)->storage_uncertain); - BOOST_CHECK(m_service->Snapshot(m_id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(m_id)->has_unreleased_reservation); } BOOST_FIXTURE_TEST_CASE(sender_release_abort_failure_remains_uncertain, ReleaseFailureFixture) @@ -4367,7 +4368,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_after_reorg_observes_reconfirmation) Flush(); BOOST_REQUIRE(m_service->Snapshot(id)->settlement); - BOOST_REQUIRE(!m_service->Snapshot(id)->owns_inputs); + BOOST_REQUIRE(!m_service->Snapshot(id)->has_unreleased_reservation); Disconnect(block); m_service->Refresh(id); @@ -4387,7 +4388,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_after_reorg_observes_reconfirmation) BOOST_CHECK(view.original_presence == TransactionPresence::Confirmed); BOOST_CHECK(view.phase == PaymentPhase::Confirmed); BOOST_CHECK(view.settlement.has_value()); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); } } @@ -4432,7 +4433,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_after_original_reorg_observes_conflict) BOOST_REQUIRE(view.settlement); BOOST_CHECK(view.settlement->kind == SpendKind::Other); BOOST_CHECK(view.phase == PaymentPhase::Conflicted); - BOOST_CHECK(!view.owns_inputs); + BOOST_CHECK(!view.has_unreleased_reservation); } } @@ -4749,7 +4750,7 @@ BOOST_AUTO_TEST_CASE(sender_cancel_after_reorg_preserves_uncertain_release) Flush(); BOOST_CHECK(m_service->Snapshot(id)->storage_uncertain); - BOOST_CHECK(m_service->Snapshot(id)->owns_inputs); + BOOST_CHECK(m_service->Snapshot(id)->has_unreleased_reservation); BOOST_CHECK(m_service->Snapshot(id)->issue == PaymentIssue::Storage); } diff --git a/test/functional/wallet_payjoin.py b/test/functional/wallet_payjoin.py index 0fa3d2047d2d..53f111f5bf0c 100755 --- a/test/functional/wallet_payjoin.py +++ b/test/functional/wallet_payjoin.py @@ -170,7 +170,10 @@ def test_not_sent(self, sender, request): payment = sender.getpayjoin(payment_id) assert_equal(payment["problem"]["code"], "delivery") assert_equal(payment["possibly_exposed"], False) - assert_equal(payment["reservations"]["owned"], True) + reservations = payment["reservations"] + assert isinstance(reservations["unreleased"], bool) + assert "owned" not in reservations + assert_equal(payment["reservations"]["unreleased"], True) duplicate = sender.sendpayjoin(**request) assert_equal(duplicate["payment_id"], payment_id) assert_equal(duplicate["duplicate"], True) @@ -182,7 +185,7 @@ def test_not_sent(self, sender, request): assert_equal(cancelled["result"], "completed") assert_equal(cancelled["payment"]["phase"], "cancelled") assert_equal(cancelled["payment"]["possibly_exposed"], False) - assert_equal(cancelled["payment"]["reservations"]["owned"], False) + assert_equal(cancelled["payment"]["reservations"]["unreleased"], False) assert_equal(sender.listlockunspent(), locks) return payment_id @@ -295,7 +298,7 @@ def published(): self.wait_until(lambda: sender.getpayjoin(payment_id)["phase"] == "confirmed") settled = sender.getpayjoin(payment_id) assert_equal(settled["selected"]["presence"], "confirmed") - assert_equal(settled["reservations"]["owned"], False) + assert_equal(settled["reservations"]["unreleased"], False) assert_equal(settled["storage_uncertain"], False) assert "problem" not in settled, settled locks = {(coin["txid"], coin["vout"]) for coin in sender.listlockunspent()} @@ -309,7 +312,7 @@ def published(): assert_equal(changed["problem"]["code"], "observed_spend") assert_equal(changed["selected"]["txid"], txid) assert changed["selected"]["presence"] != "confirmed" - assert_equal(changed["reservations"]["owned"], False) + assert_equal(changed["reservations"]["unreleased"], False) node.reconsiderblock(block) assert_equal(sender.getpayjoin(payment_id)["phase"], "confirmed") @@ -332,12 +335,12 @@ def published(): self.wait_until(lambda: sender.getpayjoin(deadline_id).get("problem", {}).get("code") == "deadline") expired = sender.getpayjoin(deadline_id) assert_equal(expired["possibly_exposed"], True) - assert_equal(expired["reservations"]["owned"], True) + assert_equal(expired["reservations"]["unreleased"], True) assert "selected" not in expired assert_equal(node.getrawmempool(), []) cancelled = sender.cancelpayjoin(deadline_id) assert_equal(cancelled["result"], "completed") - assert_equal(cancelled["payment"]["reservations"]["owned"], True) + assert_equal(cancelled["payment"]["reservations"]["unreleased"], True) assert_equal(node.getrawmempool(), []) fallback = sender.publishpayjoinfallback(deadline_id) assert_equal(fallback["result"], "completed")