Skip to content
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

refactor(chain)!: Change tx_last_seen to Option<u64> #1416

Merged
merged 9 commits into from
Jul 2, 2024

Conversation

ValuedMammal
Copy link
Contributor

@ValuedMammal ValuedMammal commented Apr 30, 2024

The PR changes the type of last_seen to Option<u64> for txs member of TxGraph.

This fixes an issue where unbroadcast and otherwise non-canonical transactions were returned from methods list_chain_txs and Wallet::transactions because every new tx inserted had a last_seen of 0 making it appear unconfirmed.

fixes #1446
fixes #1396

Notes to the reviewers

Changelog notice

Changed

  • Member last_seen_unconfirmed of TxNode is changed to Option<u64>
  • Renamed TxGraph method list_chain_txs to list_canonical_txs
  • Changed Wallet::insert_tx to take a single tx: Transaction as parameter

Added

  • Add method txs_with_no_anchor_or_last_seen for TxGraph
  • Add method unbroadcast_transactions for Wallet

Checklists

All Submissions:

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

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@nondiremanuel nondiremanuel added this to the 1.0.0-alpha milestone May 7, 2024
@ValuedMammal ValuedMammal changed the title tx_graph: change last_seen to Option<u64> [chain] Change tx_last_seen to Option<u64> May 8, 2024
@ValuedMammal ValuedMammal marked this pull request as ready for review May 8, 2024 00:29
@storopoli
Copy link
Contributor

Isn't Default for Option<T> None? If yes, maybe we can simplify with ..Default::default()?

if let Some(seen_at) = tx_tmp.last_seen {
let _ = graph.insert_seen_at(tx.txid(), seen_at);
}
let _ = graph.insert_seen_at(tx.txid(), tx_tmp.last_seen.unwrap_or(0));
Copy link
Member

Choose a reason for hiding this comment

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

Why are we force-inserting a last_seen when the template last_seen is None?

Copy link
Contributor Author

@ValuedMammal ValuedMammal May 24, 2024

Choose a reason for hiding this comment

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

This was a fix for test_tx_graph_conflicts. Because we're interested in testing tx conflicts, we have to control for the fact that txs without a last_seen would now be filtered out, so we just assume all txs are canonical.

edit: An alternative is to patch up some of the scenarios in test_tx_graph_conflicts to ensure every tx template is given some value of last_seen. That might make things clearer.

@evanlinjin
Copy link
Member

@LLFourn's comment...

None would be in no ones mempool (not even yours). You can have a transaction that is valid but has never left application memory.

summarizes the main usecase of these changes.

With this context in mind, this PR description needs to highlight how we should handle non-broadcasted transactions in TxGraph. How should these non-broadcasted transactions influence methods filter_chain_txouts/filter_chain_unspents-esc methods.

How can the caller list these non-broadcasted transactions? What if the caller would like to include UTXOs of non-broadcasted transactions when listing their UTXO set? What if the caller would NOT like to do so? What if the caller would like to replace non-broadcasted transactions? Currently, we do transaction-precedence based on last_seen. Wouldn't we need something like this for non-broadcasted transactions too?

It seems that many things have not be thought through clearly. Let's have some more concrete discussion about how everything should look after these changes.

@ValuedMammal
Copy link
Contributor Author

While it's possible to store a transaction in TxGraph before broadcasting it, I hesitate to advertise this as a feature, because there's not an easy way to make any owned txouts available for coin selection. The design of TxGraph requires all txs have a canonical chain position in order to count toward the total balance. You can manipulate the graph into considering a transaction canonical, but without a more robust way to abandon a tx and restore an input's unspent status, the only way to revert it is to do a full wallet rescan.

As an aside, if you're trying to build a chain of dependent transactions without broadcasting them, it should be possible to bypass coin selection by adding any owned txouts as a foreign utxo to a new TxBuilder.

@ValuedMammal ValuedMammal marked this pull request as ready for review June 11, 2024 14:10
@ValuedMammal
Copy link
Contributor Author

This fixes an issue where unbroadcast and otherwise non-canonical transactions were returned from methods

How would a non-canonical tx be returned by the methods?

For example in test_list_owned_txouts tx3 is registered as spending tx2 at block 1 when tx3 doesn't confirm until block 2 and is never explicitly given a last_seen in the test. Does that seem unexpected?

since we'd be lacking context that should normally occur during
sync with a chain source. The logic for inserting a graph
anchor from a `ConfirmationTime` is moved to the wallet common
test module in order to simulate receiving new txs and
confirming them.
@evanlinjin
Copy link
Member

evanlinjin commented Jun 25, 2024

I feel like we should include a method for returning "unseen and unanchored" transactions. Not sure what to name it, maybe unseen_txs is enough? I guess this would be on TxGraph and re-exposed on Wallet. The intention is to have a method that can be helpful for determining which txs still need to be broadcasted.

@ValuedMammal ValuedMammal force-pushed the fix/lastseen-none branch 2 times, most recently from 92e9720 to 42fab2b Compare June 25, 2024 17:06
Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

ACK 42fab2b

This looks good and all comments addressed.

Copy link
Contributor

@LLFourn LLFourn left a comment

Choose a reason for hiding this comment

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

Thanks. I've made a few suggestions about how to clean up the internals and tests. API looks good.

@@ -110,7 +110,7 @@ use core::{
#[derive(Clone, Debug, PartialEq)]
pub struct TxGraph<A = ()> {
// all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, u64)>,
txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, Option<u64>)>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be in this tuple I think. last_seen should just been its own HashMap<Txid, u64> if the value would be None here it would just not have an entry in the map. This is more idomatic and easier to reason about imo.

crates/chain/src/tx_graph.rs Outdated Show resolved Hide resolved
crates/chain/tests/test_tx_graph.rs Outdated Show resolved Hide resolved
crates/wallet/src/wallet/mod.rs Outdated Show resolved Hide resolved
crates/chain/src/tx_graph.rs Outdated Show resolved Hide resolved
@ValuedMammal
Copy link
Contributor Author

I'm interested in implementing the suggestion to make last-seen a new field in TxGraph that is HashMap<Txid, u64> if it can be done in this PR cleanly

Also fixup `test_list_owned_txouts` to check that the right
outputs, utxos, and balance are returned at different local
chain heights.

This fixes an issue where unbroadcast and otherwise non-canonical
transactions were returned from methods `list_chain_txs` and
`Wallet::transactions` because every tx inserted had a last_seen
of 0 making it appear unconfirmed.

Note this commit changes the way `Balance` is represented due to
new logic in `try_get_chain_position` that no longer considers
txs with non-canonical anchors. Before this change, a tx anchored
to a block that is reorged out had a permanent effect on the
pending balance, and now only txs with a last_seen time or an
anchor confirmed in the best chain will return a `ChainPosition`.
Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

Re ACK af75817

@LLFourn (or anyone else) any other suggestions before we merge this one?

@notmandatory notmandatory dismissed LLFourn’s stale review July 2, 2024 21:43

Review comment was addressed.

@notmandatory notmandatory merged commit a112b4d into bitcoindevkit:master Jul 2, 2024
12 checks passed
@ValuedMammal ValuedMammal deleted the fix/lastseen-none branch July 12, 2024 22:55
@notmandatory notmandatory mentioned this pull request Jul 20, 2024
30 tasks
@notmandatory notmandatory changed the title [chain] Change tx_last_seen to Option<u64> refactor(chain)!: Change tx_last_seen to Option<u64> Jul 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api A breaking API change module-blockchain
Projects
Archived in project
6 participants