diff --git a/Cargo.lock b/Cargo.lock index 08ffc45..ffbb722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,9 +508,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" [[package]] name = "bech32" @@ -678,7 +678,7 @@ dependencies = [ [[package]] name = "block-index" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "bincode", @@ -932,7 +932,7 @@ dependencies = [ [[package]] name = "chain" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "clap", @@ -941,13 +941,11 @@ dependencies = [ "diesel", "diesel_migrations", "futures", - "itertools 0.13.0", "namada_core", "namada_sdk", "orm", "rayon", "shared", - "tendermint", "tendermint-rpc", "tokio", "tokio-retry", @@ -991,9 +989,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -1011,9 +1009,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -1023,9 +1021,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2614,7 +2612,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.2.0", + "http 1.3.0", "indexmap", "slab", "tokio", @@ -2733,9 +2731,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "0a761d192fbf18bdef69f5ceedd0d1333afcbda0ee23840373b8317570d23c65" dependencies = [ "bytes", "fnv", @@ -2760,18 +2758,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.0", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.0", "http-body 1.0.1", "pin-project-lite", ] @@ -2828,7 +2826,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.8", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "httparse", "itoa", @@ -2859,7 +2857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.2.0", + "http 1.3.0", "hyper 1.6.0", "hyper-util", "rustls 0.23.23", @@ -2894,7 +2892,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", @@ -2962,7 +2960,7 @@ dependencies = [ "borsh", "derive_more 1.0.0", "displaydoc", - "http 1.2.0", + "http 1.3.0", "ibc-app-transfer-types", "ibc-core", "ibc-proto", @@ -3753,9 +3751,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -3991,9 +3989,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libm" @@ -4047,9 +4045,9 @@ checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "masp_note_encryption" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b136637896ff0e2a9f18f25464eb8cc75dde909ab616918d3e13ab7401ab89a" +checksum = "cfa6b33e4b4d7eb2ec6d8a6b3db92cd92b63b50b15f226e227188b3ec6a1f92c" dependencies = [ "borsh", "chacha20", @@ -4061,9 +4059,9 @@ dependencies = [ [[package]] name = "masp_primitives" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c3739b3b1a5e767ad88a38c9000cf8014e5fab84b93c4e9415e10a56cede54" +checksum = "57c4482dd67fb614da7840a08b56d387e43ec8a06129e4f7bdba02900f4e3dbc" dependencies = [ "aes", "bip0039", @@ -4093,9 +4091,9 @@ dependencies = [ [[package]] name = "masp_proofs" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131fd70050405eb3cfd45e704982a1b44fafc9443dbb352d079ccf1c1cda53c9" +checksum = "f07358ecd7aa622deec656874eee2d309dcd9f046f89022105336de73d97b34f" dependencies = [ "bellman", "blake2b_simd", @@ -4200,12 +4198,11 @@ dependencies = [ [[package]] name = "minreq" -version = "2.13.2" +version = "2.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0c420feb01b9fb5061f8c8f452534361dd783756dcf38ec45191ce55e7a161" +checksum = "567496f13503d6cae8c9f961f34536850275f396307d7a6b981eef1464032f53" dependencies = [ "log", - "once_cell", "rustls 0.21.12", "rustls-webpki 0.101.7", "webpki-roots", @@ -4336,9 +4333,9 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df13eb11542131b4b821da19e1a30e816fa9b43d2997ff35337f464bfe4967c7" +checksum = "825895fe589e6136f495acc8e203218b8ec90fb1736d8a522dca77775e0d5d03" dependencies = [ "borsh", "namada_core", @@ -4349,9 +4346,9 @@ dependencies = [ [[package]] name = "namada_controller" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e346c20510aee8c66a437cd47b0dcbf2207e0d88d3b08239c62ad8002b5dc422" +checksum = "ae91ea4502deaf7053277dd260f6580813410ab37598c36714d8120f5aa03cfc" dependencies = [ "namada_core", "smooth-operator", @@ -4360,9 +4357,9 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305f82e07d088d0f98bb9100fc31616b10151d95ba77577d4b0c67db2f43c664" +checksum = "bb6d76951adce81378692fd558650496d87d44ce22767d81538f6c5a97fde847" dependencies = [ "bech32 0.11.0", "borsh", @@ -4410,9 +4407,9 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb49f7a94c8bac46f7d4a78bb432412340996b83355cd333b406acb6a166204" +checksum = "10d55597c32a4b4167c6e5703a317f666db819da3cb62a21ce96f98d07a05e9f" dependencies = [ "borsh", "ethers", @@ -4439,9 +4436,9 @@ dependencies = [ [[package]] name = "namada_events" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb996dc93beed802b0ebc9093f7480cf16ad3c73fd9176af7fad705886583b" +checksum = "c26d2e8a9c07b9c8365861dcce4b1021e1d657a59faef77fe0f668f8582f5a89" dependencies = [ "borsh", "namada_core", @@ -4454,9 +4451,9 @@ dependencies = [ [[package]] name = "namada_gas" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "429b267efddcb8b7ade3fbb16ceaa543eafe5bc22b2aeda6fbca21ca0c4368f3" +checksum = "da478c2bae8624d3ff0551d4f0e9afb27cba202c14e2cf1c64469398767eedb6" dependencies = [ "borsh", "namada_core", @@ -4468,9 +4465,9 @@ dependencies = [ [[package]] name = "namada_governance" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8fb47a3a9ad5217470c68e9b47d0f0d0c81b906c26999044eca3750e36a30c" +checksum = "55278f2a221ce7c9cb5f36f894de3556fd5d604f6d94f201de1b0a02da436dd6" dependencies = [ "borsh", "itertools 0.14.0", @@ -4492,9 +4489,9 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f077333e403267091ed9db4a0b786273ea8da8a1164bb86f3ba398e2139087" +checksum = "c02af0d9d2dd79946e660de0a3600c5c78747fc6116a982058d6daf6549d475e" dependencies = [ "borsh", "data-encoding", @@ -4528,9 +4525,9 @@ dependencies = [ [[package]] name = "namada_io" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a83bee0d6793e8f5a3ebe83e1a16119fa998bada374b1ce7da3082fb4e372b" +checksum = "9dbeead2685556e21264866ff126e698024737576adb04ca4b834080d739b50a" dependencies = [ "async-trait", "kdam", @@ -4542,9 +4539,9 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713a7c58a2f3e801d4bfb11ff04bed7c674368800ea4b4edf00d1f1a77e7500a" +checksum = "7bdfb4030ba839bcf6856ba0cdff946edc89a65559413b8b661711ad24536b20" dependencies = [ "data-encoding", "proc-macro2", @@ -4555,9 +4552,9 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015d5bd2c06167f7f6bf47017c0221956e05399af2cf7481802e883178152d10" +checksum = "e3d78772d1334343a6b2e9c969cb5192c4c4165c98178cc335e5b07913227c9c" dependencies = [ "borsh", "eyre", @@ -4571,9 +4568,9 @@ dependencies = [ [[package]] name = "namada_parameters" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3505786793edaa9efb6810b1bed10db9848d58d97481e4306646311d7f53c53a" +checksum = "0f18d0973dc04541b6442512221c63744cff79e5a33799b3a39d3370d7b8fe5a" dependencies = [ "namada_core", "namada_macros", @@ -4587,9 +4584,9 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1a71a1e875d6716c43217870c4b0f48a2f6e9bd64440ba4ec4c0257e8c1ebe" +checksum = "6aade987142f8caf9405f602da90654b08a29ff25f410184dd59ff947050abe0" dependencies = [ "borsh", "itertools 0.14.0", @@ -4612,18 +4609,18 @@ dependencies = [ [[package]] name = "namada_replay_protection" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0be426ad51a235dc34d29422ee5568d9c5bbae89ae057614ce076541a0a6687" +checksum = "c1b0a3815a0c585ef29b39fa0673d1bb15b293ad1f4c015f3f213a9696a25176" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a20424b35946d2b4bc71bebf5da7d29d9e940ee1d4bb63be0ca1bcd2c6bebd" +checksum = "368031f2c3698b137b6cc8509db9b10752d0544db8e128f31b221e6692ced1a3" dependencies = [ "async-trait", "bech32 0.11.0", @@ -4693,9 +4690,9 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365100d5249f320f43bef27af0f10bbcb1a34660e787a4370e01a6a4ff48e80c" +checksum = "e6b930e7e5db8dd0354754055e04a90eac0a826a2d753bba9cfb4780dea547d1" dependencies = [ "async-trait", "borsh", @@ -4735,9 +4732,9 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d33311b2c0e69cffca41ea02d766842d8c76b0ee4709d9979ae18a7f5a57f66" +checksum = "adf640707caf5862fb97f2ad97c09a3a6b7342ae5cebd231f683f2fe9795008c" dependencies = [ "borsh", "clru", @@ -4759,9 +4756,9 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd75a52a7cf559552531f426540e65d5319d1205d6720b0e73c86a8d3b54b745" +checksum = "9ce775658b4b2482b62d7c84a0789a395c726b7f8bd757f356ae0e0174a93265" dependencies = [ "borsh", "itertools 0.14.0", @@ -4779,9 +4776,9 @@ dependencies = [ [[package]] name = "namada_systems" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8efe52efb02d8f690a3ff8e66e5ca76c3f20ca08f1c51727f7e5df24af5bfbc5" +checksum = "363ceed8a89888907e11cac6de01b5621c07c7af2b2d307687726e30929fa98b" dependencies = [ "namada_core", "namada_events", @@ -4790,9 +4787,9 @@ dependencies = [ [[package]] name = "namada_token" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40936e9c720b2c8dc1a485785dd0eb3c7b303b39f4354a938e118b98b8ed146b" +checksum = "cb977f3012fe0e00b6f20e3e033d374ed40c8b9933f4c5eece38039d4959e3a3" dependencies = [ "borsh", "namada_core", @@ -4809,9 +4806,9 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08c1df33aa4945fe1930e3fbeaf82eef23cc03a7947ed39446c1eae077a3406" +checksum = "5f436936b2041d11e5396d2723da10627a6e2dc19f9dce12e560cc426a4a4761" dependencies = [ "konst", "namada_core", @@ -4827,9 +4824,9 @@ dependencies = [ [[package]] name = "namada_tx" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c34662abcf0ec42b31d24db4947b9b6a1ed358a607deb12caccb11f40845d1" +checksum = "cc556a7fcf4ec14b9381128bd796973fd2f845ca27d3119c5ef92f1b532c985d" dependencies = [ "ark-bls12-381", "bitflags 2.9.0", @@ -4857,9 +4854,9 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adc84a3facc292913273aed31308901f4768da7094a9421cb9f0b26c33901ac" +checksum = "c1263a5f67795c2ebbcc53e467469a7d446b982c8b99a67ae5365b956581f338" dependencies = [ "namada_core", "namada_events", @@ -4868,9 +4865,9 @@ dependencies = [ [[package]] name = "namada_vm" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ebf6fb3200ed683021f02055733c6f3e83d8e4c1e3495c04f0809345cddc32" +checksum = "b7cb63f9e4042ffd600fcacc8055d9070ef93625ccd227118016aac03a4b3e35" dependencies = [ "borsh", "clru", @@ -4891,9 +4888,9 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e9d839db71e00bba201e40a4901a07dd90b2247cb29dc865d71947cc9b1ffe" +checksum = "3bc3356f018b1186dd3a3e7814a732d40487b63351777cf2d64932e7841a6837" dependencies = [ "borsh", "namada_core", @@ -4904,9 +4901,9 @@ dependencies = [ [[package]] name = "namada_vp" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44d0dfc36cac54942b616811b8dcaa87a0570a44d40790ecafa4a0879b0fa06" +checksum = "850a0dd511d2d7cb43aabd1046400af408bb2a1708ce0029ff46d61acaa15cff" dependencies = [ "namada_core", "namada_events", @@ -4921,9 +4918,9 @@ dependencies = [ [[package]] name = "namada_vp_env" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91311bee2c290bd8fe85dd958fa9786b4c318bebd3fd7f0e41754a021ccbe68" +checksum = "5f6a2886fa36675cd64ca75390fba2bf7ef6808ce61a4fdb00a155b6f56fb202" dependencies = [ "derivative", "masp_primitives", @@ -4937,9 +4934,9 @@ dependencies = [ [[package]] name = "namada_wallet" -version = "0.47.3" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5850c17a1caae8363f6f27cde53637c6254c917d97a35505f12dfe094456557" +checksum = "68df2c006fecfa64b2116b304a49d3219fef1d7d09c8c2c96b35cfdedea3bbc5" dependencies = [ "bimap", "borsh", @@ -5247,7 +5244,7 @@ dependencies = [ [[package]] name = "orm" -version = "1.2.1" +version = "1.3.0" dependencies = [ "diesel", "serde", @@ -6024,7 +6021,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.8", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -6642,7 +6639,7 @@ dependencies = [ [[package]] name = "shared" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "namada_core", @@ -7001,9 +6998,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.40.1" +version = "0.40.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9703e34d940c2a293804752555107f8dbe2b84ec4c6dd5203831235868105d2" +checksum = "ab2972a56891bc9173eaebdd51290bc848e448aa281881f3a4712bd8fe1899b3" dependencies = [ "bytes", "digest 0.10.7", @@ -7031,9 +7028,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.40.1" +version = "0.40.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc3ea9a39b7ee34eefcff771cc067ecaa0c988c1c5ac08defd878471a06f76" +checksum = "651cb680d41e586bb688cc17ab0b6dfe2e62a959c5742d1351fe0b084cd75d59" dependencies = [ "flex-error", "serde", @@ -7058,9 +7055,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.40.1" +version = "0.40.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9e1705aa0fa5ecb2c6aa7fb78c2313c4a31158ea5f02048bf318f849352eb" +checksum = "ca44b9eaaa98e8440fbafe5593b8a32a1c395c094ac566b3eb50497f8bd2bee0" dependencies = [ "borsh", "bytes", @@ -7077,9 +7074,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.40.1" +version = "0.40.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a52aa504c63ec05519e31348d3f4ba2fe79493c588e2cad5323d5e81b161a" +checksum = "9367678af1a9fc6c064fab8b5d05f03c0fc243fe411581c00c7eed83f8ced380" dependencies = [ "async-trait", "bytes", @@ -8032,7 +8029,7 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webserver" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "axum", @@ -8042,10 +8039,8 @@ dependencies = [ "clap", "deadpool-diesel", "diesel", - "futures", "itertools 0.13.0", "lazy_static", - "namada_core", "orm", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index aa89f89..0887fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Heliax AG "] edition = "2024" license = "GPL-3.0-or-later" readme = "README.md" -version = "1.2.1" +version = "1.3.0" [workspace.dependencies] anyhow = "1.0.75" @@ -23,9 +23,9 @@ diesel_migrations = { version = "2.2.0", default-features = false, features = [ futures = "0.3.30" itertools = "0.13.0" lazy_static = "1.4.0" -namada_core = { version = "0.47.1" } -namada_sdk = { version = "0.47.1", default-features = false, features = ["std", "async-send", "download-params"] } -namada_tx = { version = "0.47.1" } +namada_sdk = { version = "0.149.1", default-features = false, features = ["std", "async-send", "download-params"] } +namada_tx = "0.149.1" +namada_core = "0.149.1" orm = { path = "orm" } rayon = "1.10.0" serde = { version = "1.0.138", features = [ "derive" ] } @@ -42,7 +42,7 @@ tower-http = { version = "0.4.4", features = [ "compression-full", "limit", "tra tracing = "0.1" tracing-appender = "0.2.0" tracing-subscriber = { version = "0.3", features = [ "env-filter" ] } -tryhard = { version = "0.5.1" } validator = { version = "0.16.0", features = ["derive"] } -vergen = "8.0.0" xorf = { version = "0.11.0", features = ["serde"]} +tryhard = { version = "0.5.1" } +vergen = "8.0.0" diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 10b5cbf..b9fc26c 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -26,13 +26,11 @@ orm.workspace = true rayon.workspace = true shared.workspace = true tendermint-rpc.workspace = true -tendermint.workspace = true tokio-retry.workspace = true tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true tryhard.workspace = true -itertools.workspace = true [build-dependencies] vergen = { workspace = true, features = ["build", "git", "gitcl"] } diff --git a/chain/run.sh b/chain/run.sh index 628078d..708bced 100755 --- a/chain/run.sh +++ b/chain/run.sh @@ -1 +1 @@ -cargo run --release -- --tendermint-url http://127.0.0.1:27657 --database-url postgres://postgres:password@0.0.0.0:5435/namada-masp-indexer --chain-id local.61271083678d6bb47c35eda1 --checksums-filepath ../artifacts/checksums.json \ No newline at end of file +cargo run --release -- --cometbft-url http://127.0.0.1:27657 --database-url postgres://postgres:password@0.0.0.0:5435/masp-indexer diff --git a/chain/src/entity/commitment_tree.rs b/chain/src/entity/commitment_tree.rs index 93ca865..f0805f4 100644 --- a/chain/src/entity/commitment_tree.rs +++ b/chain/src/entity/commitment_tree.rs @@ -28,6 +28,10 @@ impl InnerCommitmentTree { } } + fn is_dirty(&self) -> bool { + self.transactional.is_dirty() + } + fn rollback(&mut self) { self.transactional.rollback(); } @@ -68,6 +72,10 @@ impl CommitmentTree { Self(Arc::new(Mutex::new(InnerCommitmentTree::new(tree)))) } + pub fn is_dirty(&self) -> bool { + self.0.lock().unwrap().is_dirty() + } + pub fn rollback(&self) { self.0.lock().unwrap().rollback() } diff --git a/chain/src/entity/tx_notes_index.rs b/chain/src/entity/tx_notes_index.rs index 1d36ea3..506b88e 100644 --- a/chain/src/entity/tx_notes_index.rs +++ b/chain/src/entity/tx_notes_index.rs @@ -1,13 +1,13 @@ use std::collections::BTreeMap; use orm::notes_index::NotesIndexInsertDb; -use shared::indexed_tx::IndexedTx; +use shared::indexed_tx::{IndexedTx, MaspIndexedTx}; #[derive(Default, Clone, Debug)] -pub struct TxNoteMap(BTreeMap); +pub struct TxNoteMap(BTreeMap); impl TxNoteMap { - pub fn insert(&mut self, indexed_tx: IndexedTx, note_pos: usize) { + pub fn insert(&mut self, indexed_tx: MaspIndexedTx, note_pos: usize) { self.0.insert(indexed_tx, note_pos); } @@ -20,18 +20,25 @@ impl TxNoteMap { .iter() .map( |( - &IndexedTx { - block_height, - block_index, - batch_index, - .. + &MaspIndexedTx { + indexed_tx: + IndexedTx { + block_height, + block_index, + masp_tx_index, + }, + kind, }, ¬e_pos, )| NotesIndexInsertDb { block_index: block_index.0 as i32, note_position: note_pos as i32, block_height: block_height.0 as i32, - masp_tx_index: batch_index as i32, + masp_tx_index: masp_tx_index.0 as i32, + is_masp_fee_payment: matches!( + kind, + shared::indexed_tx::MaspTxKind::FeePayment + ), }, ) .collect() diff --git a/chain/src/main.rs b/chain/src/main.rs index 344bcf0..c7d0db5 100644 --- a/chain/src/main.rs +++ b/chain/src/main.rs @@ -3,7 +3,7 @@ pub mod config; pub mod entity; pub mod services; -use std::collections::HashSet; +use std::collections::BTreeMap; use std::env; use std::sync::Arc; use std::sync::atomic::{self, AtomicBool}; @@ -11,10 +11,9 @@ use std::time::Duration; use anyhow::Context; use clap::Parser; -use shared::block::Block; use shared::error::{IntoMainError, MainError}; use shared::height::{BlockHeight, FollowingHeights}; -use shared::indexed_tx::IndexedTx; +use shared::transaction::Transaction; use tendermint_rpc::HttpClient; use tendermint_rpc::client::CompatMode; use tokio::signal; @@ -218,8 +217,6 @@ async fn build_and_commit_masp_data_at_height( return Ok(()); } - let start = Instant::now(); - // NB: rollback changes from previous failed commit attempts witness_map.rollback(); commitment_tree.rollback(); @@ -242,7 +239,9 @@ async fn build_and_commit_masp_data_at_height( return Err(MainError); } - let block_data = { + let mut checkpoint = Instant::now(); + + let (block_data, num_transactions) = { tracing::info!( %block_height, "Fetching block data from CometBFT" @@ -251,62 +250,61 @@ async fn build_and_commit_masp_data_at_height( cometbft_service::query_masp_txs_in_block(&client, block_height) .await .into_rpc_error()?; - tracing::info!( - %block_height, - "Acquired block data from CometBFT" - ); - block_data + with_time_taken(&mut checkpoint, |time_taken| { + tracing::info!( + time_taken, + %block_height, + "Acquired block data from CometBFT" + ); + }); + let num_transactions = block_data.transactions.len(); + (block_data, num_transactions) }; - let mut shielded_txs = Vec::new(); + let mut shielded_txs = BTreeMap::new(); let mut tx_notes_index = TxNoteMap::default(); - let ordered_txs = - lookup_valid_commitment_tree(&client, &commitment_tree, &block_data) - .await?; - - let anything_to_commit = !ordered_txs.is_empty(); - - commitment_tree.rollback(); + tracing::info!( + %block_height, + num_transactions, + "Processing new masp transactions...", + ); - for (new_masp_tx_index, mut indexed_tx) in - ordered_txs.into_iter().enumerate() + for (masp_indexed_tx, Transaction { masp_tx, .. }) in + block_data.transactions.into_iter() { - let masp_tx = block_data.get_masp_tx(indexed_tx).unwrap(); - - indexed_tx.masp_tx_index = new_masp_tx_index.into(); - masp_service::update_witness_map( &commitment_tree, &mut tx_notes_index, &witness_map, - indexed_tx, - masp_tx, + masp_indexed_tx, + &masp_tx, ) .into_masp_error()?; - shielded_txs.push((indexed_tx, masp_tx.clone())); + shielded_txs.insert(masp_indexed_tx, masp_tx); } - if anything_to_commit { - masp_service::query_witness_map_anchor_existence( - &witness_map, - commitment_tree.root(), - number_of_witness_map_roots_to_check, - ) - .into_masp_error()?; - } - - let first_checkpoint = Instant::now(); + with_time_taken(&mut checkpoint, |time_taken| { + tracing::info!( + %block_height, + num_transactions, + time_taken, + "Processed new masp transactions", + ); + }); - tracing::info!( - %block_height, - num_transactions = block_data.transactions.len(), - time_taken = first_checkpoint.duration_since(start).as_secs_f64(), - "Processed new masp transactions...", - ); + validate_masp_state( + &mut checkpoint, + &client, + &commitment_tree, + &witness_map, + number_of_witness_map_roots_to_check, + ) + .await?; db_service::commit( + &mut checkpoint, &conn_obj, chain_state, commitment_tree, @@ -317,83 +315,60 @@ async fn build_and_commit_masp_data_at_height( .await .into_db_error()?; - let second_checkpoint = Instant::now(); - - tracing::info!( - block_height = %chain_state.block_height, - time_taken = second_checkpoint - .duration_since(first_checkpoint) - .as_secs_f64(), - "Committed new block" - ); - Ok(()) } -async fn lookup_valid_commitment_tree( +async fn validate_masp_state( + checkpoint: &mut Instant, client: &HttpClient, commitment_tree: &CommitmentTree, - block: &Block, -) -> Result, MainError> { - use itertools::Itertools; - - let all_indexed_txs: Vec<_> = block.indexed_txs().collect(); - - let mut correct_order = Vec::with_capacity(all_indexed_txs.len()); - let mut fee_unshields = HashSet::with_capacity(all_indexed_txs.len()); - - // Guess the set of fee unshieldings at the current height - let fee_unshield_sets = all_indexed_txs.iter().copied().powerset(); - - for fee_unshield_set in fee_unshield_sets { - // Start a new attempt at guessing the root of - // the commitment tree - commitment_tree.rollback(); - correct_order.clear(); - fee_unshields.clear(); - - tracing::info!( - ?fee_unshield_set, - "Checking subset of masp fee unshields to build cmt tree" - ); - - for indexed_tx in fee_unshield_set { - let masp_tx = block.get_masp_tx(indexed_tx).unwrap(); + witness_map: &WitnessMap, + number_of_witness_map_roots_to_check: usize, +) -> Result<(), MainError> { + if commitment_tree.is_dirty() && number_of_witness_map_roots_to_check > 0 { + tracing::info!("Validating MASP state..."); - masp_service::update_commitment_tree(commitment_tree, masp_tx) - .into_masp_error()?; + let tree_root = tokio::task::block_in_place(|| commitment_tree.root()); - correct_order.push(indexed_tx); - fee_unshields.insert(indexed_tx); - } + let commitment_tree_check_fut = async { + cometbft_service::query_commitment_tree_anchor_existence( + client, tree_root, + ) + .await + .into_rpc_error() + }; + + let witness_map = witness_map.clone(); + let witness_map_check_fut = async move { + tokio::task::spawn_blocking(move || { + masp_service::query_witness_map_anchor_existence( + &witness_map, + tree_root, + number_of_witness_map_roots_to_check, + ) + .into_masp_error() + }) + .await + .context("Failed to join Tokio task") + .into_tokio_join_error()? + }; - for indexed_tx in all_indexed_txs - .iter() - .copied() - // We filter fee unshields out of this loop - .filter(|indexed_tx| !fee_unshields.contains(indexed_tx)) - { - let masp_tx = block.get_masp_tx(indexed_tx).unwrap(); + futures::try_join!(commitment_tree_check_fut, witness_map_check_fut)?; - masp_service::update_commitment_tree(commitment_tree, masp_tx) - .into_masp_error()?; + with_time_taken(checkpoint, |time_taken| { + tracing::info!(time_taken, "Validated MASP state"); + }); + } - correct_order.push(indexed_tx); - } + Ok(()) +} - if cometbft_service::query_commitment_tree_anchor_existence( - client, - commitment_tree.root(), - ) - .await - .into_masp_error()? - { - return Ok(correct_order); - } - } +fn with_time_taken(checkpoint: &mut Instant, callback: F) -> T +where + F: FnOnce(f64) -> T, +{ + let last_checkpoint = std::mem::replace(checkpoint, Instant::now()); + let time_taken = last_checkpoint.elapsed().as_secs_f64(); - Err(anyhow::anyhow!( - "Couldn't find a valid permutation of fee unshieldings" - )) - .into_masp_error() + callback(time_taken) } diff --git a/chain/src/services/cometbft.rs b/chain/src/services/cometbft.rs index a06e392..ca6dfe0 100644 --- a/chain/src/services/cometbft.rs +++ b/chain/src/services/cometbft.rs @@ -5,19 +5,6 @@ use shared::height::BlockHeight; use tendermint_rpc::endpoint::{block, block_results}; use tendermint_rpc::{Client, HttpClient}; -pub async fn query_commitment_tree_anchor_existence( - client: &HttpClient, - commitment_tree_root: Node, -) -> anyhow::Result { - let anchor_key = namada_sdk::token::storage_key::masp_commitment_anchor_key( - commitment_tree_root, - ); - - namada_sdk::rpc::query_has_storage_key(client, &anchor_key) - .await - .context("Failed to check if commitment tree root is in storage") -} - pub async fn query_masp_txs_in_block( client: &HttpClient, height: BlockHeight, @@ -49,3 +36,24 @@ async fn query_raw_block_results_at_height( .await .context("Failed to query CometBFT's block results") } + +pub async fn query_commitment_tree_anchor_existence( + client: &HttpClient, + commitment_tree_root: Node, +) -> anyhow::Result<()> { + let anchor_key = namada_sdk::token::storage_key::masp_commitment_anchor_key( + commitment_tree_root, + ); + + if !namada_sdk::rpc::query_has_storage_key(client, &anchor_key) + .await + .context("Failed to check if commitment tree root is in storage")? + { + anyhow::bail!( + "No commitment tree found with the given root: \ + {commitment_tree_root:?}" + ) + } + + Ok(()) +} diff --git a/chain/src/services/db.rs b/chain/src/services/db.rs index 775df0c..191ac95 100644 --- a/chain/src/services/db.rs +++ b/chain/src/services/db.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use anyhow::{Context, anyhow}; use deadpool_diesel::postgres::Object; @@ -21,12 +21,14 @@ use orm::tx::TxInsertDb; use orm::witness::WitnessDb; use shared::error::ContextDbInteractError; use shared::height::BlockHeight; -use shared::indexed_tx::IndexedTx; +use shared::indexed_tx::MaspIndexedTx; +use tokio::time::Instant; use crate::entity::chain_state::ChainState; use crate::entity::commitment_tree::CommitmentTree; use crate::entity::tx_notes_index::TxNoteMap; use crate::entity::witness_map::WitnessMap; +use crate::with_time_taken; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("../orm/migrations/"); @@ -177,12 +179,13 @@ pub async fn get_last_witness_map(conn: Object) -> anyhow::Result { #[allow(clippy::too_many_arguments)] pub async fn commit( + checkpoint: &mut Instant, conn: &Object, chain_state: ChainState, commitment_tree: CommitmentTree, witness_map: WitnessMap, notes_index: TxNoteMap, - shielded_txs: Vec<(IndexedTx, Transaction)>, + shielded_txs: BTreeMap, ) -> anyhow::Result<()> { tracing::info!( block_height = %chain_state.block_height, @@ -260,11 +263,20 @@ pub async fn commit( let shielded_txs_db = shielded_txs .iter() - .map(|(index, tx)| TxInsertDb { - block_index: index.block_index.0 as i32, - tx_bytes: tx.serialize_to_vec(), - block_height: index.block_height.0 as i32, - masp_tx_index: index.masp_tx_index.0 as i32, + .map(|(MaspIndexedTx { kind, indexed_tx }, tx)| { + let is_masp_fee_payment = matches!( + kind, + shared::indexed_tx::MaspTxKind::FeePayment + ); + + TxInsertDb { + block_index: indexed_tx.block_index.0 as i32, + tx_bytes: tx.serialize_to_vec(), + block_height: indexed_tx.block_height.0 as i32, + masp_tx_index: indexed_tx.masp_tx_index.0 + as i32, + is_masp_fee_payment, + } }) .collect::>(); diesel::insert_into(schema::tx::table) @@ -308,5 +320,13 @@ pub async fn commit( ) })?; + with_time_taken(checkpoint, |time_taken| { + tracing::info!( + block_height = %chain_state.block_height, + time_taken, + "Committed new block" + ); + }); + Ok(()) } diff --git a/chain/src/services/masp.rs b/chain/src/services/masp.rs index be1522c..e64cc49 100644 --- a/chain/src/services/masp.rs +++ b/chain/src/services/masp.rs @@ -1,40 +1,22 @@ use namada_core::masp_primitives::ff::PrimeField; use namada_core::masp_primitives::sapling::Node; -use namada_core::masp_primitives::transaction::Transaction; use namada_sdk::masp_primitives::merkle_tree::IncrementalWitness; use rayon::prelude::*; -use shared::indexed_tx::IndexedTx; +use shared::indexed_tx::MaspIndexedTx; use crate::entity::commitment_tree::CommitmentTree; use crate::entity::tx_notes_index::TxNoteMap; use crate::entity::witness_map::WitnessMap; -pub fn update_commitment_tree( - commitment_tree: &CommitmentTree, - stx_batch: &Transaction, -) -> anyhow::Result<()> { - for so in stx_batch - .sapling_bundle() - .map_or(&vec![], |x| &x.shielded_outputs) - { - let node = Node::new(so.cmu.to_repr()); - if !commitment_tree.append(node) { - anyhow::bail!("Note commitment tree is full"); - } - } - - Ok(()) -} - -/// Update the merkle tree of witnesses the first time we -/// scan a new MASP transaction. pub fn update_witness_map( commitment_tree: &CommitmentTree, tx_notes_index: &mut TxNoteMap, witness_map: &WitnessMap, - indexed_tx: IndexedTx, + indexed_tx: MaspIndexedTx, shielded: &namada_core::masp_primitives::transaction::Transaction, ) -> anyhow::Result<()> { + tracing::info!(?indexed_tx, "Updating witness map"); + let mut note_pos = commitment_tree.size(); tx_notes_index.insert(indexed_tx, note_pos); diff --git a/orm/migrations/2025-03-06-081320_delete_invalid_commitment_trees/up.sql b/orm/migrations/2025-03-06-081320_delete_invalid_commitment_trees/up.sql index dfccd2d..e87033e 100644 --- a/orm/migrations/2025-03-06-081320_delete_invalid_commitment_trees/up.sql +++ b/orm/migrations/2025-03-06-081320_delete_invalid_commitment_trees/up.sql @@ -1,12 +1,14 @@ -- Your SQL goes here -BEGIN; +SELECT 1; -DELETE FROM block_index; -UPDATE chain_state SET block_height = 1055117; -DELETE FROM commitment_tree WHERE block_height >= 1055118; -DELETE FROM notes_index WHERE block_height >= 1055118; -DELETE FROM tx WHERE block_height >= 1055118; -DELETE FROM witness WHERE block_height >= 1055118; +-- BEGIN; -COMMIT; +-- DELETE FROM block_index; +-- UPDATE chain_state SET block_height = 1055117; +-- DELETE FROM commitment_tree WHERE block_height >= 1055118; +-- DELETE FROM notes_index WHERE block_height >= 1055118; +-- DELETE FROM tx WHERE block_height >= 1055118; +-- DELETE FROM witness WHERE block_height >= 1055118; + +-- COMMIT; diff --git a/orm/migrations/2025-03-10-142053_masp_fee_payment/down.sql b/orm/migrations/2025-03-10-142053_masp_fee_payment/down.sql new file mode 100644 index 0000000..08f07ce --- /dev/null +++ b/orm/migrations/2025-03-10-142053_masp_fee_payment/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE tx DROP COLUMN is_masp_fee_payment; +ALTER TABLE notes_index DROP COLUMN is_masp_fee_payment; diff --git a/orm/migrations/2025-03-10-142053_masp_fee_payment/up.sql b/orm/migrations/2025-03-10-142053_masp_fee_payment/up.sql new file mode 100644 index 0000000..0073676 --- /dev/null +++ b/orm/migrations/2025-03-10-142053_masp_fee_payment/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here + +ALTER TABLE tx +ADD COLUMN is_masp_fee_payment BOOLEAN NOT NULL DEFAULT false; + +ALTER TABLE notes_index +ADD COLUMN is_masp_fee_payment BOOLEAN NOT NULL DEFAULT false; diff --git a/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/down.sql b/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/down.sql new file mode 100644 index 0000000..2a3866c --- /dev/null +++ b/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; diff --git a/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/up.sql b/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/up.sql new file mode 100644 index 0000000..e6e89db --- /dev/null +++ b/orm/migrations/2025-04-04-134858_reset_masp_data_to_migrate_events/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here + +DELETE FROM block_index; +UPDATE chain_state SET block_height = 1031830; +DELETE FROM commitment_tree WHERE block_height > 1031830; +DELETE FROM notes_index WHERE block_height > 1031830; +DELETE FROM tx WHERE block_height > 1031830; +DELETE FROM witness WHERE block_height > 1031830; diff --git a/orm/src/notes_index.rs b/orm/src/notes_index.rs index 1d33e40..b6ca5c3 100644 --- a/orm/src/notes_index.rs +++ b/orm/src/notes_index.rs @@ -11,6 +11,7 @@ pub struct NotesIndexDb { pub note_position: i32, pub block_height: i32, pub masp_tx_index: i32, + pub is_masp_fee_payment: bool, } #[derive(Serialize, Insertable, Clone)] @@ -21,4 +22,5 @@ pub struct NotesIndexInsertDb { pub note_position: i32, pub block_height: i32, pub masp_tx_index: i32, + pub is_masp_fee_payment: bool, } diff --git a/orm/src/schema.rs b/orm/src/schema.rs index 29da589..566bb4c 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -29,6 +29,7 @@ diesel::table! { block_index -> Int4, block_height -> Int4, masp_tx_index -> Int4, + is_masp_fee_payment -> Bool, } } @@ -39,6 +40,7 @@ diesel::table! { tx_bytes -> Bytea, block_height -> Int4, masp_tx_index -> Int4, + is_masp_fee_payment -> Bool, } } diff --git a/orm/src/tx.rs b/orm/src/tx.rs index bd8a700..0466bf3 100644 --- a/orm/src/tx.rs +++ b/orm/src/tx.rs @@ -12,6 +12,7 @@ pub struct TxDb { pub tx_bytes: Vec, pub block_height: i32, pub masp_tx_index: i32, + pub is_masp_fee_payment: bool, } #[derive(Serialize, Insertable, Clone)] @@ -22,4 +23,5 @@ pub struct TxInsertDb { pub tx_bytes: Vec, pub block_height: i32, pub masp_tx_index: i32, + pub is_masp_fee_payment: bool, } diff --git a/shared/src/block.rs b/shared/src/block.rs index d214371..ea6751f 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -1,21 +1,22 @@ +use std::collections::BTreeMap; use std::fmt::Display; -use namada_core::masp_primitives::transaction::Transaction as NamadaMaspTransaction; -use namada_sdk::events::extend::IndexedMaspData; +use namada_sdk::state::TxIndex as NamadaTxIndex; +use namada_tx::Tx as NamadaTx; +use namada_tx::event::MaspEvent; use tendermint_rpc::endpoint::{block, block_results}; use crate::block_results::locate_masp_txs; use crate::header::BlockHeader; use crate::id::Id; -use crate::indexed_tx::IndexedTx; +use crate::indexed_tx::MaspIndexedTx; use crate::transaction::Transaction; -use crate::tx_index::{MaspTxIndex, TxIndex}; #[derive(Debug, Clone, Default)] pub struct Block { pub hash: Id, pub header: BlockHeader, - pub transactions: Vec<(usize, Transaction)>, + pub transactions: BTreeMap, } impl Block { @@ -23,74 +24,50 @@ impl Block { raw_block: block::Response, raw_results: block_results::Response, ) -> Result { - let indexed_masp_txs = locate_masp_txs(&raw_results); + let indexed_masp_txs = locate_masp_txs(&raw_results)?; let mut block = Block { hash: Id::from(raw_block.block_id.hash), header: BlockHeader::from(raw_block.block.header), - transactions: Vec::with_capacity(raw_block.block.data.len()), + transactions: BTreeMap::new(), }; - for IndexedMaspData { + // Cache the last tx seen to avoid multiple deserializations + let mut last_tx: Option<(NamadaTx, NamadaTxIndex)> = None; + + for MaspEvent { tx_index, - masp_refs, + kind, + data, } in indexed_masp_txs { - let block_index = tx_index.0 as usize; - let tx_bytes = &raw_block.block.data[block_index]; - let tx = Transaction::from_namada_tx(tx_bytes, &masp_refs.0)?; - - block.transactions.push((block_index, tx)); + let tx = match &last_tx { + Some((tx, idx)) if idx == &tx_index.block_index => tx, + _ => { + let tx = NamadaTx::try_from_bytes( + raw_block.block.data[tx_index.block_index.0 as usize] + .as_ref(), + ) + .map_err(|e| e.to_string())?; + last_tx = Some((tx, tx_index.block_index)); + + &last_tx.as_ref().unwrap().0 + } + }; + + let tx = Transaction::from_namada_tx(tx, &data)?; + + block.transactions.insert( + MaspIndexedTx { + kind: kind.into(), + indexed_tx: tx_index.into(), + }, + tx, + ); } - block - .transactions - .sort_unstable_by_key(|(tx_index, _)| *tx_index); - Ok(block) } - - pub fn get_masp_tx( - &self, - indexed_tx: IndexedTx, - ) -> Option<&NamadaMaspTransaction> { - #[cold] - fn unlikely T>(f: F) -> T { - f() - } - - if self.header.height != indexed_tx.block_height { - return unlikely(|| None); - } - - let found_at_index = self - .transactions - .binary_search_by_key( - &indexed_tx.block_index, - |(block_index, _)| TxIndex(*block_index as _), - ) - .ok()?; - - let (_, transaction) = match self.transactions.get(found_at_index) { - Some(tx) => tx, - None => unreachable!(), - }; - - transaction.masp_txs.get(indexed_tx.batch_index) - } - - pub fn indexed_txs(&self) -> impl Iterator + '_ { - self.transactions.iter().flat_map( - |(block_index, Transaction { masp_txs, .. })| { - (0..masp_txs.len()).map(|batch_index| IndexedTx { - block_height: self.header.height, - block_index: TxIndex(*block_index as _), - masp_tx_index: MaspTxIndex(usize::MAX), - batch_index, - }) - }, - ) - } } impl Display for Block { @@ -102,7 +79,12 @@ impl Display for Block { self.header.height, self.transactions .iter() - .map(|(_, tx)| tx.to_string()) + .map(|(masp_indexed_tx, tx)| { + format!( + "Hash: {}, Batch index: {}", + tx.hash, masp_indexed_tx.indexed_tx.masp_tx_index + ) + }) .collect::>() ) } diff --git a/shared/src/block_results.rs b/shared/src/block_results.rs index 0f70c3d..52f0674 100644 --- a/shared/src/block_results.rs +++ b/shared/src/block_results.rs @@ -1,18 +1,47 @@ -use namada_sdk::events::extend::{ - IndexedMaspData, MaspDataRefs, ReadFromEventAttributes, -}; +use std::str::FromStr; + +use namada_sdk::events::EventType; +use namada_sdk::events::extend::ReadFromEventAttributes; +use namada_tx::IndexedTx; +use namada_tx::event::{MaspEvent, MaspEventKind, MaspTxRef}; use tendermint_rpc::endpoint::block_results; pub fn locate_masp_txs( raw_block_results: &block_results::Response, -) -> Vec { - raw_block_results +) -> Result, String> { + let maybe_masp_events: Result, String> = raw_block_results .end_block_events .as_ref() .unwrap_or(&vec![]) .iter() - .filter_map(|event| { - MaspDataRefs::read_from_event_attributes(&event.attributes).ok() + .map(|event| { + // Check if the event is a Masp one + let Ok(kind) = EventType::from_str(&event.kind) else { + return Ok(None); + }; + + let kind = if kind == namada_tx::event::masp_types::TRANSFER { + MaspEventKind::Transfer + } else if kind == namada_tx::event::masp_types::FEE_PAYMENT { + MaspEventKind::FeePayment + } else { + return Ok(None); + }; + // Extract the data from the event's attributes, propagate errors if + // the masp event does not follow the expected format + let data = MaspTxRef::read_from_event_attributes(&event.attributes) + .map_err(|e| e.to_string())?; + let tx_index = + IndexedTx::read_from_event_attributes(&event.attributes) + .map_err(|e| e.to_string())?; + + Ok(Some(MaspEvent { + tx_index, + kind, + data, + })) }) - .collect() + .collect(); + + Ok(maybe_masp_events?.into_iter().flatten().collect()) } diff --git a/shared/src/error.rs b/shared/src/error.rs index 1779d16..d5bb9cf 100644 --- a/shared/src/error.rs +++ b/shared/src/error.rs @@ -49,6 +49,11 @@ pub trait IntoMainError: Sized { fn into_masp_error(self) -> Result { self.into_main_error("MASP error") } + + #[inline] + fn into_tokio_join_error(self) -> Result { + self.into_main_error("Tokio join error") + } } impl IntoMainError for anyhow::Result { diff --git a/shared/src/indexed_tx.rs b/shared/src/indexed_tx.rs index cb928c2..7817157 100644 --- a/shared/src/indexed_tx.rs +++ b/shared/src/indexed_tx.rs @@ -1,15 +1,77 @@ +use std::cmp::Ordering; + use crate::height::BlockHeight; use crate::tx_index::{MaspTxIndex, TxIndex}; +/// The type of a MASP transaction +#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] +pub enum MaspTxKind { + /// A MASP transaction used for fee payment + FeePayment, + /// A general MASP transfer + #[default] + Transfer, +} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct MaspIndexedTx { + /// The masp tx kind, fee-payment or transfer + pub kind: MaspTxKind, + /// The pointer to the inner tx carrying this masp tx + pub indexed_tx: IndexedTx, +} + +impl Ord for MaspIndexedTx { + fn cmp(&self, other: &Self) -> Ordering { + // If txs are in different blocks we just have to compare their block + // heights. If instead txs are in the same block, masp fee paying txs + // take precedence over transfer masp txs. After that we sort them based + // on their indexes + self.indexed_tx + .block_height + .cmp(&other.indexed_tx.block_height) + .then( + self.kind + .cmp(&other.kind) + .then(self.indexed_tx.cmp(&other.indexed_tx)), + ) + } +} + +impl PartialOrd for MaspIndexedTx { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IndexedTx { /// The block height of the indexed tx pub block_height: BlockHeight, - /// The index pertaining to the order through - /// which a masp tx is processed in Namada - pub masp_tx_index: MaspTxIndex, /// The index in the block of the tx pub block_index: TxIndex, - /// The index of an inner tx within this batch - pub batch_index: usize, + /// The index pertaining to the order through + /// which a masp tx is included in a transaction batch + pub masp_tx_index: MaspTxIndex, +} + +impl From for IndexedTx { + fn from(value: namada_tx::IndexedTx) -> Self { + Self { + block_height: value.block_height.0.into(), + block_index: TxIndex(value.block_index.0), + masp_tx_index: MaspTxIndex(value.batch_index.unwrap() as usize), + } + } +} + +impl From for MaspTxKind { + fn from(value: namada_tx::event::MaspEventKind) -> Self { + match value { + namada_tx::event::MaspEventKind::FeePayment => { + MaspTxKind::FeePayment + } + namada_tx::event::MaspEventKind::Transfer => MaspTxKind::Transfer, + } + } } diff --git a/shared/src/transaction.rs b/shared/src/transaction.rs index 30ad9a9..4817e3a 100644 --- a/shared/src/transaction.rs +++ b/shared/src/transaction.rs @@ -1,10 +1,9 @@ use std::borrow::Cow; -use std::fmt::Display; use namada_core::hash::Hash; use namada_core::masp_primitives::transaction::Transaction as NamadaMaspTransaction; -use namada_sdk::events::extend::MaspTxRef; use namada_sdk::token::Transfer; +use namada_tx::event::MaspTxRef; use namada_tx::{Data, Section, Tx as NamadaTx}; use crate::id::Id; @@ -12,61 +11,39 @@ use crate::id::Id; #[derive(Debug, Clone)] pub struct Transaction { pub hash: Id, - pub masp_txs: Vec, + pub masp_tx: NamadaMaspTransaction, } impl Transaction { pub fn from_namada_tx( - nam_tx_bytes: &[u8], - valid_masp_tx_refs: &[MaspTxRef], + transaction: &NamadaTx, + valid_masp_tx_ref: &MaspTxRef, ) -> Result { - let transaction = - NamadaTx::try_from(nam_tx_bytes).map_err(|e| e.to_string())?; let transaction_id = transaction.header_hash(); - let masp_txs = valid_masp_tx_refs.iter().try_fold( - vec![], - |mut acc, masp_tx_ref| { - let masp_tx = match &masp_tx_ref { - MaspTxRef::MaspSection(masp_tx_id) => { - let masp_tx = transaction - .get_masp_section(masp_tx_id) - .ok_or_else(|| { - "Missing expected masp section with id: {id}" - .to_string() - })?; - Cow::Borrowed(masp_tx) - } - MaspTxRef::IbcData(sechash) => { - let masp_tx = - get_masp_tx_from_ibc_data(&transaction, sechash) - .ok_or_else(|| { - "Missing expected data section with hash: \ - {sechash}" - .to_string() - })?; - Cow::Owned(masp_tx) - } - }; - - acc.push(masp_tx.into_owned()); - Result::<_, String>::Ok(acc) - }, - )?; + let masp_tx = match &valid_masp_tx_ref { + MaspTxRef::MaspSection(masp_tx_id) => transaction + .get_masp_section(masp_tx_id) + .ok_or_else(|| { + "Missing expected masp section with id: {id}".to_string() + })? + .to_owned(), + MaspTxRef::IbcData(sechash) => get_masp_tx_from_ibc_data( + transaction, + sechash, + ) + .ok_or_else(|| { + "Missing expected data section with hash: {sechash}".to_string() + })?, + }; Ok(Transaction { - masp_txs, + masp_tx, hash: Id::from(transaction_id), }) } } -impl Display for Transaction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.hash) - } -} - fn get_masp_tx_from_ibc_data( transaction: &NamadaTx, data_sechash: &Hash, diff --git a/shared/src/transactional.rs b/shared/src/transactional.rs index 71877de..ca5f182 100644 --- a/shared/src/transactional.rs +++ b/shared/src/transactional.rs @@ -11,6 +11,10 @@ impl Transactional { working_copy: None, } } + + pub const fn is_dirty(&self) -> bool { + self.working_copy.is_some() + } } impl AsRef for Transactional { diff --git a/shared/src/tx_index.rs b/shared/src/tx_index.rs index 7491b78..b858f63 100644 --- a/shared/src/tx_index.rs +++ b/shared/src/tx_index.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use namada_sdk::state::TxIndex as NamadaTxIndex; #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -9,12 +11,12 @@ impl From for TxIndex { } } -/// The order in which a masp tx appears in a Namada tx event. +/// The batch index in which a masp tx appears in a Namada tx event. #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MaspTxIndex(pub usize); -impl From for MaspTxIndex { - fn from(masp_tx_index: usize) -> Self { - Self(masp_tx_index) +impl Display for MaspTxIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) } } diff --git a/swagger.yml b/swagger.yml index fd76129..6bc173a 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1,7 +1,7 @@ openapi: '3.0.2' info: title: Masp Indexer - version: '1.2.1' + version: '1.3.0' servers: - url: https://localhost:5000/api/v1 paths: diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml index 3f3c01e..d598e3d 100644 --- a/webserver/Cargo.toml +++ b/webserver/Cargo.toml @@ -25,10 +25,8 @@ bincode.workspace = true clap.workspace = true deadpool-diesel.workspace = true diesel.workspace = true -futures.workspace = true itertools.workspace = true lazy_static.workspace = true -namada_core.workspace = true orm.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 31f0606..7a8fffe 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -53,6 +53,7 @@ impl ApplicationServer { get(handler::notes_index::get_notes_index), ) .route("/tx", get(handler::tx::get_tx)) + .route("/tx_by_indices", get(handler::tx::get_tx_by_indices)) .route("/height", get(handler::namada_state::get_latest_height)) .route( "/block-index", diff --git a/webserver/src/dto/indices.rs b/webserver/src/dto/indices.rs new file mode 100644 index 0000000..05d31bc --- /dev/null +++ b/webserver/src/dto/indices.rs @@ -0,0 +1,121 @@ +use std::collections::{HashMap, HashSet}; + +use axum::extract::RawQuery; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +use crate::error::tx::TxError; + +#[derive(Clone, Serialize, Deserialize, Validate)] +pub struct IndexQueryParams { + #[validate(length(min = 1))] + pub indices: Vec, +} + +#[derive(Copy, Clone, Serialize, Deserialize, Validate)] +pub struct Index { + #[validate(range(min = 1))] + pub height: u64, + #[validate(range(min = 0))] + pub block_index: u32, +} + +impl TryFrom for IndexQueryParams { + type Error = TxError; + + fn try_from(raw: RawQuery) -> Result { + let Some(query) = raw.0 else { + return Err(Self::Error::RawQuery( + "Received empty indices".to_string(), + )); + }; + let args = ArgParser::parse(&query).map_err(TxError::RawQuery)?; + let heights = args.0.get("heights").ok_or_else(|| { + TxError::RawQuery("Expected argument name `heights`".to_string()) + })?; + let block_indices = args.0.get("block_indices").ok_or_else(|| { + TxError::RawQuery( + "Expected argument name `block_indices`".to_string(), + ) + })?; + let heights = heights + .split('.') + .map(|s| { + s.parse::().map_err(|_| { + TxError::RawQuery(format!( + "Could not parse {s} as block height" + )) + }) + }) + .collect::, _>>()?; + let block_indices = block_indices + .split('.') + .map(|s| { + s.parse::().map_err(|_| { + TxError::RawQuery(format!( + "Could not parse {s} as block index" + )) + }) + }) + .collect::, _>>()?; + if heights.len() != block_indices.len() { + Err(TxError::RawQuery( + "Number of block heights and block indices must be equal" + .to_string(), + )) + } else { + let mut distinct_heights = HashSet::new(); + let parsed = Self { + indices: heights + .into_iter() + .zip(block_indices) + .map(|(h, ix)| { + let ix = Index { + height: h, + block_index: ix, + }; + distinct_heights.insert(h); + ix.validate() + .map_err(|e| TxError::Validation(e.to_string())) + .map(|_| ix) + }) + .collect::, _>>()?, + }; + if distinct_heights.len() > 30 { + Err(TxError::Validation( + "Cannot request more than 30 unique block heights" + .to_string(), + )) + } else { + parsed + .validate() + .map_err(|e| TxError::Validation(e.to_string()))?; + Ok(parsed) + } + } + } +} + +#[derive(Default)] +struct ArgParser<'a>(HashMap<&'a str, &'a str>); + +impl<'b> ArgParser<'b> { + fn parse<'a>(input: &'a str) -> Result, String> + where + 'a: 'b, + { + let mut args = Self::default(); + for kv_pair in input.split('&') { + if let Ok([k, v]) = + <[&str; 2]>::try_from(kv_pair.split('=').collect::>()) + { + args.0.insert(k, v); + } else { + return Err("Could not parse one of the arguments with \ + expected format key=value" + .to_string()); + } + } + Ok(args) + } +} diff --git a/webserver/src/dto/mod.rs b/webserver/src/dto/mod.rs index fb454bd..97a5669 100644 --- a/webserver/src/dto/mod.rs +++ b/webserver/src/dto/mod.rs @@ -1,3 +1,4 @@ +pub mod indices; pub mod notes_index; pub mod tree; pub mod txs; diff --git a/webserver/src/error/tx.rs b/webserver/src/error/tx.rs index 8b69934..819cb1a 100644 --- a/webserver/src/error/tx.rs +++ b/webserver/src/error/tx.rs @@ -8,12 +8,18 @@ use crate::response::api::ApiErrorResponse; pub enum TxError { #[error("Database error: {0}")] Database(String), + #[error("Error parsing API query: {0}")] + RawQuery(String), + #[error("Failed to validate API query: {0}")] + Validation(String), } impl IntoResponse for TxError { fn into_response(self) -> Response { let status_code = match &self { TxError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, + TxError::RawQuery(_) => StatusCode::BAD_REQUEST, + TxError::Validation(_) => StatusCode::BAD_REQUEST, }; ApiErrorResponse::send(status_code.as_u16(), Some(self.to_string())) } diff --git a/webserver/src/handler/tx.rs b/webserver/src/handler/tx.rs index 30bb0e3..adaef3a 100644 --- a/webserver/src/handler/tx.rs +++ b/webserver/src/handler/tx.rs @@ -1,9 +1,10 @@ use axum::Json; -use axum::extract::{Query, State}; +use axum::extract::{Query, RawQuery, State}; use axum_macros::debug_handler; use axum_trace_id::TraceId; use shared::error::InspectWrap; +use crate::dto::indices::IndexQueryParams; use crate::dto::txs::TxQueryParams; use crate::error::tx::TxError; use crate::response::tx::TxResponse; @@ -26,3 +27,20 @@ pub async fn get_tx( Ok(Json(TxResponse::new(txs))) } + +#[debug_handler] +pub async fn get_tx_by_indices( + _trace_id: TraceId, + State(state): State, + raw_query: RawQuery, +) -> Result, TxError> { + let query_params = IndexQueryParams::try_from(raw_query)?; + let txs = state + .tx_service + .get_txs_by_indices(query_params.indices) + .await + .inspect_wrap("get_txs_by_indices", |err| { + TxError::Database(err.to_string()) + })?; + Ok(Json(TxResponse::new(txs))) +} diff --git a/webserver/src/repository/tx.rs b/webserver/src/repository/tx.rs index 3cebae4..21237f7 100644 --- a/webserver/src/repository/tx.rs +++ b/webserver/src/repository/tx.rs @@ -21,6 +21,11 @@ pub trait TxRepositoryTrait { from_block_height: i32, to_block_height: i32, ) -> anyhow::Result>; + + async fn get_txs_by_indices( + &self, + indices: Vec<[i32; 2]>, + ) -> anyhow::Result>; } impl TxRepositoryTrait for TxRepository { @@ -80,4 +85,61 @@ impl TxRepositoryTrait for TxRepository { .await .context_db_interact_error()? } + + async fn get_txs_by_indices( + &self, + indices: Vec<[i32; 2]>, + ) -> anyhow::Result> { + let conn = self.app_state.get_db_connection().await.context( + "Failed to retrieve connection from the pool of database \ + connections", + )?; + let to_block_height = + indices.iter().map(|ix| ix[0]).max().unwrap_or_default(); + + conn.interact(move |conn| { + conn.build_transaction().read_only().run(move |conn| { + let block_height: i32 = chain_state::table + .select(chain_state::dsl::block_height) + .get_result(conn) + .optional() + .with_context(|| { + "Failed to get the latest block height from the \ + database" + })? + .unwrap_or_default(); + if block_height < to_block_height { + anyhow::bail!( + "Requested indices contains {to_block_height}, which \ + exceeds latest block height ({block_height})." + ) + } + let mut query = tx::table.into_boxed(); + for [height, block_index] in &indices { + query = query.or_filter( + tx::dsl::block_height + .eq(height) + .and(tx::dsl::block_index.eq(block_index)), + ); + } + query + .order_by(( + tx::dsl::block_height.asc(), + tx::dsl::block_index.asc(), + tx::dsl::masp_tx_index.asc(), + )) + .select(TxDb::as_select()) + .get_results(conn) + .with_context(|| { + format!( + "Failed to get transations from the database with \ + indices {:?}", + indices + ) + }) + }) + }) + .await + .context_db_interact_error()? + } } diff --git a/webserver/src/response/notes_index.rs b/webserver/src/response/notes_index.rs index fcb7540..2085f6a 100644 --- a/webserver/src/response/notes_index.rs +++ b/webserver/src/response/notes_index.rs @@ -11,10 +11,11 @@ pub struct Note { pub block_index: u64, pub masp_tx_index: u64, pub note_position: u64, + pub is_masp_fee_payment: bool, } impl NotesIndexResponse { - pub fn new(notes_index: Vec<(u64, u64, u64, u64)>) -> Self { + pub fn new(notes_index: Vec<(u64, u64, u64, u64, bool)>) -> Self { Self { notes_index: notes_index .into_iter() @@ -24,12 +25,14 @@ impl NotesIndexResponse { block_index, masp_tx_index, note_position, + is_masp_fee_payment, )| { Note { block_height, block_index, masp_tx_index, note_position, + is_masp_fee_payment, } }, ) diff --git a/webserver/src/response/tx.rs b/webserver/src/response/tx.rs index 49591b6..e3b7cc9 100644 --- a/webserver/src/response/tx.rs +++ b/webserver/src/response/tx.rs @@ -15,12 +15,13 @@ pub struct Tx { #[derive(Clone, Debug, Deserialize, Serialize, Default)] pub struct TxSlot { pub masp_tx_index: u64, + pub is_masp_fee_payment: bool, pub bytes: Vec, } impl TxResponse { pub fn new( - txs: impl IntoIterator)>, u64, u64)>, + txs: impl IntoIterator)>, u64, u64)>, ) -> Self { Self { txs: txs @@ -28,9 +29,12 @@ impl TxResponse { .map(|(batch, block_height, block_index)| Tx { batch: batch .into_iter() - .map(|(masp_tx_index, bytes)| TxSlot { - masp_tx_index, - bytes, + .map(|(masp_tx_index, is_masp_fee_payment, bytes)| { + TxSlot { + masp_tx_index, + is_masp_fee_payment, + bytes, + } }) .collect(), block_height, diff --git a/webserver/src/service/notes_index.rs b/webserver/src/service/notes_index.rs index ee40b3c..ccc45d7 100644 --- a/webserver/src/service/notes_index.rs +++ b/webserver/src/service/notes_index.rs @@ -18,7 +18,7 @@ impl NotesIndexService { pub async fn get_notes_index( &self, from_block_height: u64, - ) -> anyhow::Result> { + ) -> anyhow::Result> { Ok(self .notes_index_repo .get_notes_index(from_block_height as i32) @@ -30,6 +30,7 @@ impl NotesIndexService { notes_index_entry.block_index as u64, notes_index_entry.masp_tx_index as u64, notes_index_entry.note_position as u64, + notes_index_entry.is_masp_fee_payment, ) }) .collect()) diff --git a/webserver/src/service/tx.rs b/webserver/src/service/tx.rs index b853742..dbbd0f9 100644 --- a/webserver/src/service/tx.rs +++ b/webserver/src/service/tx.rs @@ -1,6 +1,7 @@ use itertools::Itertools; use crate::appstate::AppState; +use crate::dto::indices::Index; use crate::repository::tx::{TxRepository, TxRepositoryTrait}; #[derive(Clone)] @@ -19,8 +20,9 @@ impl TxService { &self, from_block_height: u64, to_block_height: u64, - ) -> anyhow::Result)>, u64, u64)>> - { + ) -> anyhow::Result< + impl IntoIterator)>, u64, u64)>, + > { Ok(self .tx_repo .get_txs(from_block_height as i32, to_block_height as i32) @@ -36,7 +38,49 @@ impl TxService { .into_iter() .map(|((block_height, block_index), tx_batch)| { let tx_batch: Vec<_> = tx_batch - .map(|tx| (tx.masp_tx_index as u64, tx.tx_bytes)) + .map(|tx| { + ( + tx.masp_tx_index as u64, + tx.is_masp_fee_payment, + tx.tx_bytes, + ) + }) + .collect(); + (tx_batch, block_height as u64, block_index as u64) + }) + .collect::>()) + } + + pub async fn get_txs_by_indices( + &self, + indices: Vec, + ) -> anyhow::Result< + impl IntoIterator)>, u64, u64)>, + > { + Ok(self + .tx_repo + .get_txs_by_indices( + indices + .into_iter() + .map(|ix| [ix.height as i32, ix.block_index as i32]) + .collect(), + ) + .await? + .into_iter() + .chunk_by(|tx| { + // NB: group batched txs by their slot in a block + (tx.block_height, tx.block_index) + }) + .into_iter() + .map(|((block_height, block_index), tx_batch)| { + let tx_batch: Vec<_> = tx_batch + .map(|tx| { + ( + tx.masp_tx_index as u64, + tx.is_masp_fee_payment, + tx.tx_bytes, + ) + }) .collect(); (tx_batch, block_height as u64, block_index as u64) })