Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Refactor sync_kyoto_client - #230

Merged
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto
Feb 24, 2026
Merged

Refactor sync_kyoto_client #230
tvpeter merged 4 commits into
bitcoindevkit:masterfrom
Mshehu5:refactor_kyoto

Conversation

@Mshehu5

@Mshehu5Mshehu5 commented Dec 21, 2025

Copy link
Copy Markdown
Contributor

Description

This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient only being an owned variable rather than being able to be borrowed/referenced as &Blockchainclient.
While working on persistence I ran into problems while working on resume command which needs a blockchain client to resume states such as monitor_payjoin_proposal (receiver) and process_payjoin_proposal (sender)
Because BlockchainClient can only be owned the current design will require a function signature of passing two separate clients to resume sender and receiver states. I initially considered splitting the command into resume_send and resume_receive but this does not fully solve the issue. In particular the sender’s process_payjoin_proposal may call broadcast_transaction and potentially broadcast multiple transactions for persisted send entries stored in the database which still requires reusable access to the client.

This Ownership issue was previously mentioned in #200 and is also noted in a comment at the top of monitor_payjoin_proposal. It prevents the function from being able to resync multiple times and reliably detect when a transaction appears in the mempool. The root cause is that the Kyoto client Box is destructured and spawned into other tasks when passed through sync_kyoto_client making it unusable afterward.

What this PR changes
This PR fixes the issue by refactoring sync_kyoto_client

  • The logic responsible for running the Kyoto node and logger is moved into new_blockchain_client. This makes it that node is started at start of command and not during every sync

  • Instead of returning a Box the function now returns a KyotoClientHandle. Previously the boxed client takes ownership when destructured inside sync_kyoto_client, preventing reuse/reference.
    With the new design sync_kyoto_client takes &KyotoClientHandle, allowing the client to be Refrenced which can be used for syncing and broadcasting transactions without being owned

  • Additionally monitor_payjoin_proposal is refactored to support resyncing demonstrating that the Kyoto client refactor successfully resolves the original limitations

Notes to the reviewers

After refactor I tested the kyoto client on regtest (Cause I do not have access to a signet) I had to set a trusted peer in the code to connect with a cbf count of 1 this worked and I also made transaction using the steps below:
N.B Payjoin was also tested for the monitor_payjoin_proposal refactor using steps in project readme

bitcoin.conf regtest=1
server=1
rpcuser=user
rpcpassword=password
rpcallowip=127.0.0.1
blockfilterindex=1
listen=1
fallbackfee=0.001
[regtest]
bind=127.0.0.1
port=18444
peerblockfilters=1

Step 1: Create transaction

PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
create_tx --to $RECEIVER_ADDR:50000 --fee_rate 1.0 | jq -r '.psbt')

Step 2: Sign transaction

SIGNED_PSBT=$(cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
sign "$PSBT" | jq -r '.psbt')

Step 3: Broadcast transaction

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
broadcast --psbt "$SIGNED_PSBT"

Mine a block to confirm
bitcoin-cli -regtest generatetoaddress 1 $(bitcoin-cli -regtest getnewaddress)

Checking Transaction Status
After broadcasting, wait a moment and sync your wallet:
Sync wallet

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
--cbf-peer $CBF_PEER \
--cbf-conn-count $CBF_CONN_COUNT \
sync

Check balance

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
balance

List recent transactions

cargo run --features cbf,sqlite -- \
--network $NETWORK \
wallet \
--wallet sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
transactions

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@coveralls

coveralls commented Dec 22, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21587356847

Details

  • 0 of 121(0.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.2%) to 10.89%

Changes Missing CoverageCovered LinesChanged/Added Lines%
src/handlers.rs0190.0%
src/utils.rs0220.0%
src/payjoin/mod.rs0800.0%
Files with Coverage ReductionNew Missed Lines%
src/handlers.rs113.1%
src/payjoin/mod.rs10.0%
src/utils.rs10.0%
TotalsCoverage Status
Change from base Build 21153868360:0.2%
Covered Lines:268
Relevant Lines:2461

💛 - Coveralls

@notmandatory

notmandatory commented Jan 10, 2026

Copy link
Copy Markdown
Member

I tried to do a test a payjoin with CBF on signet today and ran into this error on the pj sender side:

 Generic error: Failed to create a post request for a Payjoin send: v2 error: Plaintext too large, max size is 7055 bytes, actual size is 109913 bytes

I also got this error on the receiver side after trying the send a second time:

Polling receive request...
Polling receive request...
Got a request from the sender. Responding with a Payjoin proposal.
Checking whether the original proposal can be broadcasted itself is not supported. If the Payjoin fails, manually fall back to the transaction below.
0200000000010104d1310bd87d8051211434b1538ca779f4d70c84befd6185295acc30bbfcf3161300000000fdffffff028813000000000000160014ff80fc5fe8415f7de00c54fe6b189ad08cd84f00957f040000000000160014e88f3c6f6c7382dc51631c815dde6df53b2ebc33024730440220665be4a4d3affb4a4f61b5ebfd3aac22a193968057fc0a07fba46c001457935d0220500ac88227614fe2e96b144e78ec62f1b902eaabe2513668841536ae76a1daf0012102415dca5a066b27c1fe5fe4459bb594805ba1cc83944e9570628dcac7509442e0c45e0400
Checking whether the inputs in the proposal were seen before to protect from probing attacks is not supported. Skipping the check...
[2026-01-10T01:54:31Z ERROR bdk_cli] Generic error: Error occurred when creating a post request for sending final Payjoin proposal: Protocol error: Hpke decryption failed: Plaintext too large, max size is 7088 bytes, actual size is 85324 bytes

The URI was using to send to is:

"bitcoin:tb1ql7q0chlgg90hmcqv2nlxkxy66zxdsncqunc22n?amount=0.00005&pjos=0&pj=HTTPS://PAYJO.IN/G5DQ7XTXNZ36S%23EX1UTLKY6G-OH1QYPFLM8XL59R0XV4VGPLS7FRDSSM4TUXL07TXCWC4S0GLVLNK2SE4NQ-RK1Q0JRUHAAS20FHY6DW67ARDYAGUWZY4MJZRQV8FAQDSEQDRWHQLHNZ"

I suspect the error is not related to this PR but maybe it could be? I was able to successfully do a payjoin tx when I tested #200 but that was on regtest and not signet. Is this something you can look into or @mehmetefeumit can help investigate?

If you want to test with my already funded signet wallets the descriptors are below. I ended up setting up my own local signet node, syncing it, and then setting my CBF number of connections to 1. I also had to patch the code to manually set a peer pointing to my local signet node "127.0.0.1:38332".

wallet "pj1" the sender:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)
echo $INT_DESCRIPTOR
wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)
cargo run --features cbf -- wallet -w pj1 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

wallet "pj2" the receiver:

echo $PAYJOIN_DIRECTORY https://payjo.in/
echo $PAYJOIN_OHTTP_RELAY https://pj.benalleng.com/
echo $EXT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/0/*)
echo $INT_DESCRIPTOR wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/1'/1/*)
cargo run --features cbf -- wallet -w pj2 --cbf-peer 127.0.0.1:38332 --cbf-conn-count=1 sync 

@notmandatory

Copy link
Copy Markdown
Member

My patch to add the --cbf-peer option is: notmandatory@71fbacf

@notmandatory

Copy link
Copy Markdown
Member

Otherwise this PR looks good to me, especially good commit messages and PR description.

@Mshehu5

Copy link
Copy Markdown
ContributorAuthor

My patch to add the --cbf-peer option is: notmandatory@71fbacf

Thank you very much for the review while woking on the PR I tested a CBF(kyoto) payjoin on regtest and it works using this patch:
97cb477
Please note that to make my regtest patch work I needed to change random_broadcast and use TxBroadcastPolicy::AllPeers instead because sending the proposal seems to get stuck there given that we are only using a single peer.

One thing to note is that on CBF (Kyoto) we cannot sync in monitor_payjoin_proposal. This is because it is designed to wait for blockchain events (new blocks, filters) not for mempool transactions unlike RPC which can directly query the mempool.(You can manually check the transaction though with bitcoin-cli getmempoolentry )

So it seems the issue might be from signet I will try to access a signet server and test whether I also get the same results you are seeing.

@Mshehu5

Mshehu5 commented Jan 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

I also made transactions using command in the PR body and no errors

firstly i will drop a script that i use to export variables needed for transactions i mainly just do
source wallet_config.sh

#### RECEIVER WALLET CONFIGURATION
export RECEIVER_XPRV="tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W"
export RECEIVER_EXT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/0/*)"
export RECEIVER_INT_DESC="tr(tprv8ZgxMBicQKsPdNJKeeLb4Ezh37dfsc5dJ2mkbs2XYjpY12mceBHg6cDtNijCccgEn7AUTydKBoeEdAn7m85U7L6ZYRRYL71SaMi5qr4bo8W/86h/1h/0h/1/*)"
#### SENDER WALLET CONFIGURATION
export SENDER_XPRV="tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY"
export SENDER_EXT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/0/*)"
export SENDER_INT_DESC="wpkh(tprv8ZgxMBicQKsPesNPNBP1VdHJzHe6MTiFt159vqw17QytNpvmEDKFuwj3vgo6WhAjdNYG1oUw7BQyJ9bJNWHuwpABmvBogsTR7q2C1kaPGrY/84'/1'/0'/1/*)"
#### NETWORK & CONNECTION SETTINGS
export NETWORK="signet"
export DATABASE_TYPE="sqlite"
export CLIENT_TYPE="cbf"
export CBF_CONN_COUNT="1"
#### PAYJOIN SETTINGS
export PAYJOIN_DIRECTORY="https://payjo.in"
export OHTTP_RELAY="https://ohttp.achow101.com"
export OHTTP_RELAY2="https://pj.bobspacebkk.com"
export OHTTP_RELAY3="https://pj.benalleng.com/"
#### WALLET ADDRESSES export RECEIVER_ADDR="tb1pvsascmephky7830ssxulqz28k7pxhtclhe0fpfvg09cjl5qxyqts4v6h4n"
export SENDER_ADDR="bcrt1qq78rcr8ptb1qy9hwwjv7hv7ap6v32kpc5787a74qdzfkprah576sd3efyv07mlv9u6x6rhd9sr0s8yx9"
echo "✅ Wallet configuration loaded!"
echo " Network: $NETWORK"
echo " Client: $CLIENT_TYPE"
if [ "$CLIENT_TYPE" = "cbf" ]; then
echo " CBF Peer: $CBF_PEER"
fi
if [ -n "$RECEIVER_EXT_DESC" ]; then
echo " Receiver: ${RECEIVER_EXT_DESC:0:60}..."
fi
if [ -n "$SENDER_EXT_DESC" ]; then
echo " Sender: ${SENDER_EXT_DESC:0:60}..."
fi

The commands for payjoin :

Make sure to sync wallets first
receiver sync :

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync 

sender sync:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
sync

receive payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_receiver_wallet \
--ext-descriptor "$RECEIVER_EXT_DESC" \
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3

send payjoin transaction:

cargo run --features cbf -- \
--network $NETWORK \
wallet \
--wallet signet_sender_wallet \
--ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE \
send_payjoin \
--uri "$PAYJOIN_URI" \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

for PAYJOIN_URI i usually export it to sender CLI after copying it once its displayed in receiver CLI
export PAYJOIN_URI=""

these are the steps and config I followed to get a successful payjoin transaction on public signet

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @Mshehu5, for your work on this. In addition to the comments I’ve left below, you can test the PR on Signet by using a VPN. That has been working for me if you are located in Nigeria.

Also, kindly rebase the code so that the wallet configuration changes are reflected, which will make testing this PR easier.

Thank you!

Comment threadsrc/payjoin/mod.rs
)
.save(persister)
.map_err(|e| {
Error::Generic(format!("Error occurred when saving after checking that sender has broadcasted the Payjoin transaction: {e}"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was widely used in the previous payjoin PR, you can optimize the code a bit by adding Payjoin variant errors to the BDKCliError

@Mshehu5Mshehu5Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding a variant would help optimize this but this particular error in the codebase is related to persistence. It would be better to add this variant in the intended persitence addition follow-up PR,where it fits the scope more appropriately.

I have however, added a fourth commit that introduces error variants for functions that return errors in the Payjoin crate. This slightly optimizes the code and removes the need for .map_err() calls.

Comment threadsrc/handlers.rs Outdated
Refactor KyotoClient to use a KyotoClientHandle struct instead of
Box<LightClient>. Previously, the Box<LightClient> was consumed
when destructured in sync_kyoto_client, preventing the function
from being called multiple times or borrowed. With the handle
pattern, sync_kyoto_client now takes &mut KyotoClientHandle,
allowing it to be borrowed and reused.
This also allows the node to be started at creation time in
new_blockchain_client rather than during sync, making the client
ready for use immediately after creation.
- Add KyotoClientHandle struct containing requester and
update_subscriber
- Move node startup and logger spawning to new_blockchain_client
- Update sync_kyoto_client to accept &mut KyotoClientHandle
instead of consuming Box<LightClient>
@Mshehu5

Mshehu5 commented Feb 2, 2026

Copy link
Copy Markdown
ContributorAuthor

@tvpeter
Thank you for your review !
As stated in my previous comments I have already tested the changes on signet and faced no errors for both payjoin transaction and also normal transactions with cbf(kyoto)

notmandatory I made a Payjoin transaction on public signet i did not face any errors
payjoin transaction was sucessful below is my config and command that I used

the code has also been rebased on master which has the wallet configuration changes

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more suggestion. Please fix it so I can test.

Comment threadsrc/handlers.rs Outdated
Update function signatures to accept &BlockchainClient instead of taking
ownership. This refactoring to allow the client to be Referenced across
multiple operations including repeated calls to sync_kyoto_client.
- Update handle_online_wallet_subcommand signature
- Update all PayjoinManager methods to use &BlockchainClient
- Fix parameter dereferencing in full_scan calls
- Update all call sites to pass references
Replace single sync-and-check with periodic polling loop.
This allows multiple sync operations since sync_wallet now accepts
a reference to BlockchainClient, enabling proper long-running
monitoring instead of a one-time check.
Replace Error::Generic with specific Payjoin error variants using #[from] derives for applicable places, enabling automatic conversion and eliminating verbose .map_err() calls.

@tvpetertvpeter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mshehu5Mshehu5 mentioned this pull request Feb 16, 2026
4 tasks
@tvpetertvpeter moved this from In Progress to Ready to Review in BDK-CLIFeb 23, 2026
@notmandatory

notmandatory commented Feb 23, 2026

Copy link
Copy Markdown
Member

@Mshehu5 thanks for the detailed testing steps. I modified them a little since rebase that added new config feature:

cargo run --features cbf -- wallet --wallet signet_receiver_wallet \
config --ext-descriptor "$RECEIVER_EXT_DESC"\ 
--int-descriptor "$RECEIVER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_receiver_wallet sync
cargo run --features cbf -- wallet --wallet signet_sender_wallet \
config --ext-descriptor "$SENDER_EXT_DESC" \
--int-descriptor "$SENDER_INT_DESC" \
--database-type $DATABASE_TYPE \
--client-type $CLIENT_TYPE
cargo run --features cbf -- wallet --wallet signet_sender_wallet sync
cargo run --features cbf -- wallet \
--wallet signet_receiver_wallet \
receive_payjoin \
--amount 1000 \
--directory $PAYJOIN_DIRECTORY \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3<manually copy payjoin URI into PAYJOIN_URI>
cargo run --features cbf -- wallet \
--wallet signet_sender_wallet send_payjoin \
--uri $PAYJOIN_URI \
--ohttp_relay $OHTTP_RELAY \
--ohttp_relay $OHTTP_RELAY2 \
--ohttp_relay $OHTTP_RELAY3 \
--fee_rate 10

Confirmed Tx

@notmandatorynotmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 974c8d5

Great job! exciting to see this capability in bdk-cli.

@tvpeter
tvpeter merged commit 368b8b4 into bitcoindevkit:masterFeb 24, 2026
5 checks passed
@github-project-automationgithub-project-automationBot moved this from Ready to Review to Done in BDK-CLIFeb 24, 2026
@tvpetertvpeter added this to the CLI 3.0.0 milestone Feb 26, 2026
tvpeter added a commit that referenced this pull request Jul 2, 2026
2f14a36 Retry Payjoin requests with other relays (Mshehu5)
6fb0ecd Restrict payjoin inputs to confirmed UTXOs (Mshehu5)
262ce86 Add payjoin persistence test (Mshehu5)
e85a92c Prune stale payjoin sessions on DB open (Mshehu5)
181d132 chore(deps): Bump payjoin and enable reqwest rustls (Mshehu5)
b9ca88f Document payjoin recovery commands and SQLite setup (Mshehu5)
559ba76 Add commands to resume and view payjoin history (Mshehu5)
4f71144 Integrate payjoin session persistence into workflow (Mshehu5)
c2bdb3d Add SQLite backing store for payjoin sessions (Mshehu5)
Pull request description:
### Description
#230 needed to be merged for this to go through
Address #149 also follow up to #200
This PR adds persistance to existing async payjoin integration
This introduces neccessary database model and tables also add commad for resume to allow interrupted sessions to be continued also a particular session either send or receive.
A history commad to view payjoin history and status has been added
### Notes to the reviewers
Step to review this include making a payjoin transaction
Run a receiver to get a BIP21 URI then pass it to the sender as seen in docs https://github.com/bitcoindevkit/bdk-cli/blob/b9cf2acc5f10db46fa30777ff0910b8610a5fc33/README.md?plain=1#L121-L141
To test resumption, interrupt either side with Ctrl+C mid-session then run resume on that side to continue. A few scenarios worth covering: receiver resuming after interrupt and sender resuming after interrupt. Use history after each scenario to confirm the session state was persisted correctly.
docs for this can be seen in 7e4ffd1
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added docs for the new feature
Top commit has no ACKs.
Tree-SHA512: 1e8be24fbf7d894687389192381242cebafcaffd8601418e197cfb7942917cbcd5ae308ed5e9fcc644c1dacb78abed9aa23c49c5ff08151b4657956e89d14217
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants

@Mshehu5@coveralls@notmandatory@tvpeter