From c65eb0a3ca369483e0510a3f215ad416107996f1 Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Mon, 22 Apr 2024 21:53:29 -0400 Subject: [PATCH] database: implement `ops/` (#102) * ops: add `trait MoneroR{o,w}` * update `trait MoneroR{o,w}` bounds * types: add `BlockInfoLatest` type alias * block: impl most core functions * types: fix https://github.com/Cuprate/cuprate/pull/91#discussion_r1527668916 * fix table type test * cargo.toml: add `{cuprate-types, monero-serai}` * add_block: add all other block data * ops: remove unneeded `block` functions * env: add `EnvInner::open_db_rw_all()` * types: fix test * block: `&mut TxRw` -> `&TxRw`, use `open_db_rw_all()` * add `trait Tables[Mut]` and use it in `EnvInner` * block: use `TablesMut` * tables: replace manual impl with `define_trait_tables!()` * tables: docs for `trait Tables[Mut]` * tables: doc functions * create `call_fn_on_all_tables_or_early_return!()` macro * block: cleanup signatures + bodies * block: more fn's, docs * block: add `doc_{error,single,bulk}!()` * remove `ops/monero.rs` * move `height()` to `ops/blockchain.rs` * add `ops/macros.rs` * tx: add fn signatures * output: fix fn signatures * ops: expose `_inner()` functions * block: add `add_block_header{_bulk, _inner}()` * ops: remove doc_{fn,inner}!()` * ops: remove `_{inner,bulk}()`, lifetime + generics * update lib/mod docs * ops: add and use `doc_add_block_inner_invariant!()` * ops: add docs/return to inner `add_block()` functions * add_block(): extract and use fn for {key_image, output} * ops: more fn body impl + `add_block()` * cargo: add `monero-pruning` * ops: extract out `tx` functions from `add_block()` * property: add `db_version()` * ops: `pop_block()` body, remove other `pop_block` fn's * types: add `block_blob: Vec` to `VerifiedBlockInformation` * block: put `block_blob`, pass `Tables` to sub-functions `impl TablesMut` can't mutably pass multiple tables since it takes `&mut self`, so all functions unfortunately have to take a full `&mut impl TablesMut` even though they only need a few tables. * database: add `DatabaseRw::take()` useful for `pop_block()` where we need the value afterwards * block: deserialize tx's from `block_blobs` in `pop_block()` * blockchain: `height()` -> `chain_height()` * output: fix `amount_index` * ops: fix unlock_time, chain_height * `BlockInfoV{1,2,3}` -> `BlockInfo` * constants: add `DATABASE_VERSION` * database: add `DatabaseRw::update()` * output: use `DatabaseRw::update()` in `remove_output()` * add `TxBlobs` table, ignore pruning tables * block: mostly impl `add_block()` body * ops: comments * add_block: miner v2 tx commitment, height cast * block: impl `pop_block()` * block: mostly impl `get_block()` * block: impl `get_block_{from_height,header,header_from_height}` * add `OutputFlags` bitflags * add_block: u32::try_into(height: u32), use `OutputFlags` * tx: impl `get_{tx,tx_from_id}()` * tx: move docs tests to `#[test]` testing everything in 1 go is more natural since e.g: `add_tx()` is followed by `get_tx()` * tables: add `trait TablesIter`, `all_tables_empty()` This allows `TablesMut` to be a superset of `Tables` and use all its accessor functions. * use cuprate-test-utils, fix tx tests * block: `add_block()` take block by ref * tx: use all txs in tests * output: add `all_tx_functions()` test * add_block: check current height against input * block: map more fields in `get_block()` * block: remove `get_block()`, doc tests, fix `get_block_header()` * block: dummy values in test * heed: use `last/first()` instead of `unsafe` cursors We no longer have DUP semantics and also hard to debug errors were popping up on `del_current()`... * heed: fix `DatabaseRw::delete` Ok(true) means the key did not exist, so we must return Err(RuntimeError::KeyNotFound) * block: `add_block()` (dummy value) test * ops: `key_image` tests * cleanup, docs, tests * backend: test `take()` & `update()` * docs * remove `OutputFlags::NONE` * add_block(): add asserts, panic docs, `should_panic` tests * backend: remove `Ok(())` in `Database::delete` if already deleted redb already does this, so heed so match * block: move block operations after tx/outputs * `amount == 0` -> `amount == 1` * Nit: StorableVec::wrap_ref * `saturating_sub(1)` -> `- 1` * add `TxOutputs` table * add_block(): add to `tx_outputs` table * fix `DatabaseRo::update` * add_tx(): take `block_height` as input * tx: add/remove from `TxOutputs` table * output: remove if `amount == 1` -> `amount_index == 0` * output: fix `add_output()`'s `amount_index` calculation * output: fix `add_output()`'s `amount_index` calculation again * output: tests for `amount_index/num_outputs` * block: `num_outputs - 1` and `take()` -> `get()` We don't need to `take()` since the call afterwards to `remove_output()` removes the entry * block: swap `get_block_extended_header[_from_height]()` * move `{key_image,output}` handling `add_block()` -> `add_tx()` * blockchain: add doc to `top_block_height()` * block: manual panic -> `assert_eq!()` * test-utils: add `block_blob` to `VerifiedBlockInformation` field introduced in this PR * ops: use real block/tx data in tests * block: `total_generated_coins` -> `cumulative_generated_coins` * fix clippy, docs, TODOs * `cumulative_generated_coins()`: `block/` -> `blockchain/` * blockchain: add `cumulative_generated_coins()` tests * Update database/src/ops/block.rs Co-authored-by: Boog900 * `cumulative_generated_coins()` docs for pre-block-0 special case --------- Co-authored-by: Boog900 --- Cargo.lock | 532 +++++++++++++++----------- database/Cargo.toml | 22 +- database/src/backend/heed/database.rs | 61 +-- database/src/backend/heed/env.rs | 12 +- database/src/backend/redb/database.rs | 9 + database/src/backend/redb/env.rs | 12 +- database/src/backend/tests.rs | 112 +++--- database/src/constants.rs | 12 + database/src/database.rs | 42 +- database/src/env.rs | 25 ++ database/src/lib.rs | 34 +- database/src/ops/block.rs | 483 ++++++++++++++++++++--- database/src/ops/blockchain.rs | 175 ++++++++- database/src/ops/key_image.rs | 152 ++++++++ database/src/ops/macros.rs | 33 ++ database/src/ops/mod.rs | 46 ++- database/src/ops/output.rs | 282 +++++++++++++- database/src/ops/property.rs | 52 ++- database/src/ops/spent_key.rs | 19 - database/src/ops/tx.rs | 466 ++++++++++++++++++++-- database/src/tables.rs | 302 +++++++++++++-- database/src/tests.rs | 45 +++ database/src/types.rs | 153 +++----- test-utils/src/data/free.rs | 24 +- test-utils/src/rpc/client.rs | 8 +- types/src/types.rs | 4 + 26 files changed, 2480 insertions(+), 637 deletions(-) create mode 100644 database/src/ops/key_image.rs create mode 100644 database/src/ops/macros.rs delete mode 100644 database/src/ops/spent_key.rs create mode 100644 database/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 64fb9d65..56bfa311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -42,9 +42,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -122,26 +122,26 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -168,6 +168,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -206,10 +212,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ + "bytemuck", "serde", ] @@ -245,9 +252,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" dependencies = [ "borsh-derive", "cfg_aliases", @@ -255,42 +262,42 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "syn_derive", ] [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -301,9 +308,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -328,11 +335,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.88" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ + "jobserver", "libc", + "once_cell", ] [[package]] @@ -349,14 +358,14 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -371,9 +380,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -381,9 +390,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -393,14 +402,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -577,7 +586,7 @@ dependencies = [ "rayon", "serde", "serde_json", - "syn 2.0.52", + "syn 2.0.60", "thiserror", "thread_local", "tokio", @@ -591,16 +600,24 @@ dependencies = [ name = "cuprate-database" version = "0.0.0" dependencies = [ + "bitflags 2.5.0", "bytemuck", "bytes", "cfg-if", "crossbeam", "cuprate-helper", + "cuprate-test-utils", "cuprate-types", + "curve25519-dalek", "futures", "heed", + "hex", + "hex-literal", + "monero-pruning", + "monero-serai", "page_size", "paste", + "pretty_assertions", "rayon", "redb", "serde", @@ -688,7 +705,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -716,6 +733,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -787,15 +810,15 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -852,9 +875,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "ff" @@ -869,9 +892,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "filetime" @@ -996,7 +1019,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1041,9 +1064,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -1078,8 +1101,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.11", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1118,13 +1141,19 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "heed" version = "0.20.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9648a50991c86df7d00c56c268c27754fcf4c80be2ba57fc4a00dc928c6fe934" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytemuck", "byteorder", "heed-traits", @@ -1186,9 +1215,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1197,9 +1226,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1213,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] @@ -1224,18 +1253,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.0.0", + "http 1.1.0", ] [[package]] name = "http-body-util" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "futures-util", - "http 1.0.0", + "futures-core", + "http 1.1.0", "http-body 1.0.0", "pin-project-lite", ] @@ -1263,7 +1292,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.6", "httparse", "httpdate", @@ -1278,14 +1307,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "httparse", "itoa", @@ -1302,8 +1331,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 1.0.0", - "hyper 1.2.0", + "http 1.1.0", + "hyper 1.3.1", "hyper-util", "rustls", "rustls-native-certs", @@ -1335,9 +1364,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", - "hyper 1.2.0", + "hyper 1.3.1", "pin-project-lite", "socket2", "tokio", @@ -1391,9 +1420,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1416,15 +1445,24 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +dependencies = [ + "libc", +] [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1448,7 +1486,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "levin-cuprate" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures", "proptest", @@ -1473,13 +1511,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -1528,9 +1565,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "merlin" @@ -1577,7 +1614,7 @@ dependencies = [ "borsh", "cuprate-test-utils", "futures", - "indexmap 2.2.5", + "indexmap 2.2.6", "monero-p2p", "monero-pruning", "monero-wire", @@ -1787,7 +1824,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -1804,7 +1841,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1815,9 +1852,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1964,7 +2001,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1978,29 +2015,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2016,9 +2053,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "powerfmt" @@ -2032,6 +2069,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -2066,9 +2113,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -2081,7 +2128,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", @@ -2112,9 +2159,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2186,9 +2233,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2206,9 +2253,9 @@ dependencies = [ [[package]] name = "redb" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1100a056c5dcdd4e5513d5333385223b26ef1bf92f31eb38f407e8c20549256" +checksum = "ed7508e692a49b6b2290b56540384ccae9b1fb4d77065640b165835b56ffe3bb" dependencies = [ "libc", ] @@ -2224,9 +2271,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -2250,28 +2297,28 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", "hyper-tls", @@ -2331,11 +2378,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2362,7 +2409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", @@ -2374,24 +2421,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.0", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -2406,9 +2453,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rusty-fork" @@ -2449,17 +2496,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2470,9 +2517,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -2486,29 +2533,29 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -2570,9 +2617,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2583,7 +2630,7 @@ version = "0.1.0" source = "git+https://github.com/Cuprate/serai.git?rev=347d4cf#347d4cf4135c92bc5b0a3e3cb66fa3ff51b1c629" dependencies = [ "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "tokio", @@ -2607,9 +2654,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -2638,9 +2685,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -2661,9 +2708,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -2679,7 +2726,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -2749,22 +2796,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -2779,9 +2826,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "num-conv", @@ -2822,9 +2869,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -2847,7 +2894,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -2873,9 +2920,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2913,7 +2960,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -2971,7 +3018,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -3115,9 +3162,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3125,24 +3172,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3152,9 +3199,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3162,28 +3209,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3213,12 +3260,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.54.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" dependencies = [ - "windows-core 0.54.0", - "windows-targets 0.52.4", + "windows-core 0.56.0", + "windows-targets 0.52.5", ] [[package]] @@ -3227,26 +3274,50 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] name = "windows-core" -version = "0.54.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ + "windows-implement", + "windows-interface", "windows-result", - "windows-targets 0.52.4", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] name = "windows-result" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3264,7 +3335,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3284,17 +3355,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -3305,9 +3377,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3317,9 +3389,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3329,9 +3401,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -3341,9 +3419,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3353,9 +3431,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3365,9 +3443,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3377,9 +3455,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -3420,6 +3498,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" @@ -3437,7 +3521,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -3457,7 +3541,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -3501,9 +3585,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/database/Cargo.toml b/database/Cargo.toml index 646b76bf..5d7bb8b7 100644 --- a/database/Cargo.toml +++ b/database/Cargo.toml @@ -9,26 +9,30 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/database" keywords = ["cuprate", "database"] [features] -# default = ["heed", "redb", "service"] +default = ["heed", "redb", "service"] # default = ["redb", "service"] -default = ["redb-memory", "service"] +# default = ["redb-memory", "service"] heed = ["dep:heed"] redb = ["dep:redb"] redb-memory = ["redb"] service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"] [dependencies] +bitflags = { workspace = true, features = ["serde", "bytemuck"] } bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } bytes = { workspace = true } cfg-if = { workspace = true } # FIXME: # We only need the `thread` feature if `service` is enabled. # Figure out how to enable features of an already pulled in dependency conditionally. -cuprate-helper = { path = "../helper", features = ["fs", "thread"] } -cuprate-types = { path = "../types", features = ["service"] } -paste = { workspace = true } -page_size = { version = "0.6.0" } # Needed for database resizes, they must be a multiple of the OS page size. -thiserror = { workspace = true } +cuprate-helper = { path = "../helper", features = ["fs", "thread"] } +cuprate-types = { path = "../types", features = ["service"] } +curve25519-dalek = { workspace = true } +monero-pruning = { path = "../pruning" } +monero-serai = { workspace = true, features = ["std"] } +paste = { workspace = true } +page_size = { version = "0.6.0" } # Needed for database resizes, they must be a multiple of the OS page size. +thiserror = { workspace = true } # `service` feature. crossbeam = { workspace = true, features = ["std"], optional = true } @@ -46,5 +50,9 @@ serde = { workspace = true, optional = true } [dev-dependencies] bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } cuprate-helper = { path = "../helper", features = ["thread"] } +cuprate-test-utils = { path = "../test-utils" } page_size = { version = "0.6.0" } tempfile = { version = "3.10.0" } +pretty_assertions = { version = "1.4.0" } +hex = { workspace = true } +hex-literal = { workspace = true } \ No newline at end of file diff --git a/database/src/backend/heed/database.rs b/database/src/backend/heed/database.rs index 9b3745c5..01d544de 100644 --- a/database/src/backend/heed/database.rs +++ b/database/src/backend/heed/database.rs @@ -204,55 +204,56 @@ impl DatabaseRw for HeedTableRw<'_, '_, T> { Ok(()) } + #[inline] + fn take(&mut self, key: &T::Key) -> Result { + // LMDB/heed does not return the value on deletion. + // So, fetch it first - then delete. + let value = get::(&self.db, &self.tx_rw.borrow(), key)?; + match self.db.delete(&mut self.tx_rw.borrow_mut(), key) { + Ok(true) => Ok(value), + Err(e) => Err(e.into()), + // We just `get()`'ed the value - it is + // incorrect for it to suddenly not exist. + Ok(false) => unreachable!(), + } + } + #[inline] fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { let tx_rw = &mut self.tx_rw.borrow_mut(); - // Get the first value first... - let Some(first) = self.db.first(tx_rw)? else { + // Get the value first... + let Some((key, value)) = self.db.first(tx_rw)? else { return Err(RuntimeError::KeyNotFound); }; // ...then remove it. - // - // We use an iterator because we want to semantically - // remove the _first_ and only the first `(key, value)`. - // `delete()` removes all keys including duplicates which - // is slightly different behavior. - let mut iter = self.db.iter_mut(tx_rw)?; - - // SAFETY: - // It is undefined behavior to keep a reference of - // a value from this database while modifying it. - // We are deleting the value and never accessing - // the iterator again so this should be safe. - unsafe { - iter.del_current()?; + match self.db.delete(tx_rw, &key) { + Ok(true) => Ok((key, value)), + Err(e) => Err(e.into()), + // We just `get()`'ed the value - it is + // incorrect for it to suddenly not exist. + Ok(false) => unreachable!(), } - - Ok(first) } #[inline] fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { let tx_rw = &mut self.tx_rw.borrow_mut(); - let Some(first) = self.db.last(tx_rw)? else { + // Get the value first... + let Some((key, value)) = self.db.last(tx_rw)? else { return Err(RuntimeError::KeyNotFound); }; - let mut iter = self.db.rev_iter_mut(tx_rw)?; - - // SAFETY: - // It is undefined behavior to keep a reference of - // a value from this database while modifying it. - // We are deleting the value and never accessing - // the iterator again so this should be safe. - unsafe { - iter.del_current()?; + // ...then remove it. + match self.db.delete(tx_rw, &key) { + Ok(true) => Ok((key, value)), + Err(e) => Err(e.into()), + // We just `get()`'ed the value - it is + // incorrect for it to suddenly not exist. + Ok(false) => unreachable!(), } - - Ok(first) } } diff --git a/database/src/backend/heed/env.rs b/database/src/backend/heed/env.rs index 9362cd72..56ece8f3 100644 --- a/database/src/backend/heed/env.rs +++ b/database/src/backend/heed/env.rs @@ -207,17 +207,15 @@ impl Env for ConcreteEnv { } use crate::tables::{ - BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages, - NumOutputs, Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs, - TxHeights, TxIds, TxUnlockTime, + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxBlobs, TxHeights, TxIds, TxOutputs, + TxUnlockTime, }; let mut tx_rw = env.write_txn()?; create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; - create_table::(&env, &mut tx_rw)?; - create_table::(&env, &mut tx_rw)?; - create_table::(&env, &mut tx_rw)?; + create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; @@ -225,8 +223,10 @@ impl Env for ConcreteEnv { create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; + create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; + create_table::(&env, &mut tx_rw)?; create_table::(&env, &mut tx_rw)?; // TODO: Set dupsort and comparison functions for certain tables diff --git a/database/src/backend/redb/database.rs b/database/src/backend/redb/database.rs index 43f1a3ac..bcfb2be5 100644 --- a/database/src/backend/redb/database.rs +++ b/database/src/backend/redb/database.rs @@ -188,6 +188,15 @@ impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { Ok(()) } + #[inline] + fn take(&mut self, key: &T::Key) -> Result { + if let Some(value) = redb::Table::remove(self, key)? { + Ok(value.value()) + } else { + Err(RuntimeError::KeyNotFound) + } + } + #[inline] fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { let (key, value) = redb::Table::pop_first(self)?.ok_or(RuntimeError::KeyNotFound)?; diff --git a/database/src/backend/redb/env.rs b/database/src/backend/redb/env.rs index 6525de62..bbbb6f3c 100644 --- a/database/src/backend/redb/env.rs +++ b/database/src/backend/redb/env.rs @@ -109,17 +109,15 @@ impl Env for ConcreteEnv { } use crate::tables::{ - BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages, - NumOutputs, Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs, - TxHeights, TxIds, TxUnlockTime, + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxBlobs, TxHeights, TxIds, TxOutputs, + TxUnlockTime, }; let tx_rw = env.begin_write()?; create_table::(&tx_rw)?; create_table::(&tx_rw)?; - create_table::(&tx_rw)?; - create_table::(&tx_rw)?; - create_table::(&tx_rw)?; + create_table::(&tx_rw)?; create_table::(&tx_rw)?; create_table::(&tx_rw)?; create_table::(&tx_rw)?; @@ -127,8 +125,10 @@ impl Env for ConcreteEnv { create_table::(&tx_rw)?; create_table::(&tx_rw)?; create_table::(&tx_rw)?; + create_table::(&tx_rw)?; create_table::(&tx_rw)?; create_table::(&tx_rw)?; + create_table::(&tx_rw)?; create_table::(&tx_rw)?; tx_rw.commit()?; diff --git a/database/src/backend/tests.rs b/database/src/backend/tests.rs index 50f92644..9d05872b 100644 --- a/database/src/backend/tests.rs +++ b/database/src/backend/tests.rs @@ -31,32 +31,21 @@ use crate::{ storable::StorableVec, table::Table, tables::{ - BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages, NumOutputs, - Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxHeights, TxIds, + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxBlobs, TxHeights, TxIds, TxOutputs, TxUnlockTime, }, + tests::tmp_concrete_env, transaction::{TxRo, TxRw}, types::{ - Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfoV1, - BlockInfoV2, BlockInfoV3, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash, - PrunedBlob, RctOutput, TxHash, TxId, UnlockTime, + Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage, + Output, OutputFlags, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput, + TxBlob, TxHash, TxId, UnlockTime, }, ConcreteEnv, }; //---------------------------------------------------------------------------------------------------- Tests -/// Create an `Env` in a temporarily directory. -/// The directory is automatically removed after the `TempDir` is dropped. -/// -/// TODO: changing this to `-> impl Env` causes lifetime errors... -fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) { - let tempdir = tempfile::tempdir().unwrap(); - let config = Config::low_power(Some(tempdir.path().into())); - let env = ConcreteEnv::open(config).unwrap(); - - (env, tempdir) -} - /// Simply call [`Env::open`]. If this fails, something is really wrong. #[test] fn open() { @@ -87,9 +76,7 @@ fn open_db() { // This should be updated when tables are modified. env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); - env_inner.open_db_ro::(&tx_ro).unwrap(); - env_inner.open_db_ro::(&tx_ro).unwrap(); - env_inner.open_db_ro::(&tx_ro).unwrap(); + env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); @@ -97,17 +84,17 @@ fn open_db() { env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); + env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); + env_inner.open_db_ro::(&tx_ro).unwrap(); env_inner.open_db_ro::(&tx_ro).unwrap(); TxRo::commit(tx_ro).unwrap(); // Open all tables in read/write mode. env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); - env_inner.open_db_rw::(&tx_rw).unwrap(); - env_inner.open_db_rw::(&tx_rw).unwrap(); - env_inner.open_db_rw::(&tx_rw).unwrap(); + env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); @@ -115,8 +102,10 @@ fn open_db() { env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); + env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); + env_inner.open_db_rw::(&tx_rw).unwrap(); env_inner.open_db_rw::(&tx_rw).unwrap(); TxRw::commit(tx_rw).unwrap(); } @@ -182,7 +171,7 @@ fn db_read_write() { const VALUE: Output = Output { key: [35; 32], height: 45_761_798, - output_flags: 0, + output_flags: OutputFlags::empty(), tx_idx: 2_353_487, }; /// How many `(key, value)` pairs will be inserted. @@ -271,6 +260,22 @@ fn db_read_write() { } } + // Assert `update()` works. + { + const HEIGHT: u32 = 999; + + assert_ne!(table.get(&KEY).unwrap().height, HEIGHT); + + table + .update(&KEY, |mut value| { + value.height = HEIGHT; + Some(value) + }) + .unwrap(); + + assert_eq!(table.get(&KEY).unwrap().height, HEIGHT); + } + // Assert deleting works. { table.delete(&KEY).unwrap(); @@ -284,6 +289,23 @@ fn db_read_write() { assert_same(value); } + // Assert `take()` works. + { + let mut key = KEY; + key.amount += 1; + let value = table.take(&key).unwrap(); + assert_eq!(value, VALUE); + + let get = table.get(&KEY); + assert!(!table.contains(&key).unwrap()); + assert!(matches!(get, Err(RuntimeError::KeyNotFound))); + + // Assert the other `(key, value)` pairs are still there. + key.amount += 1; + let value = table.get(&key).unwrap(); + assert_same(value); + } + drop(table); TxRw::commit(tx_rw).unwrap(); @@ -406,36 +428,14 @@ test_tables! { BlockHash => BlockHeight, [32; 32] => 123, - BlockInfoV1s, - BlockHeight => BlockInfoV1, - 123 => BlockInfoV1 { + BlockInfos, + BlockHeight => BlockInfo, + 123 => BlockInfo { timestamp: 1, - total_generated_coins: 123, + cumulative_generated_coins: 123, weight: 321, cumulative_difficulty: 111, block_hash: [54; 32], - }, - - BlockInfoV2s, - BlockHeight => BlockInfoV2, - 123 => BlockInfoV2 { - timestamp: 1, - total_generated_coins: 123, - weight: 321, - cumulative_difficulty: 111, - cumulative_rct_outs: 2389, - block_hash: [54; 32], - }, - - BlockInfoV3s, - BlockHeight => BlockInfoV3, - 123 => BlockInfoV3 { - timestamp: 1, - total_generated_coins: 123, - weight: 321, - cumulative_difficulty_low: 111, - cumulative_difficulty_high: 112, - block_hash: [54; 32], cumulative_rct_outs: 2389, long_term_weight: 2389, }, @@ -448,6 +448,10 @@ test_tables! { Amount => AmountIndex, 123 => 123, + TxBlobs, + TxId => TxBlob, + 123 => StorableVec(vec![1,2,3,4,5,6,7,8]), + TxIds, TxHash => TxId, [32; 32] => 123, @@ -456,6 +460,10 @@ test_tables! { TxId => BlockHeight, 123 => 123, + TxOutputs, + TxId => AmountIndices, + 123 => StorableVec(vec![1,2,3,4,5,6,7,8]), + TxUnlockTime, TxId => UnlockTime, 123 => 123, @@ -468,7 +476,7 @@ test_tables! { } => Output { key: [1; 32], height: 1, - output_flags: 0, + output_flags: OutputFlags::empty(), tx_idx: 3, }, @@ -489,7 +497,7 @@ test_tables! { 123 => RctOutput { key: [1; 32], height: 1, - output_flags: 0, + output_flags: OutputFlags::empty(), tx_idx: 3, commitment: [3; 32], }, diff --git a/database/src/constants.rs b/database/src/constants.rs index 37a3efaf..a5d810dc 100644 --- a/database/src/constants.rs +++ b/database/src/constants.rs @@ -3,6 +3,18 @@ //---------------------------------------------------------------------------------------------------- Import use cfg_if::cfg_if; +//---------------------------------------------------------------------------------------------------- Version +/// Current major version of the database. +/// +/// Returned by [`crate::ops::property::db_version`]. +/// +/// This is incremented by 1 when `cuprate_database`'s +/// structure/schema/tables change. +/// +/// This is akin to `VERSION` in `monerod`: +/// +pub const DATABASE_VERSION: u64 = 0; + //---------------------------------------------------------------------------------------------------- Error Messages /// Corrupt database error message. /// diff --git a/database/src/database.rs b/database/src/database.rs index df62414b..2ec3d328 100644 --- a/database/src/database.rs +++ b/database/src/database.rs @@ -13,7 +13,7 @@ use crate::{ transaction::{TxRo, TxRw}, }; -//---------------------------------------------------------------------------------------------------- DatabaseRoIter +//---------------------------------------------------------------------------------------------------- DatabaseIter /// Database (key-value store) read-only iteration abstraction. /// /// These are read-only iteration-related operations that @@ -140,14 +140,50 @@ pub trait DatabaseRw: DatabaseRo { /// This will overwrite any existing key-value pairs. /// /// # Errors - /// This will not return [`RuntimeError::KeyExists`]. + /// This will never [`RuntimeError::KeyExists`]. fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError>; /// Delete a key-value pair in the database. /// + /// This will return `Ok(())` if the key does not exist. + /// + /// # Errors + /// This will never [`RuntimeError::KeyNotFound`]. + fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>; + + /// Delete and return a key-value pair in the database. + /// + /// This is the same as [`DatabaseRw::delete`], however, + /// it will serialize the `T::Value` and return it. + /// /// # Errors /// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist. - fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>; + fn take(&mut self, key: &T::Key) -> Result; + + /// Fetch the value, and apply a function to it - or delete the entry. + /// + /// This will call [`DatabaseRo::get`] and call your provided function `f` on it. + /// + /// The [`Option`] `f` returns will dictate whether `update()`: + /// - Updates the current value OR + /// - Deletes the `(key, value)` pair + /// + /// - If `f` returns `Some(value)`, that will be [`DatabaseRw::put`] as the new value + /// - If `f` returns `None`, the entry will be [`DatabaseRw::delete`]d + /// + /// # Errors + /// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist. + fn update(&mut self, key: &T::Key, mut f: F) -> Result<(), RuntimeError> + where + F: FnMut(T::Value) -> Option, + { + let value = DatabaseRo::get(self, key)?; + + match f(value) { + Some(value) => DatabaseRw::put(self, key, &value), + None => DatabaseRw::delete(self, key), + } + } /// TODO /// diff --git a/database/src/env.rs b/database/src/env.rs index 4109d889..d21595c5 100644 --- a/database/src/env.rs +++ b/database/src/env.rs @@ -9,6 +9,11 @@ use crate::{ error::{InitError, RuntimeError}, resize::ResizeAlgorithm, table::Table, + tables::{ + call_fn_on_all_tables_or_early_return, BlockBlobs, BlockHeights, BlockInfos, KeyImages, + NumOutputs, Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, + TablesMut, TxHeights, TxIds, TxUnlockTime, + }, transaction::{TxRo, TxRw}, }; @@ -224,6 +229,26 @@ where /// a table doesn't exist. fn open_db_rw(&self, tx_rw: &Rw) -> Result, RuntimeError>; + /// TODO + /// + /// # Errors + /// TODO + fn open_tables(&self, tx_ro: &Ro) -> Result { + call_fn_on_all_tables_or_early_return! { + Self::open_db_ro(self, tx_ro) + } + } + + /// TODO + /// + /// # Errors + /// TODO + fn open_tables_mut(&self, tx_rw: &Rw) -> Result { + call_fn_on_all_tables_or_early_return! { + Self::open_db_rw(self, tx_rw) + } + } + /// Clear all `(key, value)`'s from a database table. /// /// This will delete all key and values in the passed diff --git a/database/src/lib.rs b/database/src/lib.rs index d9f0181f..ce7df283 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -1,4 +1,4 @@ -//! Database abstraction and utilities. +//! Cuprate's database abstraction. //! //! This documentation is mostly for practical usage of `cuprate_database`. //! @@ -8,28 +8,33 @@ //! # Purpose //! This crate does 3 things: //! 1. Abstracts various database backends with traits -//! 2. Implements various `Monero` related [functions](ops) & [tables] & [types] +//! 2. Implements various `Monero` related [operations](ops), [tables], and [types] //! 3. Exposes a [`tower::Service`] backed by a thread-pool //! +//! Each layer builds on-top of the previous. +//! +//! As a user of `cuprate_database`, consider using the higher-level [`service`], +//! or at the very least [`ops`] instead of interacting with the database traits directly. +//! +//! With that said, many database traits and internals (like [`DatabaseRo::get`]) are exposed. +//! //! # Terminology //! To be more clear on some terms used in this crate: //! -//! | Term | Meaning | -//! |---------------|--------------------------------------| -//! | `Env` | The 1 database environment, the "whole" thing -//! | `DatabaseRo` | A read-only `key/value` store -//! | `DatabaseRw` | A readable/writable `key/value` store -//! | `Table` | Solely the metadata of a `Database` (the `key` and `value` types, and the name) -//! | `TxRo` | Read only transaction -//! | `TxRw` | Read/write transaction -//! | `Storable` | A data that type can be stored in the database +//! | Term | Meaning | +//! |------------------|--------------------------------------| +//! | `Env` | The 1 database environment, the "whole" thing +//! | `DatabaseR{o,w}` | A _actively open_ readable/writable `key/value` store +//! | `Table` | Solely the metadata of a `Database` (the `key` and `value` types, and the name) +//! | `TxR{o,w}` | A read/write transaction +//! | `Storable` | A data that type can be stored in the database //! //! The dataflow is `Env` -> `Tx` -> `Database` //! //! Which reads as: //! 1. You have a database `Environment` //! 1. You open up a `Transaction` -//! 1. You get a particular `Database` from that `Environment` +//! 1. You open a particular `Table` from that `Environment`, getting a `Database` //! 1. You can now read/write data from/to that `Database` //! //! # `ConcreteEnv` @@ -138,7 +143,6 @@ unconditional_recursion, for_loops_over_fallibles, unused_braces, - unused_doc_comments, unused_labels, keyword_idents, non_ascii_idents, @@ -169,6 +173,7 @@ clippy::pedantic, clippy::nursery, clippy::cargo, + unused_doc_comments, unused_mut, missing_docs, deprecated, @@ -221,6 +226,7 @@ pub mod config; mod constants; pub use constants::{ DATABASE_BACKEND, DATABASE_CORRUPT_MSG, DATABASE_DATA_FILENAME, DATABASE_LOCK_FILENAME, + DATABASE_VERSION, }; mod database; @@ -261,3 +267,5 @@ pub use transaction::{TxRo, TxRw}; pub mod service; //---------------------------------------------------------------------------------------------------- Private +#[cfg(test)] +pub(crate) mod tests; diff --git a/database/src/ops/block.rs b/database/src/ops/block.rs index 361b391b..8cfadc3f 100644 --- a/database/src/ops/block.rs +++ b/database/src/ops/block.rs @@ -1,89 +1,450 @@ //! Blocks. //---------------------------------------------------------------------------------------------------- Import +use std::sync::Arc; -//---------------------------------------------------------------------------------------------------- Free Functions -/// TODO -pub fn add_block() { - todo!() +use bytemuck::TransparentWrapper; +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar}; +use monero_serai::{ + block::Block, + transaction::{Input, Timelock, Transaction}, +}; + +use cuprate_types::{ExtendedBlockHeader, TransactionVerificationData, VerifiedBlockInformation}; + +use crate::{ + database::{DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::{ + blockchain::{chain_height, cumulative_generated_coins}, + key_image::{add_key_image, remove_key_image}, + macros::doc_error, + output::{ + add_output, add_rct_output, get_rct_num_outputs, remove_output, remove_rct_output, + }, + tx::{add_tx, get_num_tx, remove_tx}, + }, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{ + AmountIndex, BlockHash, BlockHeight, BlockInfo, KeyImage, Output, OutputFlags, + PreRctOutputId, RctOutput, TxHash, + }, + StorableVec, +}; + +//---------------------------------------------------------------------------------------------------- `add_block_*` +/// Add a [`VerifiedBlockInformation`] to the database. +/// +/// This extracts all the data from the input block and +/// maps/adds them to the appropriate database tables. +/// +#[doc = doc_error!()] +/// +/// # Panics +/// This function will panic if: +/// - `block.height > u32::MAX` (not normally possible) +/// - `block.height` is not != [`chain_height`] +/// +/// # Already exists +/// This function will operate normally even if `block` already +/// exists, i.e., this function will not return `Err` even if you +/// call this function infinitely with the same block. +// no inline, too big. +pub fn add_block( + block: &VerifiedBlockInformation, + tables: &mut impl TablesMut, +) -> Result<(), RuntimeError> { + //------------------------------------------------------ Check preconditions first + + // Cast height to `u32` for storage (handled at top of function). + // Panic (should never happen) instead of allowing DB corruption. + // + let Ok(height) = u32::try_from(block.height) else { + panic!("block.height ({}) > u32::MAX", block.height); + }; + + let chain_height = chain_height(tables.block_heights())?; + assert_eq!( + block.height, chain_height, + "block.height ({}) != chain_height ({})", + block.height, chain_height, + ); + + // Expensive checks - debug only. + #[cfg(debug_assertions)] + { + assert_eq!(block.block.serialize(), block.block_blob); + assert_eq!(block.block.txs.len(), block.txs.len()); + for (i, tx) in block.txs.iter().enumerate() { + assert_eq!(tx.tx_blob, tx.tx.serialize()); + assert_eq!(tx.tx_hash, block.block.txs[i]); + } + } + + //------------------------------------------------------ Transaction / Outputs / Key Images + for tx_verification_data in &block.txs { + add_tx(tx_verification_data, &chain_height, tables)?; + } + + //------------------------------------------------------ Block Info + + // INVARIANT: must be below the above transaction loop since this + // RCT output count needs account for _this_ block's outputs. + let cumulative_rct_outs = get_rct_num_outputs(tables.rct_outputs())?; + + let cumulative_generated_coins = + cumulative_generated_coins(&block.height.saturating_sub(1), tables.block_infos())? + + block.generated_coins; + + // Block Info. + tables.block_infos_mut().put( + &block.height, + &BlockInfo { + cumulative_generated_coins, + cumulative_rct_outs, + timestamp: block.block.header.timestamp, + cumulative_difficulty: block.cumulative_difficulty, + block_hash: block.block_hash, + // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` + weight: block.weight as u64, + long_term_weight: block.long_term_weight as u64, + }, + )?; + + // Block blobs. + tables + .block_blobs_mut() + .put(&block.height, StorableVec::wrap_ref(&block.block_blob))?; + + // Block heights. + tables + .block_heights_mut() + .put(&block.block_hash, &block.height)?; + + Ok(()) } -/// TODO -pub fn add_block_data() { - todo!() +//---------------------------------------------------------------------------------------------------- `pop_block` +/// Remove the top/latest block from the database. +/// +/// The removed block's height and hash are returned. +#[doc = doc_error!()] +// no inline, too big +pub fn pop_block( + tables: &mut impl TablesMut, +) -> Result<(BlockHeight, BlockHash, Block), RuntimeError> { + //------------------------------------------------------ Block Info + // Remove block data from tables. + let (block_height, block_hash) = { + let (block_height, block_info) = tables.block_infos_mut().pop_last()?; + (block_height, block_info.block_hash) + }; + + // Block heights. + tables.block_heights_mut().delete(&block_hash)?; + + // Block blobs. + // We deserialize the block blob into a `Block`, such + // that we can remove the associated transactions later. + let block_blob = tables.block_blobs_mut().take(&block_height)?.0; + let block = Block::read(&mut block_blob.as_slice())?; + + //------------------------------------------------------ Transaction / Outputs / Key Images + for tx_hash in &block.txs { + remove_tx(tx_hash, tables)?; + } + + Ok((block_height, block_hash, block)) } -/// TODO -pub fn pop_block() { - todo!() +//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*` +/// Retrieve a [`ExtendedBlockHeader`] from the database. +/// +/// This extracts all the data from the database tables +/// needed to create a full `ExtendedBlockHeader`. +/// +/// # Notes +/// This is slightly more expensive than [`get_block_extended_header_from_height`] +/// (1 more database lookup). +#[doc = doc_error!()] +#[inline] +pub fn get_block_extended_header( + block_hash: &BlockHash, + tables: &impl Tables, +) -> Result { + get_block_extended_header_from_height(&tables.block_heights().get(block_hash)?, tables) } -/// TODO -pub fn block_exists() { - todo!() +/// Same as [`get_block_extended_header`] but with a [`BlockHeight`]. +#[doc = doc_error!()] +#[inline] +pub fn get_block_extended_header_from_height( + block_height: &BlockHeight, + tables: &impl Tables, +) -> Result { + let block_info = tables.block_infos().get(block_height)?; + let block_blob = tables.block_blobs().get(block_height)?.0; + let block = Block::read(&mut block_blob.as_slice())?; + + // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` + #[allow(clippy::cast_possible_truncation)] + Ok(ExtendedBlockHeader { + version: block.header.major_version, + vote: block.header.minor_version, + timestamp: block.header.timestamp, + cumulative_difficulty: block_info.cumulative_difficulty, + block_weight: block_info.weight as usize, + long_term_weight: block_info.long_term_weight as usize, + }) } -/// TODO -pub fn get_block_hash() { - todo!() +/// Return the top/latest [`ExtendedBlockHeader`] from the database. +#[doc = doc_error!()] +#[inline] +pub fn get_block_extended_header_top( + tables: &impl Tables, +) -> Result<(ExtendedBlockHeader, BlockHeight), RuntimeError> { + let height = chain_height(tables.block_heights())?.saturating_sub(1); + let header = get_block_extended_header_from_height(&height, tables)?; + Ok((header, height)) } -/// TODO -pub fn get_block_height() { - todo!() +//---------------------------------------------------------------------------------------------------- Misc +/// Retrieve a [`BlockHeight`] via its [`BlockHash`]. +#[doc = doc_error!()] +#[inline] +pub fn get_block_height( + block_hash: &BlockHash, + table_block_heights: &impl DatabaseRo, +) -> Result { + table_block_heights.get(block_hash) } -/// TODO -pub fn get_block_weight() { - todo!() +/// Check if a block exists in the database. +#[doc = doc_error!()] +#[inline] +pub fn block_exists( + block_hash: &BlockHash, + table_block_heights: &impl DatabaseRo, +) -> Result { + table_block_heights.contains(block_hash) } -/// TODO -pub fn get_block_already_generated_coins() { - todo!() -} +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +#[allow( + clippy::significant_drop_tightening, + clippy::cognitive_complexity, + clippy::too_many_lines +)] +mod test { + use hex_literal::hex; + use pretty_assertions::assert_eq; -/// TODO -pub fn get_block_long_term_weight() { - todo!() -} + use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v2_rct3}; -/// TODO -pub fn get_block_timestamp() { - todo!() -} + use super::*; + use crate::{ + ops::tx::{get_tx, tx_exists}, + tests::{assert_all_tables_are_empty, tmp_concrete_env}, + Env, + }; -/// TODO -pub fn get_block_cumulative_rct_outputs() { - todo!() -} + /// Tests all above block functions. + /// + /// Note that this doesn't test the correctness of values added, as the + /// functions have a pre-condition that the caller handles this. + /// + /// It simply tests if the proper tables are mutated, and if the data + /// stored and retrieved is the same. + #[test] + fn all_block_functions() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); -/// TODO -pub fn get_block() { - todo!() -} + let mut blocks = [ + block_v1_tx2().clone(), + block_v9_tx3().clone(), + block_v16_tx0().clone(), + ]; + // HACK: `add_block()` asserts blocks with non-sequential heights + // cannot be added, to get around this, manually edit the block height. + for (height, block) in blocks.iter_mut().enumerate() { + block.height = height as u64; + assert_eq!(block.block.serialize(), block.block_blob); + } + let generated_coins_sum = blocks + .iter() + .map(|block| block.generated_coins) + .sum::(); -/// TODO -pub fn get_block_from_height() { - todo!() -} + // Add blocks. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); -/// TODO -pub fn get_block_header() { - todo!() -} + for block in &blocks { + // println!("add_block: {block:#?}"); + add_block(block, &mut tables).unwrap(); + } -/// TODO -pub fn get_block_header_from_height() { - todo!() -} + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } -/// TODO -pub fn get_top_block() { - todo!() -} + // Assert all reads are OK. + let block_hashes = { + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); -/// TODO -pub fn get_top_block_hash() { - todo!() + // Assert only the proper tables were added to. + assert_eq!(tables.block_infos().len().unwrap(), 3); + assert_eq!(tables.block_blobs().len().unwrap(), 3); + assert_eq!(tables.block_heights().len().unwrap(), 3); + assert_eq!(tables.key_images().len().unwrap(), 69); + assert_eq!(tables.num_outputs().len().unwrap(), 38); + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.outputs().len().unwrap(), 107); + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 6); + assert_eq!(tables.tx_blobs().len().unwrap(), 5); + assert_eq!(tables.tx_ids().len().unwrap(), 5); + assert_eq!(tables.tx_heights().len().unwrap(), 5); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 0); + + // Check `cumulative` functions work. + assert_eq!( + cumulative_generated_coins(&2, tables.block_infos()).unwrap(), + generated_coins_sum, + ); + + // Both height and hash should result in getting the same data. + let mut block_hashes = vec![]; + for block in &blocks { + println!("blocks.iter(): hash: {}", hex::encode(block.block_hash)); + + let height = get_block_height(&block.block_hash, tables.block_heights()).unwrap(); + + println!("blocks.iter(): height: {height}"); + + assert!(block_exists(&block.block_hash, tables.block_heights()).unwrap()); + + let block_header_from_height = + get_block_extended_header_from_height(&height, &tables).unwrap(); + let block_header_from_hash = + get_block_extended_header(&block.block_hash, &tables).unwrap(); + + // Just an alias, these names are long. + let b1 = block_header_from_hash; + let b2 = block; + assert_eq!(b1, block_header_from_height); + assert_eq!(b1.version, b2.block.header.major_version); + assert_eq!(b1.vote, b2.block.header.minor_version); + assert_eq!(b1.timestamp, b2.block.header.timestamp); + assert_eq!(b1.cumulative_difficulty, b2.cumulative_difficulty); + assert_eq!(b1.block_weight, b2.weight); + assert_eq!(b1.long_term_weight, b2.long_term_weight); + + block_hashes.push(block.block_hash); + + // Assert transaction reads are OK. + for (i, tx) in block.txs.iter().enumerate() { + println!("tx_hash: {:?}", hex::encode(tx.tx_hash)); + + assert!(tx_exists(&tx.tx_hash, tables.tx_ids()).unwrap()); + + let tx2 = get_tx(&tx.tx_hash, tables.tx_ids(), tables.tx_blobs()).unwrap(); + + assert_eq!(tx.tx_blob, tx2.serialize()); + assert_eq!(tx.tx_weight, tx2.weight()); + assert_eq!(tx.tx_hash, block.block.txs[i]); + assert_eq!(tx.tx_hash, tx2.hash()); + } + } + + block_hashes + }; + + { + let len = block_hashes.len(); + let hashes: Vec = block_hashes.iter().map(hex::encode).collect(); + println!("block_hashes: len: {len}, hashes: {hashes:?}"); + } + + // Remove the blocks. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + for block_hash in block_hashes.into_iter().rev() { + println!("pop_block(): block_hash: {}", hex::encode(block_hash)); + + let (popped_height, popped_hash, popped_block) = pop_block(&mut tables).unwrap(); + + assert_eq!(block_hash, popped_hash); + + assert!(matches!( + get_block_extended_header(&block_hash, &tables), + Err(RuntimeError::KeyNotFound) + )); + } + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + assert_all_tables_are_empty(&env); + } + + /// We should panic if: `block.height` > `u32::MAX` + #[test] + #[should_panic(expected = "block.height (4294967296) > u32::MAX")] + fn block_height_gt_u32_max() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + let mut block = block_v9_tx3().clone(); + + block.height = u64::from(u32::MAX) + 1; + add_block(&block, &mut tables).unwrap(); + } + + /// We should panic if: `block.height` != the chain height + #[test] + #[should_panic( + expected = "assertion `left == right` failed: block.height (123) != chain_height (1)\n left: 123\n right: 1" + )] + fn block_height_not_chain_height() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + let mut block = block_v9_tx3().clone(); + // HACK: `add_block()` asserts blocks with non-sequential heights + // cannot be added, to get around this, manually edit the block height. + block.height = 0; + + // OK, `0 == 0` + assert_eq!(block.height, 0); + add_block(&block, &mut tables).unwrap(); + + // FAIL, `123 != 1` + block.height = 123; + add_block(&block, &mut tables).unwrap(); + } } diff --git a/database/src/ops/blockchain.rs b/database/src/ops/blockchain.rs index 28c2284c..b94cea05 100644 --- a/database/src/ops/blockchain.rs +++ b/database/src/ops/blockchain.rs @@ -1,9 +1,178 @@ //! Blockchain. //---------------------------------------------------------------------------------------------------- Import +use monero_serai::transaction::Timelock; + +use cuprate_types::VerifiedBlockInformation; + +use crate::{ + database::{DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::macros::doc_error, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{BlockHash, BlockHeight, BlockInfo, KeyImage, Output, PreRctOutputId, RctOutput}, +}; //---------------------------------------------------------------------------------------------------- Free Functions -/// TODO -pub fn height() { - todo!() +/// Retrieve the height of the chain. +/// +/// This returns the chain-tip, not the [`top_block_height`]. +/// +/// For example: +/// - The blockchain has 0 blocks => this returns `0` +/// - The blockchain has 1 block (height 0) => this returns `1` +/// - The blockchain has 2 blocks (height 1) => this returns `2` +/// +/// So the height of a new block would be `chain_height()`. +#[doc = doc_error!()] +#[inline] +pub fn chain_height( + table_block_heights: &impl DatabaseRo, +) -> Result { + table_block_heights.len() +} + +/// Retrieve the height of the top block. +/// +/// This returns the height of the top block, not the [`chain_height`]. +/// +/// For example: +/// - The blockchain has 0 blocks => this returns `Err(RuntimeError::KeyNotFound)` +/// - The blockchain has 1 block (height 0) => this returns `Ok(0)` +/// - The blockchain has 2 blocks (height 1) => this returns `Ok(1)` +/// +/// Note that in cases where no blocks have been written to the +/// database yet, an error is returned: `Err(RuntimeError::KeyNotFound)`. +/// +#[doc = doc_error!()] +#[inline] +pub fn top_block_height( + table_block_heights: &impl DatabaseRo, +) -> Result { + match table_block_heights.len()? { + 0 => Err(RuntimeError::KeyNotFound), + height => Ok(height - 1), + } +} + +/// Check how many cumulative generated coins there have been until a certain [`BlockHeight`]. +/// +/// This returns the total amount of Monero generated up to `block_height` +/// (including the block itself) in atomic units. +/// +/// For example: +/// - on the genesis block `0`, this returns the amount block `0` generated +/// - on the next block `1`, this returns the amount block `0` and `1` generated +/// +/// If no blocks have been added and `block_height == 0` +/// (i.e., the cumulative generated coins before genesis block is being calculated), +/// this returns `Ok(0)`. +#[doc = doc_error!()] +#[inline] +pub fn cumulative_generated_coins( + block_height: &BlockHeight, + table_block_infos: &impl DatabaseRo, +) -> Result { + match table_block_infos.get(block_height) { + Ok(block_info) => Ok(block_info.cumulative_generated_coins), + Err(RuntimeError::KeyNotFound) if block_height == &0 => Ok(0), + Err(e) => Err(e), + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +#[allow(clippy::significant_drop_tightening)] +mod test { + use hex_literal::hex; + use pretty_assertions::assert_eq; + + use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v2_rct3}; + + use super::*; + use crate::{ + ops::{ + block::add_block, + tx::{get_tx, tx_exists}, + }, + tests::{assert_all_tables_are_empty, tmp_concrete_env}, + Env, + }; + + /// Tests all above functions. + /// + /// Note that this doesn't test the correctness of values added, as the + /// functions have a pre-condition that the caller handles this. + /// + /// It simply tests if the proper tables are mutated, and if the data + /// stored and retrieved is the same. + #[test] + #[allow(clippy::cognitive_complexity, clippy::cast_possible_truncation)] + fn all_blockchain_functions() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let mut blocks = [ + block_v1_tx2().clone(), + block_v9_tx3().clone(), + block_v16_tx0().clone(), + ]; + let blocks_len = u64::try_from(blocks.len()).unwrap(); + + // Add blocks. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + assert!(matches!( + top_block_height(tables.block_heights()), + Err(RuntimeError::KeyNotFound), + )); + assert_eq!( + 0, + cumulative_generated_coins(&0, tables.block_infos()).unwrap() + ); + + for (i, block) in blocks.iter_mut().enumerate() { + let i = u64::try_from(i).unwrap(); + // HACK: `add_block()` asserts blocks with non-sequential heights + // cannot be added, to get around this, manually edit the block height. + block.height = i; + add_block(block, &mut tables).unwrap(); + } + + // Assert reads are correct. + assert_eq!(blocks_len, chain_height(tables.block_heights()).unwrap()); + assert_eq!( + blocks_len - 1, + top_block_height(tables.block_heights()).unwrap() + ); + assert_eq!( + cumulative_generated_coins(&0, tables.block_infos()).unwrap(), + 13_138_270_467_918, + ); + assert_eq!( + cumulative_generated_coins(&1, tables.block_infos()).unwrap(), + 16_542_044_490_081, + ); + assert_eq!( + cumulative_generated_coins(&2, tables.block_infos()).unwrap(), + 17_142_044_490_081, + ); + assert!(matches!( + cumulative_generated_coins(&3, tables.block_infos()), + Err(RuntimeError::KeyNotFound), + )); + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + } } diff --git a/database/src/ops/key_image.rs b/database/src/ops/key_image.rs new file mode 100644 index 00000000..1fa00ea4 --- /dev/null +++ b/database/src/ops/key_image.rs @@ -0,0 +1,152 @@ +//! Spent keys. + +//---------------------------------------------------------------------------------------------------- Import +use monero_serai::transaction::{Timelock, Transaction}; + +use cuprate_types::{OutputOnChain, VerifiedBlockInformation}; + +use crate::{ + database::{DatabaseIter, DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::macros::{doc_add_block_inner_invariant, doc_error}, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{ + BlockHash, BlockHeight, BlockInfo, KeyImage, Output, PreRctOutputId, RctOutput, TxHash, + }, +}; + +//---------------------------------------------------------------------------------------------------- Key image functions +/// Add a [`KeyImage`] to the "spent" set in the database. +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn add_key_image( + key_image: &KeyImage, + table_key_images: &mut impl DatabaseRw, +) -> Result<(), RuntimeError> { + table_key_images.put(key_image, &()) +} + +/// Remove a [`KeyImage`] from the "spent" set in the database. +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn remove_key_image( + key_image: &KeyImage, + table_key_images: &mut impl DatabaseRw, +) -> Result<(), RuntimeError> { + table_key_images.delete(key_image) +} + +/// Check if a [`KeyImage`] exists - i.e. if it is "spent". +#[doc = doc_error!()] +#[inline] +pub fn key_image_exists( + key_image: &KeyImage, + table_key_images: &impl DatabaseRo, +) -> Result { + table_key_images.contains(key_image) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +#[allow(clippy::significant_drop_tightening, clippy::cognitive_complexity)] +mod test { + use hex_literal::hex; + use pretty_assertions::assert_eq; + + use super::*; + use crate::{ + ops::tx::{get_tx, tx_exists}, + tests::{assert_all_tables_are_empty, tmp_concrete_env}, + Env, + }; + + /// Tests all above key-image functions. + /// + /// Note that this doesn't test the correctness of values added, as the + /// functions have a pre-condition that the caller handles this. + /// + /// It simply tests if the proper tables are mutated, and if the data + /// stored and retrieved is the same. + #[test] + fn all_key_image_functions() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let key_images = [ + hex!("be1c87fc8f958f68fbe346a18dfb314204dca7573f61aae14840b8037da5c286"), + hex!("c5e4a592c11f34a12e13516ab2883b7c580d47b286b8fe8b15d57d2a18ade275"), + hex!("93288b646f858edfb0997ae08d7c76f4599b04c127f108e8e69a0696ae7ba334"), + hex!("726e9e3d8f826d24811183f94ff53aeba766c9efe6274eb80806f69b06bfa3fc"), + ]; + + // Add. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + for key_image in &key_images { + println!("add_key_image(): {}", hex::encode(key_image)); + add_key_image(key_image, tables.key_images_mut()).unwrap(); + } + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + // Assert all reads are OK. + { + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + + // Assert only the proper tables were added to. + assert_eq!( + tables.key_images().len().unwrap(), + u64::try_from(key_images.len()).unwrap() + ); + assert_eq!(tables.block_infos().len().unwrap(), 0); + assert_eq!(tables.block_blobs().len().unwrap(), 0); + assert_eq!(tables.block_heights().len().unwrap(), 0); + assert_eq!(tables.num_outputs().len().unwrap(), 0); + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.outputs().len().unwrap(), 0); + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 0); + assert_eq!(tables.tx_blobs().len().unwrap(), 0); + assert_eq!(tables.tx_ids().len().unwrap(), 0); + assert_eq!(tables.tx_heights().len().unwrap(), 0); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 0); + + for key_image in &key_images { + println!("key_image_exists(): {}", hex::encode(key_image)); + key_image_exists(key_image, tables.key_images()).unwrap(); + } + } + + // Remove. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + for key_image in key_images { + println!("remove_key_image(): {}", hex::encode(key_image)); + remove_key_image(&key_image, tables.key_images_mut()).unwrap(); + assert!(!key_image_exists(&key_image, tables.key_images()).unwrap()); + } + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + assert_all_tables_are_empty(&env); + } +} diff --git a/database/src/ops/macros.rs b/database/src/ops/macros.rs new file mode 100644 index 00000000..6a219e6d --- /dev/null +++ b/database/src/ops/macros.rs @@ -0,0 +1,33 @@ +//! Macros. +//! +//! These generate repetitive documentation +//! for all the functions defined in `ops/`. + +//---------------------------------------------------------------------------------------------------- Documentation macros +/// Generate documentation for the required `# Error` section. +macro_rules! doc_error { + () => { + r#"# Errors +This function returns [`RuntimeError::KeyNotFound`] if the input doesn't exist or other `RuntimeError`'s on database errors."# + }; +} +pub(super) use doc_error; + +/// Generate `# Invariant` documentation for internal `fn`'s +/// that should be called directly with caution. +macro_rules! doc_add_block_inner_invariant { + () => { + r#"# ⚠️ Invariant ⚠️ +This function mainly exists to be used internally by the parent function [`crate::ops::block::add_block`]. + +`add_block()` makes sure all data related to the input is mutated, while +this function _does not_, it specifically mutates _particular_ tables. + +This is usually undesired - although this function is still available to call directly. + +When calling this function, ensure that either: +1. This effect (incomplete database mutation) is what is desired, or that... +2. ...the other tables will also be mutated to a correct state"# + }; +} +pub(super) use doc_add_block_inner_invariant; diff --git a/database/src/ops/mod.rs b/database/src/ops/mod.rs index c4468e92..658be45f 100644 --- a/database/src/ops/mod.rs +++ b/database/src/ops/mod.rs @@ -4,18 +4,48 @@ //! traits in this crate to generically call Monero-related //! database operations. //! -//! # TODO -//! TODO: These functions should pretty much map 1-1 to the `Request` enum. +//! # `impl Table` +//! `ops/` functions take [`Tables`](crate::tables::Tables) and +//! [`TablesMut`](crate::tables::TablesMut) directly - these are +//! _already opened_ database tables. //! -//! TODO: These are function names from `old_database/` for now. -//! The actual underlying functions (e.g `get()`) aren't implemented. +//! As such, the function puts the responsibility +//! of transactions, tables, etc on the caller. //! -//! TODO: All of these functions need to take in generic -//! database trait parameters (and their actual inputs). +//! This does mean these functions are mostly as lean +//! as possible, so calling them in a loop should be okay. +//! +//! # Atomicity +//! As transactions are handled by the _caller_ of these functions, +//! it is up to the caller to decide what happens if one them return +//! an error. +//! +//! To maintain atomicity, transactions should be [`abort`](crate::transaction::TxRw::abort)ed +//! if one of the functions failed. +//! +//! For example, if [`add_block()`](block::add_block) is called and returns an [`Err`], +//! `abort`ing the transaction that opened the input `TableMut` would reverse all tables +//! mutated by `add_block()` up until the error, leaving it in the state it was in before +//! `add_block()` was called. +//! +//! # Sub-functions +//! The main functions within this module are mostly within the [`block`] module. +//! +//! Practically speaking, you should only be using 2 functions: +//! - [`add_block`](block::add_block) +//! - [`pop_block`](block::pop_block) +//! +//! The `block` functions are "parent" functions, calling other +//! sub-functions such as [`add_output()`](output::add_output). `add_output()` +//! itself only modifies output-related tables, while the `block` "parent" functions +//! (like `add_block` and `pop_block`) modify _everything_ that is required. -pub mod alt_block; +// pub mod alt_block; // TODO: is this needed? pub mod block; +pub mod blockchain; +pub mod key_image; pub mod output; pub mod property; -pub mod spent_key; pub mod tx; + +mod macros; diff --git a/database/src/ops/output.rs b/database/src/ops/output.rs index e0db143f..b2c615f7 100644 --- a/database/src/ops/output.rs +++ b/database/src/ops/output.rs @@ -1,34 +1,278 @@ //! Outputs. //---------------------------------------------------------------------------------------------------- Import +use monero_serai::transaction::{Timelock, Transaction}; -//---------------------------------------------------------------------------------------------------- Free Functions -/// TODO -pub fn add_output() { - todo!() +use cuprate_types::{OutputOnChain, VerifiedBlockInformation}; + +use crate::{ + database::{DatabaseIter, DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::macros::{doc_add_block_inner_invariant, doc_error}, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{ + Amount, AmountIndex, BlockHash, BlockHeight, BlockInfo, KeyImage, Output, PreRctOutputId, + RctOutput, TxHash, + }, +}; + +//---------------------------------------------------------------------------------------------------- Pre-RCT Outputs +/// Add a Pre-RCT [`Output`] to the database. +/// +/// Upon [`Ok`], this function returns the [`PreRctOutputId`] that +/// can be used to lookup the `Output` in [`get_output()`]. +/// +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn add_output( + amount: Amount, + output: &Output, + tables: &mut impl TablesMut, +) -> Result { + // FIXME: this would be much better expressed with a + // `btree_map::Entry`-like API, fix `trait DatabaseRw`. + let num_outputs = match tables.num_outputs().get(&amount) { + // Entry with `amount` already exists. + Ok(num_outputs) => num_outputs, + // Entry with `amount` didn't exist, this is + // the 1st output with this amount. + Err(RuntimeError::KeyNotFound) => 0, + Err(e) => return Err(e), + }; + // Update the amount of outputs. + tables.num_outputs_mut().put(&amount, &(num_outputs + 1))?; + + let pre_rct_output_id = PreRctOutputId { + amount, + // The new `amount_index` is the length of amount of outputs with same amount. + amount_index: num_outputs, + }; + + tables.outputs_mut().put(&pre_rct_output_id, output)?; + Ok(pre_rct_output_id) } -/// TODO -pub fn remove_output() { - todo!() +/// Remove a Pre-RCT [`Output`] from the database. +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn remove_output( + pre_rct_output_id: &PreRctOutputId, + tables: &mut impl TablesMut, +) -> Result<(), RuntimeError> { + // Decrement the amount index by 1, or delete the entry out-right. + // FIXME: this would be much better expressed with a + // `btree_map::Entry`-like API, fix `trait DatabaseRw`. + tables + .num_outputs_mut() + .update(&pre_rct_output_id.amount, |num_outputs| { + // INVARIANT: Should never be 0. + if num_outputs == 1 { + None + } else { + Some(num_outputs - 1) + } + })?; + + // Delete the output data itself. + tables.outputs_mut().delete(pre_rct_output_id) } -/// TODO -pub fn get_output() { - todo!() +/// Retrieve a Pre-RCT [`Output`] from the database. +#[doc = doc_error!()] +#[inline] +pub fn get_output( + pre_rct_output_id: &PreRctOutputId, + table_outputs: &impl DatabaseRo, +) -> Result { + table_outputs.get(pre_rct_output_id) } -/// TODO -pub fn get_output_list() { - todo!() +/// How many pre-RCT [`Output`]s are there? +/// +/// This returns the amount of pre-RCT outputs currently stored. +#[doc = doc_error!()] +#[inline] +pub fn get_num_outputs(table_outputs: &impl DatabaseRo) -> Result { + table_outputs.len() } -/// TODO -pub fn get_rct_num_outputs() { - todo!() +//---------------------------------------------------------------------------------------------------- RCT Outputs +/// Add an [`RctOutput`] to the database. +/// +/// Upon [`Ok`], this function returns the [`AmountIndex`] that +/// can be used to lookup the `RctOutput` in [`get_rct_output()`]. +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn add_rct_output( + rct_output: &RctOutput, + table_rct_outputs: &mut impl DatabaseRw, +) -> Result { + let amount_index = get_rct_num_outputs(table_rct_outputs)?; + table_rct_outputs.put(&amount_index, rct_output)?; + Ok(amount_index) } -/// TODO -pub fn get_pre_rct_num_outputs() { - todo!() +/// Remove an [`RctOutput`] from the database. +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +#[inline] +pub fn remove_rct_output( + amount_index: &AmountIndex, + table_rct_outputs: &mut impl DatabaseRw, +) -> Result<(), RuntimeError> { + table_rct_outputs.delete(amount_index) +} + +/// Retrieve an [`RctOutput`] from the database. +#[doc = doc_error!()] +#[inline] +pub fn get_rct_output( + amount_index: &AmountIndex, + table_rct_outputs: &impl DatabaseRo, +) -> Result { + table_rct_outputs.get(amount_index) +} + +/// How many [`RctOutput`]s are there? +/// +/// This returns the amount of RCT outputs currently stored. +#[doc = doc_error!()] +#[inline] +pub fn get_rct_num_outputs( + table_rct_outputs: &impl DatabaseRo, +) -> Result { + table_rct_outputs.len() +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +#[allow(clippy::significant_drop_tightening, clippy::cognitive_complexity)] +mod test { + use super::*; + use crate::{ + tests::{assert_all_tables_are_empty, tmp_concrete_env}, + types::OutputFlags, + Env, + }; + use cuprate_test_utils::data::{tx_v1_sig2, tx_v2_rct3}; + use pretty_assertions::assert_eq; + + /// Dummy `Output`. + const OUTPUT: Output = Output { + key: [44; 32], + height: 0, + output_flags: OutputFlags::NON_ZERO_UNLOCK_TIME, + tx_idx: 0, + }; + + /// Dummy `RctOutput`. + const RCT_OUTPUT: RctOutput = RctOutput { + key: [88; 32], + height: 1, + output_flags: OutputFlags::empty(), + tx_idx: 1, + commitment: [100; 32], + }; + + /// Dummy `Amount` + const AMOUNT: Amount = 22; + + /// Tests all above output functions when only inputting `Output` data (no Block). + /// + /// Note that this doesn't test the correctness of values added, as the + /// functions have a pre-condition that the caller handles this. + /// + /// It simply tests if the proper tables are mutated, and if the data + /// stored and retrieved is the same. + #[test] + fn all_output_functions() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + // Assert length is correct. + assert_eq!(get_num_outputs(tables.outputs()).unwrap(), 0); + assert_eq!(get_rct_num_outputs(tables.rct_outputs()).unwrap(), 0); + + // Add outputs. + let pre_rct_output_id = add_output(AMOUNT, &OUTPUT, &mut tables).unwrap(); + let amount_index = add_rct_output(&RCT_OUTPUT, tables.rct_outputs_mut()).unwrap(); + + assert_eq!( + pre_rct_output_id, + PreRctOutputId { + amount: AMOUNT, + amount_index: 0, + } + ); + + // Assert all reads of the outputs are OK. + { + // Assert proper tables were added to. + assert_eq!(tables.block_infos().len().unwrap(), 0); + assert_eq!(tables.block_blobs().len().unwrap(), 0); + assert_eq!(tables.block_heights().len().unwrap(), 0); + assert_eq!(tables.key_images().len().unwrap(), 0); + assert_eq!(tables.num_outputs().len().unwrap(), 1); + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.outputs().len().unwrap(), 1); + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 1); + assert_eq!(tables.tx_blobs().len().unwrap(), 0); + assert_eq!(tables.tx_ids().len().unwrap(), 0); + assert_eq!(tables.tx_heights().len().unwrap(), 0); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 0); + + // Assert length is correct. + assert_eq!(get_num_outputs(tables.outputs()).unwrap(), 1); + assert_eq!(get_rct_num_outputs(tables.rct_outputs()).unwrap(), 1); + assert_eq!(1, tables.num_outputs().get(&AMOUNT).unwrap()); + + // Assert value is save after retrieval. + assert_eq!( + OUTPUT, + get_output(&pre_rct_output_id, tables.outputs()).unwrap(), + ); + + assert_eq!( + RCT_OUTPUT, + get_rct_output(&amount_index, tables.rct_outputs()).unwrap(), + ); + } + + // Remove the outputs. + { + remove_output(&pre_rct_output_id, &mut tables).unwrap(); + remove_rct_output(&amount_index, tables.rct_outputs_mut()).unwrap(); + + // Assert value no longer exists. + assert!(matches!( + get_output(&pre_rct_output_id, tables.outputs()), + Err(RuntimeError::KeyNotFound) + )); + assert!(matches!( + get_rct_output(&amount_index, tables.rct_outputs()), + Err(RuntimeError::KeyNotFound) + )); + + // Assert length is correct. + assert_eq!(get_num_outputs(tables.outputs()).unwrap(), 0); + assert_eq!(get_rct_num_outputs(tables.rct_outputs()).unwrap(), 0); + } + + assert_all_tables_are_empty(&env); + } } diff --git a/database/src/ops/property.rs b/database/src/ops/property.rs index 8801ae8f..563bc8c0 100644 --- a/database/src/ops/property.rs +++ b/database/src/ops/property.rs @@ -1,9 +1,57 @@ //! Properties. //---------------------------------------------------------------------------------------------------- Import +use monero_pruning::PruningSeed; +use monero_serai::transaction::{Timelock, Transaction}; +use cuprate_types::{OutputOnChain, VerifiedBlockInformation}; + +use crate::{ + database::{DatabaseIter, DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::macros::{doc_add_block_inner_invariant, doc_error}, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{ + BlockHash, BlockHeight, BlockInfo, KeyImage, Output, PreRctOutputId, RctOutput, TxHash, + TxId, + }, +}; //---------------------------------------------------------------------------------------------------- Free Functions /// TODO -pub fn get_blockchain_pruning_seed() { - todo!() +/// +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +/// +/// # Example +/// ```rust +/// # use cuprate_database::{*, tables::*, ops::block::*, ops::tx::*}; +/// // TODO +/// ``` +#[inline] +pub const fn get_blockchain_pruning_seed() -> Result { + // TODO: impl pruning. + // We need a DB properties table. + Ok(PruningSeed::NotPruned) +} + +/// TODO +/// +#[doc = doc_add_block_inner_invariant!()] +#[doc = doc_error!()] +/// +/// # Example +/// ```rust +/// # use cuprate_database::{*, tables::*, ops::block::*, ops::tx::*}; +/// // TODO +/// ``` +#[inline] +pub const fn db_version() -> Result { + // TODO: We need a DB properties table. + Ok(crate::constants::DATABASE_VERSION) } diff --git a/database/src/ops/spent_key.rs b/database/src/ops/spent_key.rs deleted file mode 100644 index a8e6fe02..00000000 --- a/database/src/ops/spent_key.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Spent keys. - -//---------------------------------------------------------------------------------------------------- Import - -//---------------------------------------------------------------------------------------------------- Free Functions -/// TODO -pub fn add_spent_key() { - todo!() -} - -/// TODO -pub fn remove_spent_key() { - todo!() -} - -/// TODO -pub fn is_spent_key_recorded() { - todo!() -} diff --git a/database/src/ops/tx.rs b/database/src/ops/tx.rs index 9acafc9c..a66750ec 100644 --- a/database/src/ops/tx.rs +++ b/database/src/ops/tx.rs @@ -1,64 +1,446 @@ //! Transactions. //---------------------------------------------------------------------------------------------------- Import +use bytemuck::TransparentWrapper; +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar}; +use monero_serai::transaction::{Input, Timelock, Transaction}; -//---------------------------------------------------------------------------------------------------- Free Functions -/// TODO -pub fn add_transaction() { - todo!() +use cuprate_types::{OutputOnChain, TransactionVerificationData, VerifiedBlockInformation}; +use monero_pruning::PruningSeed; + +use crate::{ + database::{DatabaseIter, DatabaseRo, DatabaseRw}, + env::EnvInner, + error::RuntimeError, + ops::{ + blockchain::chain_height, + macros::{doc_add_block_inner_invariant, doc_error}, + property::get_blockchain_pruning_seed, + }, + tables::{ + BlockBlobs, BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, PrunableHashes, + PrunableTxBlobs, PrunedTxBlobs, RctOutputs, Tables, TablesMut, TxBlobs, TxHeights, TxIds, + TxUnlockTime, + }, + transaction::{TxRo, TxRw}, + types::{ + AmountIndices, BlockHash, BlockHeight, BlockInfo, KeyImage, Output, OutputFlags, + PreRctOutputId, RctOutput, TxBlob, TxHash, TxId, + }, + StorableVec, +}; + +use super::{ + key_image::{add_key_image, remove_key_image}, + output::{add_output, add_rct_output, get_rct_num_outputs, remove_output, remove_rct_output}, +}; + +//---------------------------------------------------------------------------------------------------- Private +/// Add a [`TransactionVerificationData`] to the database. +/// +/// The `block_height` is the block that this `tx` belongs to. +/// +/// Note that the caller's input is trusted implicitly and no checks +/// are done (in this function) whether the `block_height` is correct or not. +/// +#[doc = doc_add_block_inner_invariant!()] +/// +/// # Notes +/// This function is different from other sub-functions and slightly more similar to +/// [`add_block()`](crate::ops::block::add_block) in that it calls other sub-functions. +/// +/// This function calls: +/// - [`add_output()`] +/// - [`add_rct_output()`] +/// - [`add_key_image()`] +/// +/// Thus, after [`add_tx`], those values (outputs and key images) +/// will be added to database tables as well. +/// +/// # Panics +/// This function will panic if: +/// - `block.height > u32::MAX` (not normally possible) +#[doc = doc_error!()] +#[inline] +pub fn add_tx( + tx: &TransactionVerificationData, + block_height: &BlockHeight, + tables: &mut impl TablesMut, +) -> Result { + let tx_id = get_num_tx(tables.tx_ids_mut())?; + + //------------------------------------------------------ Transaction data + tables.tx_ids_mut().put(&tx.tx_hash, &tx_id)?; + tables.tx_heights_mut().put(&tx_id, block_height)?; + tables + .tx_blobs_mut() + .put(&tx_id, StorableVec::wrap_ref(&tx.tx_blob))?; + + //------------------------------------------------------ Timelocks + // Height/time is not differentiated via type, but rather: + // "height is any value less than 500_000_000 and timestamp is any value above" + // so the `u64/usize` is stored without any tag. + // + // + match tx.tx.prefix.timelock { + Timelock::None => (), + Timelock::Block(height) => tables.tx_unlock_time_mut().put(&tx_id, &(height as u64))?, + Timelock::Time(time) => tables.tx_unlock_time_mut().put(&tx_id, &time)?, + } + + //------------------------------------------------------ Pruning + // SOMEDAY: implement pruning after `monero-serai` does. + // if let PruningSeed::Pruned(decompressed_pruning_seed) = get_blockchain_pruning_seed()? { + // SOMEDAY: what to store here? which table? + // } + + //------------------------------------------------------ + // Refer to the inner transaction type from now on. + let tx: &Transaction = &tx.tx; + let Ok(height) = u32::try_from(*block_height) else { + panic!("add_tx(): block_height ({block_height}) > u32::MAX"); + }; + + //------------------------------------------------------ Key Images + // Is this a miner transaction? + // Which table we add the output data to depends on this. + // + let mut miner_tx = false; + + // Key images. + for inputs in &tx.prefix.inputs { + match inputs { + // Key images. + Input::ToKey { key_image, .. } => { + add_key_image(key_image.compress().as_bytes(), tables.key_images_mut())?; + } + // This is a miner transaction, set it for later use. + Input::Gen(_) => miner_tx = true, + } + } + + //------------------------------------------------------ Outputs + // Output bit flags. + // Set to a non-zero bit value if the unlock time is non-zero. + let output_flags = match tx.prefix.timelock { + Timelock::None => OutputFlags::empty(), + Timelock::Block(_) | Timelock::Time(_) => OutputFlags::NON_ZERO_UNLOCK_TIME, + }; + + let mut amount_indices = Vec::with_capacity(tx.prefix.outputs.len()); + let tx_idx = get_num_tx(tables.tx_ids_mut())?; + + for (i, output) in tx.prefix.outputs.iter().enumerate() { + let key = *output.key.as_bytes(); + + // Outputs with clear amounts. + let amount_index = if let Some(amount) = output.amount { + // RingCT (v2 transaction) miner outputs. + if miner_tx && tx.prefix.version == 2 { + // Create commitment. + // + // FIXME: implement lookup table for common values: + // + let commitment = (ED25519_BASEPOINT_POINT + + monero_serai::H() * Scalar::from(amount)) + .compress() + .to_bytes(); + + add_rct_output( + &RctOutput { + key, + height, + output_flags, + tx_idx, + commitment, + }, + tables.rct_outputs_mut(), + )? + // Pre-RingCT outputs. + } else { + add_output( + amount, + &Output { + key, + height, + output_flags, + tx_idx, + }, + tables, + )? + .amount_index + } + // RingCT outputs. + } else { + let commitment = tx.rct_signatures.base.commitments[i].compress().to_bytes(); + add_rct_output( + &RctOutput { + key, + height, + output_flags, + tx_idx, + commitment, + }, + tables.rct_outputs_mut(), + )? + }; + + amount_indices.push(amount_index); + } // for each output + + tables + .tx_outputs_mut() + .put(&tx_id, &StorableVec(amount_indices))?; + + Ok(tx_id) } -/// TODO -pub fn add_transaction_data() { - todo!() +/// Remove a transaction from the database with its [`TxHash`]. +/// +/// This returns the [`TxId`] and [`TxBlob`] of the removed transaction. +/// +#[doc = doc_add_block_inner_invariant!()] +/// +/// # Notes +/// As mentioned in [`add_tx`], this function will call other sub-functions: +/// - [`remove_output()`] +/// - [`remove_rct_output()`] +/// - [`remove_key_image()`] +/// +/// Thus, after [`remove_tx`], those values (outputs and key images) +/// will be remove from database tables as well. +/// +#[doc = doc_error!()] +#[inline] +pub fn remove_tx( + tx_hash: &TxHash, + tables: &mut impl TablesMut, +) -> Result<(TxId, Transaction), RuntimeError> { + //------------------------------------------------------ Transaction data + let tx_id = tables.tx_ids_mut().take(tx_hash)?; + let tx_blob = tables.tx_blobs_mut().take(&tx_id)?; + tables.tx_heights_mut().delete(&tx_id)?; + tables.tx_outputs_mut().delete(&tx_id)?; + + //------------------------------------------------------ Pruning + // SOMEDAY: implement pruning after `monero-serai` does. + // table_prunable_hashes.delete(&tx_id)?; + // table_prunable_tx_blobs.delete(&tx_id)?; + // if let PruningSeed::Pruned(decompressed_pruning_seed) = get_blockchain_pruning_seed()? { + // SOMEDAY: what to remove here? which table? + // } + + //------------------------------------------------------ Unlock Time + match tables.tx_unlock_time_mut().delete(&tx_id) { + Ok(()) | Err(RuntimeError::KeyNotFound) => (), + // An actual error occurred, return. + Err(e) => return Err(e), + } + + //------------------------------------------------------ + // Refer to the inner transaction type from now on. + let tx = Transaction::read(&mut tx_blob.0.as_slice())?; + + //------------------------------------------------------ Key Images + // Is this a miner transaction? + let mut miner_tx = false; + for inputs in &tx.prefix.inputs { + match inputs { + // Key images. + Input::ToKey { key_image, .. } => { + remove_key_image(key_image.compress().as_bytes(), tables.key_images_mut())?; + } + // This is a miner transaction, set it for later use. + Input::Gen(_) => miner_tx = true, + } + } // for each input + + //------------------------------------------------------ Outputs + // Remove each output in the transaction. + for (i, output) in tx.prefix.outputs.iter().enumerate() { + // Outputs with clear amounts. + if let Some(amount) = output.amount { + // RingCT miner outputs. + if miner_tx && tx.prefix.version == 2 { + let amount_index = get_rct_num_outputs(tables.rct_outputs())? - 1; + remove_rct_output(&amount_index, tables.rct_outputs_mut())?; + // Pre-RingCT outputs. + } else { + let amount_index = tables.num_outputs_mut().get(&amount)? - 1; + remove_output( + &PreRctOutputId { + amount, + amount_index, + }, + tables, + )?; + } + // RingCT outputs. + } else { + let amount_index = get_rct_num_outputs(tables.rct_outputs())? - 1; + remove_rct_output(&amount_index, tables.rct_outputs_mut())?; + } + } // for each output + + Ok((tx_id, tx)) } -/// TODO -pub fn remove_transaction() { - todo!() +//---------------------------------------------------------------------------------------------------- `get_tx_*` +/// Retrieve a [`Transaction`] from the database with its [`TxHash`]. +#[doc = doc_error!()] +#[inline] +pub fn get_tx( + tx_hash: &TxHash, + table_tx_ids: &impl DatabaseRo, + table_tx_blobs: &impl DatabaseRo, +) -> Result { + get_tx_from_id(&table_tx_ids.get(tx_hash)?, table_tx_blobs) } -/// TODO -pub fn remove_transaction_data() { - todo!() +/// Retrieve a [`Transaction`] from the database with its [`TxId`]. +#[doc = doc_error!()] +#[inline] +pub fn get_tx_from_id( + tx_id: &TxId, + table_tx_blobs: &impl DatabaseRo, +) -> Result { + let tx_blob = table_tx_blobs.get(tx_id)?.0; + Ok(Transaction::read(&mut tx_blob.as_slice())?) } -/// TODO -pub fn remove_tx_outputs() { - todo!() +//---------------------------------------------------------------------------------------------------- +/// How many [`Transaction`]s are there? +/// +/// This returns the amount of transactions currently stored. +/// +/// For example: +/// - 0 transactions exist => returns 0 +/// - 1 transactions exist => returns 1 +/// - 5 transactions exist => returns 5 +/// - etc +#[doc = doc_error!()] +#[inline] +pub fn get_num_tx(table_tx_ids: &impl DatabaseRo) -> Result { + table_tx_ids.len() } -/// TODO -pub fn get_num_tx() { - todo!() +//---------------------------------------------------------------------------------------------------- +/// Check if a transaction exists in the database. +/// +/// Returns `true` if it does, else `false`. +#[doc = doc_error!()] +#[inline] +pub fn tx_exists( + tx_hash: &TxHash, + table_tx_ids: &impl DatabaseRo, +) -> Result { + table_tx_ids.contains(tx_hash) } -/// TODO -pub fn tx_exists() { - todo!() -} +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +#[allow(clippy::significant_drop_tightening)] +mod test { + use super::*; + use crate::{ + tests::{assert_all_tables_are_empty, tmp_concrete_env}, + Env, + }; + use cuprate_test_utils::data::{tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; + use pretty_assertions::assert_eq; -/// TODO -pub fn get_tx_unlock_time() { - todo!() -} + /// Tests all above tx functions when only inputting `Transaction` data (no Block). + #[test] + fn all_tx_functions() { + let (env, tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); -/// TODO -pub fn get_tx() { - todo!() -} + // Monero `Transaction`, not database tx. + let txs = [tx_v1_sig0(), tx_v1_sig2(), tx_v2_rct3()]; -/// TODO -pub fn get_tx_list() { - todo!() -} + // Add transactions. + let tx_ids = { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); -/// TODO -pub fn get_pruned_tx() { - todo!() -} + let tx_ids = txs + .iter() + .map(|tx| { + println!("add_tx(): {tx:#?}"); + add_tx(tx, &0, &mut tables).unwrap() + }) + .collect::>(); -/// TODO -pub fn get_tx_block_height() { - todo!() + drop(tables); + TxRw::commit(tx_rw).unwrap(); + + tx_ids + }; + + // Assert all reads of the transactions are OK. + let tx_hashes = { + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + + // Assert only the proper tables were added to. + assert_eq!(tables.block_infos().len().unwrap(), 0); + assert_eq!(tables.block_blobs().len().unwrap(), 0); + assert_eq!(tables.block_heights().len().unwrap(), 0); + assert_eq!(tables.key_images().len().unwrap(), 4); // added to key images + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.num_outputs().len().unwrap(), 9); + assert_eq!(tables.outputs().len().unwrap(), 10); // added to outputs + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 2); + assert_eq!(tables.tx_blobs().len().unwrap(), 3); + assert_eq!(tables.tx_ids().len().unwrap(), 3); + assert_eq!(tables.tx_heights().len().unwrap(), 3); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 1); // only 1 has a timelock + + // Both from ID and hash should result in getting the same transaction. + let mut tx_hashes = vec![]; + for (i, tx_id) in tx_ids.iter().enumerate() { + println!("tx_ids.iter(): i: {i}, tx_id: {tx_id}"); + + let tx_get_from_id = get_tx_from_id(tx_id, tables.tx_blobs()).unwrap(); + let tx_hash = tx_get_from_id.hash(); + let tx_get = get_tx(&tx_hash, tables.tx_ids(), tables.tx_blobs()).unwrap(); + + println!("tx_ids.iter(): tx_get_from_id: {tx_get_from_id:#?}, tx_get: {tx_get:#?}"); + + assert_eq!(tx_get_from_id.hash(), tx_get.hash()); + assert_eq!(tx_get_from_id.hash(), txs[i].tx_hash); + assert_eq!(tx_get_from_id, tx_get); + assert_eq!(tx_get, txs[i].tx); + assert!(tx_exists(&tx_hash, tables.tx_ids()).unwrap()); + + tx_hashes.push(tx_hash); + } + + tx_hashes + }; + + // Remove the transactions. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + for tx_hash in tx_hashes { + println!("remove_tx(): tx_hash: {tx_hash:?}"); + + let (tx_id, _) = remove_tx(&tx_hash, &mut tables).unwrap(); + assert!(matches!( + get_tx_from_id(&tx_id, tables.tx_blobs()), + Err(RuntimeError::KeyNotFound) + )); + } + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + assert_all_tables_are_empty(&env); + } } diff --git a/database/src/tables.rs b/database/src/tables.rs index 7944b3ce..846d4054 100644 --- a/database/src/tables.rs +++ b/database/src/tables.rs @@ -4,19 +4,17 @@ //---------------------------------------------------------------------------------------------------- Import use crate::{ + database::{DatabaseIter, DatabaseRo, DatabaseRw}, table::Table, types::{ - Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfoV1, - BlockInfoV2, BlockInfoV3, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash, - PrunedBlob, RctOutput, TxHash, TxId, UnlockTime, + Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage, + Output, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput, TxBlob, TxHash, + TxId, UnlockTime, }, }; -//---------------------------------------------------------------------------------------------------- Tables +//---------------------------------------------------------------------------------------------------- Sealed /// Private module, should not be accessible outside this crate. -/// -/// Used to block outsiders implementing [`Table`]. -/// All [`Table`] types must also implement [`Sealed`]. pub(super) mod private { /// Private sealed trait. /// @@ -24,6 +22,255 @@ pub(super) mod private { pub trait Sealed {} } +//---------------------------------------------------------------------------------------------------- `trait Tables[Mut]` +/// Creates: +/// - `pub trait Tables` +/// - `pub trait TablesMut` +/// - Blanket implementation for `(tuples, containing, all, open, database, tables, ...)` +/// +/// For why this exists, see: . +macro_rules! define_trait_tables { + ($( + // The `T: Table` type The index in a tuple + // | containing all tables + // v v + $table:ident => $index:literal + ),* $(,)?) => { paste::paste! { + /// Object containing all opened [`Table`]s in read-only mode. + /// + /// This is an encapsulated object that contains all + /// available [`Table`]'s in read-only mode. + /// + /// It is a `Sealed` trait and is only implemented on a + /// `(tuple, containing, all, table, types, ...)`. + /// + /// This is used to return a _single_ object from functions like + /// [`EnvInner::open_tables`](crate::EnvInner::open_tables) rather + /// than the tuple containing the tables itself. + /// + /// To replace `tuple.0` style indexing, `field_accessor_functions()` + /// are provided on this trait, which essentially map the object to + /// fields containing the particular database table, for example: + /// ```rust,ignore + /// let tables = open_tables(); + /// + /// // The accessor function `block_info_v1s()` returns the field + /// // containing an open database table for `BlockInfoV1s`. + /// let _ = tables.block_info_v1s(); + /// ``` + pub trait Tables: private::Sealed { + // This expands to creating `fn field_accessor_functions()` + // for each passed `$table` type. + // + // It is essentially a mapping to the field + // containing the proper opened database table. + // + // The function name of the function is + // the table type in `snake_case`, e.g., `block_info_v1s()`. + $( + /// Access an opened + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake>](&self) -> &impl DatabaseRo<$table>; + )* + + /// This returns `true` if all tables are empty. + /// + /// # Errors + /// This returns errors on regular database errors. + fn all_tables_empty(&self) -> Result; + } + + /// Object containing all opened [`Table`]s in read + iter mode. + /// + /// This is the same as [`Tables`] but includes `_iter()` variants. + /// + /// See [`Tables`] for documentation - this trait exists for the same reasons. + pub trait TablesIter: private::Sealed + Tables { + $( + /// Access an opened read-only + iterable + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>); + )* + } + + /// Object containing all opened [`Table`]s in write mode. + /// + /// This is the same as [`Tables`] but for mutable accesses. + /// + /// See [`Tables`] for documentation - this trait exists for the same reasons. + pub trait TablesMut: private::Sealed + Tables { + $( + /// Access an opened + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table>; + )* + } + + // Implement `Sealed` for all table types. + impl<$([<$table:upper>]),*> private::Sealed for ($([<$table:upper>]),*) {} + + // This creates a blanket-implementation for + // `(tuple, containing, all, table, types)`. + // + // There is a generic defined here _for each_ `$table` input. + // Specifically, the generic letters are just the table types in UPPERCASE. + // Concretely, this expands to something like: + // ```rust + // impl + // ``` + impl<$([<$table:upper>]),*> Tables + // We are implementing `Tables` on a tuple that + // contains all those generics specified, i.e., + // a tuple containing all open table types. + // + // Concretely, this expands to something like: + // ```rust + // (BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...]) + // ``` + // which is just a tuple of the generics defined above. + for ($([<$table:upper>]),*) + where + // This expands to a where bound that asserts each element + // in the tuple implements some database table type. + // + // Concretely, this expands to something like: + // ```rust + // BLOCKINFOSV1S: DatabaseRo + DatabaseIter, + // BLOCKINFOSV2S: DatabaseRo + DatabaseIter, + // [...] + // ``` + $( + [<$table:upper>]: DatabaseRo<$table>, + )* + { + $( + // The function name of the accessor function is + // the table type in `snake_case`, e.g., `block_info_v1s()`. + #[inline] + fn [<$table:snake>](&self) -> &impl DatabaseRo<$table> { + // The index of the database table in + // the tuple implements the table trait. + &self.$index + } + )* + + fn all_tables_empty(&self) -> Result { + $( + if !DatabaseRo::is_empty(&self.$index)? { + return Ok(false); + } + )* + Ok(true) + } + } + + // This is the same as the above + // `Tables`, but for `TablesIter`. + impl<$([<$table:upper>]),*> TablesIter + for ($([<$table:upper>]),*) + where + $( + [<$table:upper>]: DatabaseRo<$table> + DatabaseIter<$table>, + )* + { + $( + // The function name of the accessor function is + // the table type in `snake_case` + `_iter`, e.g., `block_info_v1s_iter()`. + #[inline] + fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>) { + &self.$index + } + )* + } + + // This is the same as the above + // `Tables`, but for `TablesMut`. + impl<$([<$table:upper>]),*> TablesMut + for ($([<$table:upper>]),*) + where + $( + [<$table:upper>]: DatabaseRw<$table>, + )* + { + $( + // The function name of the mutable accessor function is + // the table type in `snake_case` + `_mut`, e.g., `block_info_v1s_mut()`. + #[inline] + fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table> { + &mut self.$index + } + )* + } + }}; +} + +// Format: $table_type => $index +// +// The $index: +// - Simply increments by 1 for each table +// - Must be 0.. +// - Must end at the total amount of table types +// +// Compile errors will occur if these aren't satisfied. +define_trait_tables! { + BlockInfos => 0, + BlockBlobs => 1, + BlockHeights => 2, + KeyImages => 3, + NumOutputs => 4, + PrunedTxBlobs => 5, + PrunableHashes => 6, + Outputs => 7, + PrunableTxBlobs => 8, + RctOutputs => 9, + TxBlobs => 10, + TxIds => 11, + TxHeights => 12, + TxOutputs => 13, + TxUnlockTime => 14, +} + +//---------------------------------------------------------------------------------------------------- Table function macro +/// `crate`-private macro for callings functions on all tables. +/// +/// This calls the function `$fn` with the optional +/// arguments `$args` on all tables - returning early +/// (within whatever scope this is called) if any +/// of the function calls error. +/// +/// Else, it evaluates to an `Ok((tuple, of, all, table, types, ...))`, +/// i.e., an `impl Table[Mut]` wrapped in `Ok`. +macro_rules! call_fn_on_all_tables_or_early_return { + ( + $($fn:ident $(::)?)* + ( + $($arg:ident),* $(,)? + ) + ) => {{ + Ok(( + $($fn ::)*<$crate::tables::BlockInfos>($($arg),*)?, + $($fn ::)*<$crate::tables::BlockBlobs>($($arg),*)?, + $($fn ::)*<$crate::tables::BlockHeights>($($arg),*)?, + $($fn ::)*<$crate::tables::KeyImages>($($arg),*)?, + $($fn ::)*<$crate::tables::NumOutputs>($($arg),*)?, + $($fn ::)*<$crate::tables::PrunedTxBlobs>($($arg),*)?, + $($fn ::)*<$crate::tables::PrunableHashes>($($arg),*)?, + $($fn ::)*<$crate::tables::Outputs>($($arg),*)?, + $($fn ::)*<$crate::tables::PrunableTxBlobs>($($arg),*)?, + $($fn ::)*<$crate::tables::RctOutputs>($($arg),*)?, + $($fn ::)*<$crate::tables::TxBlobs>($($arg),*)?, + $($fn ::)*<$crate::tables::TxIds>($($arg),*)?, + $($fn ::)*<$crate::tables::TxHeights>($($arg),*)?, + $($fn ::)*<$crate::tables::TxOutputs>($($arg),*)?, + $($fn ::)*<$crate::tables::TxUnlockTime>($($arg),*)?, + )) + }}; +} +pub(crate) use call_fn_on_all_tables_or_early_return; + //---------------------------------------------------------------------------------------------------- Table macro /// Create all tables, should be used _once_. /// @@ -80,8 +327,10 @@ macro_rules! tables { // Notes: // - Keep this sorted A-Z (by table name) // - Tables are defined in plural to avoid name conflicts with types -// - If adding/changing a table, also edit the tests in `src/backend/tests.rs` -// and edit `Env::open` to make sure it creates the table +// - If adding/changing a table also edit: +// a) the tests in `src/backend/tests.rs` +// b) `Env::open` to make sure it creates the table (for all backends) +// c) `call_fn_on_all_tables_or_early_return!()` macro defined in this file tables! { /// TODO BlockBlobs, @@ -92,22 +341,17 @@ tables! { BlockHash => BlockHeight, /// TODO - BlockInfoV1s, - BlockHeight => BlockInfoV1, - - /// TODO - BlockInfoV2s, - BlockHeight => BlockInfoV2, - - /// TODO - BlockInfoV3s, - BlockHeight => BlockInfoV3, + BlockInfos, + BlockHeight => BlockInfo, /// TODO KeyImages, KeyImage => (), - /// TODO + /// Maps an output's amount to the number of outputs with that amount. + /// + /// For a new output the `AmountIndex` value from this + /// table will be its index in a list of duplicate outputs. NumOutputs, Amount => AmountIndex, @@ -119,18 +363,28 @@ tables! { Outputs, PreRctOutputId => Output, - /// TODO + // SOMEDAY: impl when `monero-serai` supports pruning PrunableTxBlobs, TxId => PrunableBlob, - /// TODO + // SOMEDAY: impl when `monero-serai` supports pruning PrunableHashes, TxId => PrunableHash, + // SOMEDAY: impl a properties table: + // - db version + // - pruning seed + // Properties, + // StorableString => StorableVec, + /// TODO RctOutputs, AmountIndex => RctOutput, + /// SOMEDAY: remove when `monero-serai` supports pruning + TxBlobs, + TxId => TxBlob, + /// TODO TxIds, TxHash => TxId, @@ -139,6 +393,10 @@ tables! { TxHeights, TxId => BlockHeight, + /// TODO + TxOutputs, + TxId => AmountIndices, + /// TODO TxUnlockTime, TxId => UnlockTime, diff --git a/database/src/tests.rs b/database/src/tests.rs new file mode 100644 index 00000000..e7b7e1ae --- /dev/null +++ b/database/src/tests.rs @@ -0,0 +1,45 @@ +//! Utilities for `cuprate_database` testing. +//! +//! These types/fn's are only: +//! - enabled on #[cfg(test)] +//! - only used internally + +#![allow(clippy::significant_drop_tightening)] + +//---------------------------------------------------------------------------------------------------- Import +use std::{ + fmt::Debug, + sync::{Arc, OnceLock}, +}; + +use monero_serai::{ + ringct::{RctPrunable, RctSignatures}, + transaction::{Timelock, Transaction, TransactionPrefix}, +}; + +use crate::{ + config::Config, key::Key, storable::Storable, tables::Tables, transaction::TxRo, ConcreteEnv, + Env, EnvInner, +}; + +//---------------------------------------------------------------------------------------------------- fn +/// Create an `Env` in a temporarily directory. +/// The directory is automatically removed after the `TempDir` is dropped. +/// +/// FIXME: changing this to `-> impl Env` causes lifetime errors... +pub(crate) fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) { + let tempdir = tempfile::tempdir().unwrap(); + let config = Config::low_power(Some(tempdir.path().into())); + let env = ConcreteEnv::open(config).unwrap(); + + (env, tempdir) +} + +/// Assert all the tables in the environment are empty. +pub(crate) fn assert_all_tables_are_empty(env: &ConcreteEnv) { + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + assert!(tables.all_tables_empty().unwrap()); + assert_eq!(crate::ops::tx::get_num_tx(tables.tx_ids()).unwrap(), 0); +} diff --git a/database/src/types.rs b/database/src/types.rs index 9ca9c598..8f36e5e5 100644 --- a/database/src/types.rs +++ b/database/src/types.rs @@ -82,6 +82,9 @@ pub type PrunableBlob = StorableVec; /// TODO pub type PrunableHash = [u8; 32]; +/// TODO +pub type TxBlob = StorableVec; + /// TODO pub type TxId = u64; @@ -124,96 +127,6 @@ pub struct PreRctOutputId { pub amount_index: AmountIndex, } -//---------------------------------------------------------------------------------------------------- BlockInfoV1 -/// TODO -/// -/// ```rust -/// # use std::borrow::*; -/// # use cuprate_database::{*, types::*}; -/// // Assert Storable is correct. -/// let a = BlockInfoV1 { -/// timestamp: 1, -/// total_generated_coins: 123, -/// weight: 321, -/// cumulative_difficulty: 111, -/// block_hash: [54; 32], -/// }; -/// let b = Storable::as_bytes(&a); -/// let c: BlockInfoV1 = Storable::from_bytes(b); -/// assert_eq!(a, c); -/// ``` -/// -/// # Size & Alignment -/// ```rust -/// # use cuprate_database::types::*; -/// # use std::mem::*; -/// assert_eq!(size_of::(), 64); -/// assert_eq!(align_of::(), 8); -/// ``` -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] -#[repr(C)] -pub struct BlockInfoV1 { - /// TODO - pub timestamp: u64, - /// TODO - pub total_generated_coins: u64, - /// TODO - pub weight: u64, - /// TODO - pub cumulative_difficulty: u64, - /// TODO - pub block_hash: [u8; 32], -} - -//---------------------------------------------------------------------------------------------------- BlockInfoV2 -/// TODO -/// -/// ```rust -/// # use std::borrow::*; -/// # use cuprate_database::{*, types::*}; -/// // Assert Storable is correct. -/// let a = BlockInfoV2 { -/// timestamp: 1, -/// total_generated_coins: 123, -/// weight: 321, -/// block_hash: [54; 32], -/// cumulative_difficulty: 111, -/// cumulative_rct_outs: 2389, -/// }; -/// let b = Storable::as_bytes(&a); -/// let c: BlockInfoV2 = Storable::from_bytes(b); -/// assert_eq!(a, c); -/// ``` -/// -/// # Size & Alignment -/// ```rust -/// # use cuprate_database::types::*; -/// # use std::mem::*; -/// assert_eq!(size_of::(), 72); -/// assert_eq!(align_of::(), 8); -/// ``` -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] -#[repr(C)] -pub struct BlockInfoV2 { - /// TODO - pub timestamp: u64, - /// TODO - pub total_generated_coins: u64, - /// TODO - pub weight: u64, - /// TODO - pub block_hash: [u8; 32], - /// TODO - pub cumulative_difficulty: u64, - /// TODO - /// - /// TODO: note that this is originally u32, - /// but is u64 here for padding reasons. - pub cumulative_rct_outs: u64, -} - //---------------------------------------------------------------------------------------------------- BlockInfoV3 /// TODO /// @@ -221,18 +134,17 @@ pub struct BlockInfoV2 { /// # use std::borrow::*; /// # use cuprate_database::{*, types::*}; /// // Assert Storable is correct. -/// let a = BlockInfoV3 { +/// let a = BlockInfo { /// timestamp: 1, -/// total_generated_coins: 123, +/// cumulative_generated_coins: 123, /// weight: 321, -/// cumulative_difficulty_low: 111, -/// cumulative_difficulty_high: 112, +/// cumulative_difficulty: 112, /// block_hash: [54; 32], /// cumulative_rct_outs: 2389, /// long_term_weight: 2389, /// }; /// let b = Storable::as_bytes(&a); -/// let c: BlockInfoV3 = Storable::from_bytes(b); +/// let c: BlockInfo = Storable::from_bytes(b); /// assert_eq!(a, c); /// ``` /// @@ -240,24 +152,21 @@ pub struct BlockInfoV2 { /// ```rust /// # use cuprate_database::types::*; /// # use std::mem::*; -/// assert_eq!(size_of::(), 88); -/// assert_eq!(align_of::(), 8); +/// assert_eq!(size_of::(), 88); +/// assert_eq!(align_of::(), 8); /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] #[repr(C)] -pub struct BlockInfoV3 { +pub struct BlockInfo { /// TODO pub timestamp: u64, /// TODO - pub total_generated_coins: u64, + pub cumulative_generated_coins: u64, /// TODO pub weight: u64, - // Maintain 8 byte alignment. /// TODO - pub cumulative_difficulty_low: u64, - /// TODO - pub cumulative_difficulty_high: u64, + pub cumulative_difficulty: u128, /// TODO pub block_hash: [u8; 32], /// TODO @@ -266,6 +175,36 @@ pub struct BlockInfoV3 { pub long_term_weight: u64, } +//---------------------------------------------------------------------------------------------------- OutputFlags +bitflags::bitflags! { + /// TODO + /// + /// ```rust + /// # use std::borrow::*; + /// # use cuprate_database::{*, types::*}; + /// // Assert Storable is correct. + /// let a = OutputFlags::NON_ZERO_UNLOCK_TIME; + /// let b = Storable::as_bytes(&a); + /// let c: OutputFlags = Storable::from_bytes(b); + /// assert_eq!(a, c); + /// ``` + /// + /// # Size & Alignment + /// ```rust + /// # use cuprate_database::types::*; + /// # use std::mem::*; + /// assert_eq!(size_of::(), 4); + /// assert_eq!(align_of::(), 4); + /// ``` + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] + #[repr(transparent)] + pub struct OutputFlags: u32 { + /// This output has a non-zero unlock time. + const NON_ZERO_UNLOCK_TIME = 0b0000_0001; + } +} + //---------------------------------------------------------------------------------------------------- Output /// TODO /// @@ -276,7 +215,7 @@ pub struct BlockInfoV3 { /// let a = Output { /// key: [1; 32], /// height: 1, -/// output_flags: 0, +/// output_flags: OutputFlags::empty(), /// tx_idx: 3, /// }; /// let b = Storable::as_bytes(&a); @@ -300,7 +239,7 @@ pub struct Output { /// We could get this from the tx_idx with the Tx Heights table but that would require another look up per out. pub height: u32, /// Bit flags for this output, currently only the first bit is used and, if set, it means this output has a non-zero unlock time. - pub output_flags: u32, + pub output_flags: OutputFlags, /// TODO pub tx_idx: u64, } @@ -315,7 +254,7 @@ pub struct Output { /// let a = RctOutput { /// key: [1; 32], /// height: 1, -/// output_flags: 0, +/// output_flags: OutputFlags::empty(), /// tx_idx: 3, /// commitment: [3; 32], /// }; @@ -340,7 +279,7 @@ pub struct RctOutput { /// We could get this from the tx_idx with the Tx Heights table but that would require another look up per out. pub height: u32, /// Bit flags for this output, currently only the first bit is used and, if set, it means this output has a non-zero unlock time. - pub output_flags: u32, + pub output_flags: OutputFlags, /// TODO pub tx_idx: u64, /// The amount commitment of this output. diff --git a/test-utils/src/data/free.rs b/test-utils/src/data/free.rs index 64ec0864..a08c6fec 100644 --- a/test-utils/src/data/free.rs +++ b/test-utils/src/data/free.rs @@ -27,8 +27,8 @@ use crate::data::constants::{ /// this struct represents that data that must be provided. /// /// Consider using `cuprate_test_utils::rpc` to get this data easily. -struct VerifiedBlockMap<'a> { - block: Block, +struct VerifiedBlockMap { + block_blob: &'static [u8], pow_hash: [u8; 32], height: u64, generated_coins: u64, @@ -37,10 +37,10 @@ struct VerifiedBlockMap<'a> { cumulative_difficulty: u128, // Vec of `tx_blob`'s, i.e. the data in `/test-utils/src/data/tx/`. // This should the actual `tx_blob`'s of the transactions within this block. - txs: Vec<&'a [u8]>, + txs: &'static [&'static [u8]], } -impl VerifiedBlockMap<'_> { +impl VerifiedBlockMap { /// Turn the various static data bits in `self` into a `VerifiedBlockInformation`. /// /// Transactions are verified that they at least match the block's, @@ -48,7 +48,7 @@ impl VerifiedBlockMap<'_> { /// is not checked. fn into_verified(self) -> VerifiedBlockInformation { let Self { - block, + block_blob, pow_hash, height, generated_coins, @@ -58,8 +58,11 @@ impl VerifiedBlockMap<'_> { txs, } = self; + let block_blob = block_blob.to_vec(); + let block = Block::read(&mut block_blob.as_slice()).unwrap(); + let txs: Vec> = txs - .into_iter() + .iter() .map(to_tx_verification_data) .map(Arc::new) .collect(); @@ -79,6 +82,7 @@ impl VerifiedBlockMap<'_> { VerifiedBlockInformation { block_hash: block.hash(), + block_blob, block, txs, pow_hash, @@ -92,8 +96,8 @@ impl VerifiedBlockMap<'_> { } // Same as [`VerifiedBlockMap`] but for [`TransactionVerificationData`]. -fn to_tx_verification_data(tx_blob: &[u8]) -> TransactionVerificationData { - let tx_blob = tx_blob.to_vec(); +fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> TransactionVerificationData { + let tx_blob = tx_blob.as_ref().to_vec(); let tx = Transaction::read(&mut tx_blob.as_slice()).unwrap(); TransactionVerificationData { tx_weight: tx.weight(), @@ -158,14 +162,14 @@ macro_rules! verified_block_information_fn { static BLOCK: OnceLock = OnceLock::new(); BLOCK.get_or_init(|| { VerifiedBlockMap { - block: Block::read(&mut $block_blob).unwrap(), + block_blob: $block_blob, pow_hash: hex!($pow_hash), height: $height, generated_coins: $generated_coins, weight: $weight, long_term_weight: $long_term_weight, cumulative_difficulty: $cumulative_difficulty, - txs: vec![$($tx_blob),*], + txs: &[$($tx_blob),*], } .into_verified() }) diff --git a/test-utils/src/rpc/client.rs b/test-utils/src/rpc/client.rs index 5531888e..34d194cd 100644 --- a/test-utils/src/rpc/client.rs +++ b/test-utils/src/rpc/client.rs @@ -102,9 +102,10 @@ impl HttpRpcClient { let reward = result.block_header.reward; - let (block_hash, block) = spawn_blocking(|| { - let block = Block::read(&mut hex::decode(result.blob).unwrap().as_slice()).unwrap(); - (block.hash(), block) + let (block_hash, block_blob, block) = spawn_blocking(|| { + let block_blob = hex::decode(result.blob).unwrap(); + let block = Block::read(&mut block_blob.as_slice()).unwrap(); + (block.hash(), block_blob, block) }) .await .unwrap(); @@ -139,6 +140,7 @@ impl HttpRpcClient { VerifiedBlockInformation { block, + block_blob, txs, block_hash, pow_hash, diff --git a/types/src/types.rs b/types/src/types.rs index 6532aec7..82652c9b 100644 --- a/types/src/types.rs +++ b/types/src/types.rs @@ -78,6 +78,10 @@ pub struct VerifiedBlockInformation { pub long_term_weight: usize, /// TODO pub cumulative_difficulty: u128, + /// TODO + /// + /// + pub block_blob: Vec, } //---------------------------------------------------------------------------------------------------- OutputOnChain