Refactor sync_kyoto_client
Description
This PR addresses issues encountered while implementing persistence for Payjoin specifically around the BlockchainClient being consumed rather than borrowed. 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 is consumed the current design effectively requires 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 consumption 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<LightClient> 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.
-
Instead of returning a Box<lightClient> the function now returns a KyotoClientHandle. Previously the boxed client was consumed when destructured inside sync_kyoto_client, preventing reuse. With the new design sync_kyoto_client takes &mut KyotoClientHandle, allowing the client to be Refrenced which can be used syncing and broadcasting transactions without consumption
-
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 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 \
sync
Check balance
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 \
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:
- [x] I've signed all my commits
- [x] I followed the contribution guidelines
- [x] I ran
cargo fmtandcargo clippybefore committing
Pull Request Test Coverage Report for Build 20762701892
Details
- 0 of 110 (0.0%) changed or added relevant lines in 3 files are covered.
- 3 unchanged lines in 3 files lost coverage.
- Overall coverage decreased (-0.007%) to 7.894%
| Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
|---|---|---|---|
| src/handlers.rs | 0 | 19 | 0.0% |
| src/utils.rs | 0 | 22 | 0.0% |
| src/payjoin/mod.rs | 0 | 69 | 0.0% |
| <!-- | Total: | 0 | 110 |
| Files with Coverage Reduction | New Missed Lines | % |
|---|---|---|
| src/handlers.rs | 1 | 15.3% |
| src/payjoin/mod.rs | 1 | 0.0% |
| src/utils.rs | 1 | 0.0% |
| <!-- | Total: | 3 |
| Totals | |
|---|---|
| Change from base Build 20714655583: | -0.007% |
| Covered Lines: | 172 |
| Relevant Lines: | 2179 |
💛 - Coveralls
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
My patch to add the --cbf-peer option is: https://github.com/notmandatory/bdk-cli/commit/71fbacf3d05c4f7268a6cc581cf2c3a9a15278b0
Otherwise this PR looks good to me, especially good commit messages and PR description.
My patch to add the
--cbf-peeroption 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: https://github.com/bitcoindevkit/bdk-cli/commit/97cb4775730ddccc589509b0a32c01d1866a1b11 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.