Add functions to test persistence#2012
Conversation
8f06dc6 to
247d478
Compare
247d478 to
83b54a6
Compare
|
I tried re-running CI to no avail, maybe try force pushing. |
83b54a6 to
51d148e
Compare
|
Great idea. ConceptACK |
f2623db to
b77a114
Compare
|
In 10c42dc I added a |
We could have the testenv default feature enable |
|
Should the miniscript feature introduced be removed? I was thinking if some users might not want bdk_chain/miniscript feature to be enabled? |
1eee95b to
fd9429c
Compare
|
|
|
|
|
I moved this to chain-0.24.0 milestone for visibility, @110CodingP let us know when it's ready for another round of review. |
c4b6873 to
d56cea9
Compare
6d1738b to
6f09bde
Compare
|
Since we use v13 of |
|
This PR will be important to have if we still want to do #1980 to help test persistence implementations. |
| fn txgraph_is_persisted() { | ||
| persist_txgraph_changeset::<rusqlite::Connection, _, _, _>( | ||
| "wallet.sqlite", | ||
| |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), |
There was a problem hiding this comment.
nit: Is there a reason you are using different connection types? from rusqlite::Connection and bdk_chain::rusqlite::Connection
| let temp_dir = tempfile::tempdir().expect("must create tempdir"); | ||
| let file_path = temp_dir.path().join(file_name); | ||
| let mut store = create_store(&file_path).expect("store should get created"); |
There was a problem hiding this comment.
nit: seemed like every fn creates the store, could you refactor it into a separate fn?
tvpeter
left a comment
There was a problem hiding this comment.
Hi @110CodingP , this is incredible work on a massive PR.
I have left some nit comments.
In addition to the comments, I think it will also be great if you could consider including test utils for handling edge cases/conflicts, especially in the persist_test_utils (if that will not make the PR too big)
| let mut tx_graph_changeset1 = ChangeSet::<ConfirmationBlockTime> { | ||
| txs: [tx1.clone()].into(), | ||
| txouts: [ | ||
| (OutPoint::new(hash!("BDK"), 0), create_txout(1300, ADDRS[1])), | ||
| ( | ||
| OutPoint::new(hash!("Bitcoin_fixes_things"), 0), | ||
| create_txout(1400, ADDRS[1]), | ||
| ), | ||
| ] | ||
| .into(), | ||
| anchors: [(conf_anchor, tx1.compute_txid())].into(), | ||
| last_seen: [(tx1.compute_txid(), 1755416650)].into(), | ||
| first_seen: [(tx1.compute_txid(), 1755416655)].into(), | ||
| last_evicted: [(tx1.compute_txid(), 1755416660)].into(), | ||
| }; |
There was a problem hiding this comment.
Here we are also testing the persistence of all the other changesets. Being such a high level test, Is it worth it to have the granularity we have, i.e., a test for each of the smaller changesets?
There was a problem hiding this comment.
I agree they are redundant. The reason I was doing it this way was that users will then be able to use these redundant tests as unit tests in case they have separate functions to persist each of the smaller changesets. I would be fine with removing them too if that increases maintainability.
| let tx2 = Arc::new(create_test_tx( | ||
| [tx1.compute_txid()], | ||
| [0], | ||
| [20_000], | ||
| [ADDRS[0]], | ||
| 1, | ||
| 0, | ||
| )); |
There was a problem hiding this comment.
https://github.com/rust-fuzz/arbitrary could help remove work here, at the same time is exposing the API for fuzzers and other tests.
| input: input_vec, | ||
| output: output_vec, | ||
| } | ||
| } |
There was a problem hiding this comment.
Again, https://github.com/rust-fuzz/arbitrary could help with this.
|
@tvpeter and @nymius thank you for the reviews! I will restructure the PR to address the concerns. Also looking at mammal's restructuring of the wallet tests.
|
There was a problem hiding this comment.
Concept ACK
Thanks for working on this. Having some sort of tooling to ensure persistence implementations is not only helpful, but also crucial if we ever want to do #1980.
Approach NACK
There is quite a lot of boilerplate happening here and lots of repeated code making this approach hard to read and hard to maintain. All the test methods essentially all do the same thing, but with different changeset types. As mentioned by @nymius here, we should really abstract this.
Proposal
We can represent all that we want to test with a single generic helper. Then just have predefined sets of changesets (C).
pub fn assert_persists<C, S, CREATE, LOAD, PERSIST>(
create: CREATE,
load: LOAD,
persist: PERSIST,
changesets: &[C],
) where
C: Default + Merge + PartialEq + Clone + core::fmt::Debug,
CREATE: Fn(&Path) -> anyhow::Result<S>,
LOAD: Fn(&mut S) -> anyhow::Result<C>,
PERSIST: Fn(&mut S, &C) -> anyhow::Result<()>,
{
// Persist one at a time, reload after each, asserting merged accumulation.
let dir = tempfile::tempdir().expect("tempdir");
let mut store = create(dir.path()).expect("interleaved: create store");
assert_eq!(
load(&mut store).expect("interleaved: load empty"),
C::default(),
);
let mut acc = C::default();
for cs in changesets {
persist(&mut store, cs).expect("interleaved: persist");
acc.merge(cs.clone());
assert_eq!(
load(&mut store).expect("interleaved: reload"),
acc,
);
}
// Persist all into a fresh store, reload once.
let dir = tempfile::tempdir().expect("tempdir");
let mut store = create(dir.path()).expect("batched: create store");
for cs in changesets {
persist(&mut store, cs).expect("batched: persist");
}
assert_eq!(
load(&mut store).expect("batched: reload"),
acc,
);
}Predefined test changesets via builders:
pub fn tx_graph_fixture() -> [tx_graph::ChangeSet<ConfirmationBlockTime>; 2] { … }
pub fn keychain_txout_fixture() -> [keychain_txout::ChangeSet; 2] { … }
pub fn local_chain_fixture() -> [local_chain::ChangeSet; 2] { … }For fuzzing, proptest, we can even create methods that generates changeset sets on the fly.
6f09bde to
0d0ac5b
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2012 +/- ##
==========================================
+ Coverage 76.63% 79.47% +2.83%
==========================================
Files 29 29
Lines 5705 5705
Branches 257 257
==========================================
+ Hits 4372 4534 +162
+ Misses 1263 1103 -160
+ Partials 70 68 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Added the following functions to test if persistence of `bdk_chain` is happening correctly. - `persist_txgraph_changeset` - `persist_indexer_changeset` - `persist_local_chain_changeset` which leverage a more general `persist_changeset`. In order to not panic and instead return errors raised by the persistence backend being tested, introduced `PersistErr`. To ensure downcasting and to allow usage of anyhow, `PersisterErr::Persister` requires an error of with 'static + Send + Sync.
0d0ac5b to
48912ee
Compare
|
Thanks a lot @evanlinjin for the review! Also for convenience the PR still contains changeset specific persistence tests ( I will try to leverage |
There was a problem hiding this comment.
There is a whole bunch of functions in this file that is only called once. Let's just inline it - this will improve readability and reduce lines of code.
| #[test] | ||
| fn txgraph_is_persisted() -> anyhow::Result<()> { | ||
| let temp_dir = tempfile::tempdir().unwrap(); | ||
| Ok(persist_txgraph_changeset::<rusqlite::Connection, _, _, _>( |
There was a problem hiding this comment.
Nit: The explicit turbofishing is not really needed for any of these functions.
| Ok(persist_txgraph_changeset::<rusqlite::Connection, _, _, _>( | |
| Ok(persist_txgraph_changeset( |
| Ok(persist_txgraph_changeset::< | ||
| Store<tx_graph::ChangeSet<ConfirmationBlockTime>>, | ||
| _, | ||
| _, | ||
| _, | ||
| >( |
There was a problem hiding this comment.
| Ok(persist_txgraph_changeset::< | |
| Store<tx_graph::ChangeSet<ConfirmationBlockTime>>, | |
| _, | |
| _, | |
| _, | |
| >( | |
| Ok(persist_txgraph_changeset( |
Turbofishing not necessary.
|
|
||
| [dev-dependencies] | ||
| tempfile = "3" | ||
| anyhow = { version = "1.0.102", default-features = false} |
There was a problem hiding this comment.
| anyhow = { version = "1.0.102", default-features = false} | |
| anyhow = { version = "1", default-features = false} |
No need to explicitly define the minor and patch versions.
| /// We create a dummy `ChangeSet`, persist it and check if loaded `ChangeSet` matches | ||
| /// the persisted one. We then create another such dummy `ChangeSet`, persist it and load it to | ||
| /// check if merged `ChangeSet` is returned. | ||
| pub fn persist_changeset<CS, Store, CreateStore, Initialize, Persist>( |
There was a problem hiding this comment.
persist_changeset reads like "perform persistence" but this is a persistence-assertion-helper.
| pub fn persist_changeset<CS, Store, CreateStore, Initialize, Persist>( | |
| pub fn assert_persist_changesets<CS, Store, CreateStore, Initialize, Persist>( |
|
|
||
| /// Get two [`keychain_txout::ChangeSet`](keychain_txout::ChangeSet)s. | ||
| #[cfg(feature = "miniscript")] | ||
| fn keychain_txout_changesets() -> [keychain_txout::ChangeSet; 2] { |
There was a problem hiding this comment.
Make these methods that return fixtures of changesets pub. Then we can remove the persist_xxx_changeset methods. Much less code to maintain.
|
@110CodingP How about we focus this PR on getting the API shape correct and introduce something like |
| pub enum PersistErr<C: Debug> { | ||
| /// ChangeSet Mismatch | ||
| ChangeSetMismatch { | ||
| /// the resulting changeset | ||
| got: Box<C>, | ||
| /// the expected changeset | ||
| expected: Box<C>, | ||
| }, | ||
| /// Errors thrown by underlying persistence backend. | ||
| Persister(Box<dyn Err + 'static + Send + Sync>), | ||
| } |
There was a problem hiding this comment.
This is overengineered. We don't need to programmatically inspect different failure types. These helpers are for tests - failure just needs to panic!/assert!.
assert_persist_changesets should not need a return type and just panic on failure.
Description
Added basic tests to the testenv for testing custom persistence backends. Partially solves bdk_wallet#14 and might help with bdk_wallet#234.
Notes to the reviewers
An alternative approach could be to create a struct whose methods are these tests but the current approach seems better since these tests are meant to be used as unit tests. For the same reason redundant functions to check persistence of individual fields of the
ChangeSets are provided.Changelog notice
Todo
Checklists
All Submissions:
New Features: