-
Notifications
You must be signed in to change notification settings - Fork 30
Introduce IntentTracker
#257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Introduce IntentTracker
#257
Conversation
a4fbb83
to
a9efbcc
Compare
Pull Request Test Coverage Report for Build 15943002368Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
a9efbcc
to
808bbdf
Compare
808bbdf
to
75bc892
Compare
If the caller wishes to avoid spending unbroadcasted UTXOs without double-spending any of the unbroadcasted transactions.
@@ -73,6 +73,8 @@ pub struct LocalOutput { | |||
pub derivation_index: u32, | |||
/// The position of the output in the blockchain. | |||
pub chain_position: ChainPosition<ConfirmationBlockTime>, | |||
/// Whether this output exists in a transaction that is yet to be broadcasted. | |||
pub needs_broadcast: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe an enum state
field with something like: UNSPENT
, ON_QUEUE
, SPENT
, BROADCASTED
will avoid keep adding new boolean fields here, and provide a better path for update on future occasions, taking advantage of non exhaustive patters. is_spent
could be marked for deprecation and be used along the new field in the meantime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea if done as a non-exhaustive enum to help reduce future API breaking changes. If we include a "LOCKED" variant could this also support #259?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I’m not convinced this feature is necessary, and it could lead to inconsistent behavior if callers sometimes use the
BroadcastQueue
, bypass it to broadcast transactions directly, or if multiple instances of the same wallet broadcast concurrently. In such cases—when intermediate transactions are missing from the queue—the logic described above will fail.
Can we enforce or support BroadcastQueue
as the only way to broadcast transactions in bdk_wallet
?
A user bypassing this mechanism should be considered? Are there reasons to not doing it?
Why would you keep broadcasting tx outside of the queue when you have an unbroadcasted tx in the queue?
I like the approach, and think is easy to reason about. Maybe we could leave the door open to implement other broadcast policies.
IMHO, the BroadcastQueue
"profile" should ensure internal consistency, so I don't think is over engineered.
Why would you need to RBF unbroadcasted transactions?
Not a use case that I've needed, but maybe share multiple conflicting transactions offline looking for fee optimization in different scenarios.
Concept ACK. I like the idea of the queue. I took a look at the diff and here are some thoughts/questions, pardon me if some of them would have been answered by doing a code deep dive, I just know you wanted early feedback so decided to get moving on it sooner than later.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this, took a first look.
I think I may have done some overthinking for the BroadcastQueue implementation. This is the current implementation:
Do we know how Core handles these things?
Also, when do we expect this queue to be processed? Would this happen manually or automatically in intervals?
wallet/src/wallet/broadcast_queue.rs
Outdated
queue: VecDeque<Txid>, | ||
|
||
/// Enforces that we do not have duplicates in `queue`. | ||
dedup: HashSet<Txid>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth having this separate set? How many unbroadcasted transactions are we expecting at any given time? Maybe it would just be quicker to simply iterate over the queue itself, also saving the heap allocations/memory footprint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good point. Maybe premature optimization here.
@@ -73,6 +73,8 @@ pub struct LocalOutput { | |||
pub derivation_index: u32, | |||
/// The position of the output in the blockchain. | |||
pub chain_position: ChainPosition<ConfirmationBlockTime>, | |||
/// Whether this output exists in a transaction that is yet to be broadcasted. | |||
pub needs_broadcast: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When do we expect this to be set/unset exactly? I guess it can only be unset once the transaction in question reaches threshold confirmations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good point, and it shows the limitations of the BroadcastQueue
concept. In fact, I did some further thinking on this and the BroadcastQueue
should really be an IntentTracker
and should track txs even if they are "network canonical".
There should be a method such as .tracked_txs_which_are_not_network_canonical
(better name needed) so that the caller can decide to either replace the tx, or explicitly forget about it. There are caveats to doing both since we don't want to create a sub-graph where intended payments are duplicated - BDK should handle these situations properly, or provide the required information so that the caller can make a safe decision.
@tnull do you mean how does the Core wallet handle un-broadcasted Tx and building new Tx on those un-broadcasted Tx outputs? As far as I know there are no features in the Core wallet for this beyond you the user holding on to your signed and un-broadcast Tx and manually building on those Tx outputs with the commands:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks like a powerful new feature, I only have minor comments. Once you feel the API is ready I'd like to have a live chat to review it with L2 users like @tnull and @stevenroose to validate it meets their use cases.
@@ -73,6 +73,8 @@ pub struct LocalOutput { | |||
pub derivation_index: u32, | |||
/// The position of the output in the blockchain. | |||
pub chain_position: ChainPosition<ConfirmationBlockTime>, | |||
/// Whether this output exists in a transaction that is yet to be broadcasted. | |||
pub needs_broadcast: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea if done as a non-exhaustive enum to help reduce future API breaking changes. If we include a "LOCKED" variant could this also support #259?
wallet/src/wallet/broadcast_queue.rs
Outdated
let tx = match tx_graph.get_tx(txid) { | ||
Some(tx) => tx, | ||
None => { | ||
debug_assert!( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to throw and Err
here instead of the panic? It seems possible a user could mistakenly try to queue a Txid
not in the tx_graph
. Or is this ment to warn app devs that they should never let this happen?
I also don't understand why you only panic if the txid
is not in the tx_graph
and not in the dedup set. Isn't not having the Txid
in the graph enough to panic due to it being invalid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this was never meant to be in the public API. The idea is that we should only add txids into the BroadcastQueue
which are also in TxGraph
. If that is not the case, it is definitely an internal BDK error.
@nymius I've rethought about this problem. I think instead of a
@thunderbiscuit I agree that it is nice to reason with. However, I think it will introduce some footguns. Let me provide an example:
Now A and B can exist in the same history, and thus we have the potential birth of a double-payment situation. My proposal right now, as mentioned above, is to have an The wallet will keep track of two consistent views of history:
If these two views are the same, no action is required. If these two views diverge, the caller should be able to easily respond to it explicitly.
|
When doing coin selection, we should use the "intent view" to obtain the UTXO set. This is to avoid accidentally double-spending intended-to-be-canonical transactions. However, some intended-to-be-canonical transactions could not be canonical now (due to confirmed conflicts), or conflicts with mempool transactions (RBF, which may not go through in time). So there should be some sort of filtering based on transactions in the
Of course, there are other filters that BDK does not do, but should really do (out of scope of this PR, but probably part of the same interface/structure):
|
Mh, right, regarding the UTXO locking usecase, it does feature a rather simple interface through |
Thanks for modeling this, from my perspective, it resembles to React DOM and virtual DOM, and its reconciliation model.
A quick check: when you say non-conflicting subgraphs, it is implied B is not spending any inputs from A, but is spending to the same outputs, right?
Do you have in mind some diff method between this
I'm confused here,
My guess is relevant transactions should be left out of the equation here. |
Fixes #166
Fixes #40
Replaces #220
Description
Allows callers to spend from unbroadcasted transactions.
Notes to the reviewers
I think I may have done some overthinking for the
BroadcastQueue
implementation. This is the current implementation:Wallet::add_tx_to_broadcast_queue
will also remove conflicts (of the tx being inserted) from the broadcast queue.Wallet::remove_tx_from_broadcast_queue
will also remove descendants of the tx being removed.However, I’m not convinced this feature is necessary, and it could lead to inconsistent behavior if callers sometimes use the
BroadcastQueue
, bypass it to broadcast transactions directly, or if multiple instances of the same wallet broadcast concurrently. In such cases—when intermediate transactions are missing from the queue—the logic described above will fail.There is an argument for RBF, however, why would you need to RBF unbroadcasted transactions? It's better to empty the queue and start again.
Changelog notice
Checklists
To Get Out of Draft Status:
Wallet
) docs that explains the broadcast queue.To Get This Merged:
All Submissions:
cargo +nightly fmt
andcargo clippy
before committingNew Features:
Bugfixes: