diff --git a/Cargo.lock b/Cargo.lock index bbd5af9..29f0621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -69,19 +69,19 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72832d73be48bac96a5d7944568f305d829ed55b0ce3b483647089dfaf6cf704" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.12", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -119,117 +119,139 @@ 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 = "anchor-attribute-access-control" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa5be5b72abea167f87c868379ba3c2be356bfca9e6f474fd055fa0f7eeb4f2" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" dependencies = [ "anchor-syn", - "anyhow", - "proc-macro2 1.0.78", - "quote 1.0.35", - "regex", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-attribute-account" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f468970344c7c9f9d03b4da854fd7c54f21305059f53789d0045c1dd803f0018" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" dependencies = [ "anchor-syn", - "anyhow", - "bs58 0.5.0", - "proc-macro2 1.0.78", - "quote 1.0.35", - "rustversion", + "bs58 0.5.1", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-attribute-constant" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59948e7f9ef8144c2aefb3f32a40c5fce2798baeec765ba038389e82301017ef" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" dependencies = [ "anchor-syn", - "proc-macro2 1.0.78", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-attribute-error" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc753c9d1c7981cb8948cf7e162fb0f64558999c0413058e2d43df1df5448086" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" dependencies = [ "anchor-syn", - "proc-macro2 1.0.78", - "quote 1.0.35", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-attribute-event" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38b4e172ba1b52078f53fdc9f11e3dc0668ad27997838a0aad2d148afac8c97" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" dependencies = [ "anchor-syn", - "anyhow", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-attribute-program" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eebd21543606ab61e2d83d9da37d24d3886a49f390f9c43a1964735e8c0f0d5" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" dependencies = [ "anchor-syn", - "anyhow", - "proc-macro2 1.0.78", - "quote 1.0.35", + "quote", "syn 1.0.109", ] +[[package]] +name = "anchor-client" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb48c4a7911038da546dc752655a29fa49f6bd50ebc1edca218bac8da1012acd" +dependencies = [ + "anchor-lang", + "anyhow", + "futures", + "regex", + "serde", + "solana-account-decoder", + "solana-client", + "solana-sdk", + "thiserror", + "tokio", + "url", +] + [[package]] name = "anchor-derive-accounts" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4720d899b3686396cced9508f23dab420f1308344456ec78ef76f98fda42af" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" dependencies = [ "anchor-syn", - "anyhow", - "proc-macro2 1.0.78", - "quote 1.0.35", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.3", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-derive-space" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f495e85480bd96ddeb77b71d499247c7d4e8b501e75ecb234e9ef7ae7bd6552a" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "anchor-lang" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2d4b20100f1310a774aba3471ef268e5c4ba4d5c28c0bbe663c2658acbc414" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" dependencies = [ "anchor-attribute-access-control", "anchor-attribute-account", @@ -238,28 +260,29 @@ dependencies = [ "anchor-attribute-event", "anchor-attribute-program", "anchor-derive-accounts", + "anchor-derive-serde", "anchor-derive-space", "arrayref", "base64 0.13.1", "bincode", "borsh 0.10.3", "bytemuck", - "getrandom 0.2.12", + "getrandom 0.2.15", "solana-program", "thiserror", ] [[package]] name = "anchor-syn" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a125e4b0cc046cfec58f5aa25038e34cf440151d58f0db3afc55308251fe936d" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" dependencies = [ "anyhow", - "bs58 0.5.0", + "bs58 0.5.1", "heck 0.3.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "serde", "serde_json", "sha2 0.10.8", @@ -293,47 +316,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -341,9 +365,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-bn254" @@ -368,7 +392,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools 0.10.5", + "itertools", "num-traits", "zeroize", ] @@ -385,8 +409,8 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools 0.10.5", - "num-bigint 0.4.4", + "itertools", + "num-bigint 0.4.6", "num-traits", "paste", "rustc_version", @@ -399,7 +423,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "quote 1.0.35", + "quote", "syn 1.0.109", ] @@ -409,10 +433,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ - "num-bigint 0.4.4", + "num-bigint 0.4.6", "num-traits", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -438,7 +462,7 @@ dependencies = [ "ark-serialize-derive", "ark-std", "digest 0.10.7", - "num-bigint 0.4.4", + "num-bigint 0.4.6", ] [[package]] @@ -447,8 +471,8 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -462,12 +486,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "array-bytes" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad284aeb45c13f2fb4f084de4a420ebf447423bdf9386c0540ce33cb3ef4b8c" - [[package]] name = "arrayref" version = "0.3.7" @@ -508,8 +526,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", "synstructure", ] @@ -520,8 +538,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -537,7 +555,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ - "quote 1.0.35", + "quote", "syn 1.0.109", ] @@ -554,22 +572,21 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.1.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compression" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "brotli", "flate2", @@ -581,15 +598,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.2.0", + "fastrand 2.1.0", + "futures-lite 2.3.0", "slab", ] @@ -599,12 +615,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.0", + "async-channel 2.3.1", "async-executor", - "async-io 2.3.1", - "async-lock 3.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", "blocking", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "once_cell", "tokio", ] @@ -631,18 +647,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ - "async-lock 3.3.0", + "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "parking", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.7.2", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -659,12 +675,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.3.1", + "event-listener-strategy", "pin-project-lite", ] @@ -721,26 +737,26 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[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 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -761,6 +777,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -780,15 +805,27 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backon" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67782c3f868daa71d3533538e98a8e13713231969def7536e8039606fc46bf0" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "pin-project", + "tokio", +] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -807,8 +844,8 @@ checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -832,9 +869,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -859,9 +896,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bitmaps" @@ -886,9 +926,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -925,39 +965,50 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blockbuster" -version = "1.0.1" -source = "git+https://github.com/rpcpool/blockbuster?branch=rm-plerkle-101#9f70f30bfa9f9fa8830d0b9d0877fbc825c49da7" +version = "2.3.0" dependencies = [ "anchor-lang", "async-trait", "borsh 0.10.3", "bs58 0.4.0", + "bytemuck", + "flatbuffers", "lazy_static", "log", "mpl-bubblegum", + "mpl-core", "mpl-token-metadata", + "plerkle_serialization", + "rand 0.8.5", + "serde", + "serde_json", + "solana-client", + "solana-geyser-plugin-interface", "solana-sdk", "solana-transaction-status", + "solana-zk-token-sdk", "spl-account-compression", + "spl-concurrent-merkle-tree", "spl-noop", + "spl-pod", "spl-token", + "spl-token-2022", + "spl-token-group-interface", + "spl-token-metadata-interface", "thiserror", ] [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.2.0", - "async-lock 3.3.0", + "async-channel 2.3.1", "async-task", - "fastrand 2.0.1", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "piper", - "tracing", ] [[package]] @@ -982,11 +1033,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ - "borsh-derive 1.3.1", + "borsh-derive 1.5.1", "cfg_aliases", ] @@ -999,7 +1050,7 @@ dependencies = [ "borsh-derive-internal 0.9.3", "borsh-schema-derive-internal 0.9.3", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.78", + "proc-macro2", "syn 1.0.109", ] @@ -1012,21 +1063,21 @@ dependencies = [ "borsh-derive-internal 0.10.3", "borsh-schema-derive-internal 0.10.3", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.78", + "proc-macro2", "syn 1.0.109", ] [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", "syn_derive", ] @@ -1036,8 +1087,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -1047,8 +1098,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -1058,8 +1109,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -1069,16 +1120,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "brotli" -version = "3.4.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1087,9 +1138,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1103,18 +1154,18 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] [[package]] name = "bumpalo" -version = "3.15.2" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b1be7772ee4501dba05acbe66bb1e8760f6a6c474a36035631638e4415f130" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bv" @@ -1143,29 +1194,29 @@ version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -1176,9 +1227,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 = "cadence" @@ -1210,12 +1261,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1226,15 +1278,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[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", @@ -1242,7 +1294,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -1287,9 +1339,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -1297,26 +1349,26 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", - "strsim 0.11.0", + "clap_lex 0.7.1", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -1330,15 +1382,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "combine" @@ -1355,9 +1407,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1407,6 +1459,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1434,9 +1492,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -1449,18 +1507,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -1495,9 +1553,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -1550,9 +1608,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.6" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -1560,34 +1618,108 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.6" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.78", - "quote 1.0.35", - "strsim 0.10.0", - "syn 2.0.50", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.68", ] [[package]] name = "darling_macro" -version = "0.20.6" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", - "quote 1.0.35", - "syn 2.0.50", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "das-bubblegum-backfill" +version = "0.1.0" +dependencies = [ + "anchor-client", + "anyhow", + "blockbuster", + "borsh 0.10.3", + "bs58 0.4.0", + "clap 4.5.7", + "das-core", + "digital_asset_types", + "futures", + "heck 0.5.0", + "log", + "mpl-bubblegum", + "num-traits", + "program_transformers", + "sea-orm 0.10.7", + "serde_json", + "solana-client", + "solana-program", + "solana-sdk", + "solana-transaction-status", + "spl-account-compression", + "spl-token", + "sqlx 0.6.3", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "das-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "backon", + "borsh 0.10.3", + "bs58 0.4.0", + "cadence", + "cadence-macros", + "clap 4.5.7", + "derive_more", + "digital_asset_types", + "figment", + "futures", + "indicatif", + "log", + "plerkle_messenger", + "reqwest", + "sea-orm 0.10.7", + "serde_json", + "solana-account-decoder", + "solana-client", + "solana-sdk", + "solana-transaction-status", + "spl-account-compression", + "sqlx 0.6.3", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" @@ -1607,7 +1739,7 @@ dependencies = [ "asn1-rs", "displaydoc", "nom", - "num-bigint 0.4.4", + "num-bigint 0.4.6", "num-traits", "rusticata-macros", ] @@ -1634,11 +1766,24 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.68", +] + [[package]] name = "dialoguer" version = "0.10.4" @@ -1722,36 +1867,36 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] -name = "dlopen" -version = "0.1.8" +name = "dlopen2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" dependencies = [ - "dlopen_derive", - "lazy_static", + "dlopen2_derive", "libc", + "once_cell", "winapi", ] [[package]] -name = "dlopen_derive" -version = "0.1.4" +name = "dlopen2_derive" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" +checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ - "libc", - "quote 0.6.13", - "syn 0.15.44", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -1768,9 +1913,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "eager" @@ -1815,9 +1960,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] @@ -1830,9 +1975,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[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", ] @@ -1848,13 +1993,13 @@ dependencies = [ [[package]] name = "enum-iterator-derive" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -1878,9 +2023,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1905,20 +2050,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.1.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -1927,21 +2061,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.1.0", + "event-listener 5.3.1", "pin-project-lite", ] @@ -1956,9 +2080,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "feature-probe" @@ -1967,10 +2091,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" [[package]] -name = "finl_unicode" -version = "1.2.0" +name = "figment" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "serde", + "uncased", + "version_check", +] [[package]] name = "flatbuffers" @@ -1984,9 +2114,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -2089,7 +2219,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -2115,11 +2245,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-core", "futures-io", "parking", @@ -2132,9 +2262,9 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -2203,9 +2333,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -2216,9 +2346,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -2251,9 +2381,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2261,7 +2391,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.3", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2301,16 +2431,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.11", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.11", "allocator-api2", ] @@ -2320,7 +2450,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.5", ] [[package]] @@ -2341,6 +2471,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2352,9 +2488,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -2418,9 +2560,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", @@ -2440,9 +2582,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -2458,9 +2600,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -2473,7 +2615,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2489,7 +2631,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.10", + "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] @@ -2575,12 +2717,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.5", "serde", ] @@ -2603,16 +2745,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -2623,7 +2765,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.6", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -2635,43 +2777,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "itertools" -version = "0.10.5" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" -version = "0.12.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[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.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 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", ] @@ -2704,9 +2843,9 @@ dependencies = [ [[package]] name = "kaigan" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e623cca1f0e2a0919032c1bdabbf81dd9aa34658d5066aca7bb90d608317ab91" +checksum = "b6dd100976df9dd59d0c3fecf6f9ad3f161a087374d1b2a77ebb4ad8920f11bb" dependencies = [ "borsh 0.10.3", ] @@ -2731,25 +2870,24 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[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.6.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -2800,30 +2938,58 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint 0.4.6", + "thiserror", +] + [[package]] name = "lightdas" version = "0.1.0" dependencies = [ + "anchor-client", + "anchor-lang", "anyhow", "async-trait", - "base64 0.22.0", + "backon", + "base64 0.22.1", "blockbuster", "borsh 0.10.3", "borsh-derive 0.10.3", "bs58 0.4.0", + "bytemuck", + "clap 4.5.7", + "das-bubblegum-backfill", + "das-core", + "derive_more", "digital_asset_types", "dotenv", + "figment", + "flatbuffers", "futures", + "heck 0.5.0", "indexmap 1.9.3", + "indicatif", "jsonpath_lib", "lazy_static", "log", "mime_guess", "mpl-bubblegum", + "mpl-core", + "mpl-token-metadata", "num-derive 0.3.3", "num-traits", + "plerkle_messenger", "plerkle_serialization", "program_transformers", + "rand 0.8.5", "reqwest", "schemars", "schemars_derive", @@ -2831,11 +2997,21 @@ dependencies = [ "sea-query 0.28.5", "serde", "serde_json", + "signal-hook", + "solana-account-decoder", "solana-client", + "solana-geyser-plugin-interface", + "solana-program", "solana-rpc-client-api", "solana-sdk", "solana-transaction-status", + "solana-zk-token-sdk", "spl-concurrent-merkle-tree", + "spl-noop", + "spl-pod", + "spl-token-2022", + "spl-token-group-interface", + "spl-token-metadata-interface", "sqlx 0.6.3", "thiserror", "tokio", @@ -2850,15 +3026,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2866,9 +3042,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] @@ -2894,9 +3070,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -2918,9 +3094,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -2969,29 +3145,50 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "mpl-bubblegum" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3cbca5deb859e66a1a21ada94f2eaab3eb5caa4584c0c8ade0efac29a5414b8" +checksum = "a9eff5ae5cafd1acdf7e7c93359da1eec91dcaede318470d9f68b78e8b7469f4" dependencies = [ "borsh 0.10.3", "kaigan", @@ -3001,28 +3198,46 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mpl-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefa4362d05dec8fb97ebebf20fb2dc5224228c8c9550d9da8f2d1b657b49954" +dependencies = [ + "base64 0.22.1", + "borsh 0.10.3", + "modular-bitfield", + "num-derive 0.3.3", + "num-traits", + "rmp-serde", + "serde", + "serde_json", + "serde_with 3.8.1", + "solana-program", + "thiserror", +] + [[package]] name = "mpl-token-metadata" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b2de608098eb2ef2a5392069dea83084967e25a4d69d0380a6bb02454fc0fe" +checksum = "caf0f61b553e424a6234af1268456972ee66c2222e1da89079242251fa7479e5" dependencies = [ "borsh 0.10.3", "num-derive 0.3.3", "num-traits", "serde", - "serde_with 3.6.1", + "serde_with 3.8.1", "solana-program", "thiserror", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -3084,11 +3299,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3115,8 +3329,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -3126,9 +3340,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -3142,9 +3356,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -3165,9 +3379,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -3178,7 +3392,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.6", + "hermit-abi 0.3.9", "libc", ] @@ -3207,9 +3421,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate 1.3.1", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -3219,9 +3433,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -3232,9 +3446,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -3256,9 +3470,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" @@ -3266,7 +3480,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -3281,9 +3495,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -3294,9 +3508,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", @@ -3348,8 +3562,8 @@ checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -3361,9 +3575,9 @@ checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -3385,12 +3599,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -3409,22 +3623,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" @@ -3468,11 +3682,31 @@ dependencies = [ "num", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[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" @@ -3482,12 +3716,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-io", ] @@ -3514,11 +3748,29 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plerkle_messenger" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94c75e3178528c45bca0b7d515bdc6a3733b5f67d5b37b8a085cf6d7f81e3f2" +dependencies = [ + "async-mutex", + "async-trait", + "blake3", + "cadence", + "cadence-macros", + "figment", + "futures", + "log", + "serde", + "thiserror", +] + [[package]] name = "plerkle_serialization" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f021e409a6a1ec8b7a325db27254e7fa3942e845cfe96f5f8f494977f2646a8" +checksum = "f832646491065468aa8e222b47d41dd5250e4be7866725bef5f0d31c64538f5f" dependencies = [ "bs58 0.4.0", "chrono", @@ -3547,14 +3799,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.5.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.31", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -3624,8 +3877,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", "version_check", ] @@ -3636,25 +3889,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "version_check", ] [[package]] name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid 0.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -3667,8 +3911,10 @@ dependencies = [ "bs58 0.4.0", "cadence", "cadence-macros", + "das-core", "digital_asset_types", "futures", + "heck 0.5.0", "mpl-bubblegum", "num-traits", "sea-orm 0.10.7", @@ -3698,8 +3944,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -3712,72 +3958,72 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "quinn" -version = "0.9.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8b432585672228923edbbf64b8b12c14e1112f62e88737655b4a083dbcd78e" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.20.9", + "rustls 0.21.12", "thiserror", "tokio", "tracing", - "webpki", ] [[package]] name = "quinn-proto" -version = "0.9.6" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", "rand 0.8.5", "ring 0.16.20", "rustc-hash", - "rustls 0.20.9", + "rustls 0.21.12", "rustls-native-certs", "slab", "thiserror", "tinyvec", "tracing", - "webpki", ] [[package]] name = "quinn-udp" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ + "bytes", "libc", - "quinn-proto", - "socket2 0.4.10", + "socket2 0.5.7", "tracing", - "windows-sys 0.42.0", -] - -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", + "windows-sys 0.48.0", ] [[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 1.0.78", + "proc-macro2", ] [[package]] @@ -3845,7 +4091,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", ] [[package]] @@ -3868,9 +4114,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -3916,27 +4162,36 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.6.0", +] + [[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 0.2.12", + "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -3950,13 +4205,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", ] [[package]] @@ -3967,9 +4222,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rend" @@ -3982,9 +4237,9 @@ dependencies = [ [[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 = [ "async-compression", "base64 0.21.7", @@ -4006,7 +4261,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-pemfile", "serde", "serde_json", @@ -4049,7 +4304,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.12", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -4080,11 +4335,33 @@ version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rpassword" version = "7.3.1" @@ -4108,12 +4385,12 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.34.3" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", - "borsh 1.3.1", + "borsh 1.5.1", "bytes", "num-traits", "rand 0.8.5", @@ -4124,9 +4401,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -4168,14 +4445,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] @@ -4193,9 +4470,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -4236,15 +4513,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -4257,9 +4534,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -4269,14 +4546,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] @@ -4300,9 +4577,9 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -4323,9 +4600,9 @@ checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -4358,20 +4635,20 @@ dependencies = [ [[package]] name = "sea-orm" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6632f499b80cc6aaa781b302e4c9fae663e0e3dcf2640e9d80034d5b10731efe" +checksum = "c8814e37dc25de54398ee62228323657520b7f29713b8e238649385dbe473ee0" dependencies = [ "async-stream", "async-trait", "futures", "log", "ouroboros 0.17.2", - "sea-orm-macros 0.12.14", + "sea-orm-macros 0.12.15", "sea-query 0.30.7", "sea-query-binder 0.5.0", "serde", - "sqlx 0.7.2", + "sqlx 0.7.4", "strum", "thiserror", "tracing", @@ -4380,12 +4657,12 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465ea2308d4716837e9af4a2cff8e14c28135867a580bb93e9e03d408a3a6afb" +checksum = "620bc560062ae251b1366bde43b3f1508445cab5c2c8cbdb397034638ab1b357" dependencies = [ "chrono", - "clap 4.5.1", + "clap 4.5.7", "dotenvy", "glob", "regex", @@ -4403,36 +4680,36 @@ checksum = "7216195de9c6b2474fd0efab486173dccd0eff21f28cc54aa4c0205d52fb3af0" dependencies = [ "bae", "heck 0.3.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "sea-orm-macros" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec13bfb4c4aef208f68dbea970dd40d13830c868aa8dcb4e106b956e6bb4f2fa" +checksum = "5e115c6b078e013aa963cc2d38c196c2c40b05f03d0ac872fe06b6e0d5265603" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "sea-bae", - "syn 2.0.50", + "syn 2.0.68", "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac734b6e5610c2764056cc8495fbc293cd1c8ebe084fdfb74c3b0cdaaff9bb92" +checksum = "ee8269bc6ff71afd6b78aa4333ac237a69eebd2cdb439036291e64fb4b8db23c" dependencies = [ "async-trait", - "clap 4.5.1", + "clap 4.5.7", "dotenvy", "futures", - "sea-orm 0.12.14", + "sea-orm 0.12.15", "sea-orm-cli", "sea-schema", "tracing", @@ -4496,7 +4773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" dependencies = [ "sea-query 0.30.7", - "sqlx 0.7.2", + "sqlx 0.7.4", ] [[package]] @@ -4506,8 +4783,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cdc022b4f606353fe5dc85b09713a04e433323b70163e81513b141c6ae6eb5" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", "thiserror", ] @@ -4519,8 +4796,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", "thiserror", ] @@ -4532,9 +4809,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", "thiserror", ] @@ -4556,8 +4833,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f686050f76bffc4f635cda8aea6df5548666b830b52387e8bc7de11056d11e" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -4577,8 +4854,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "rustversion", "syn 1.0.109", ] @@ -4591,11 +4868,11 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -4604,9 +4881,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -4614,57 +4891,57 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -4694,19 +4971,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.3", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.6.1", + "serde_with_macros 3.8.1", "time", ] @@ -4717,32 +4994,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", -] - -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -4817,11 +5083,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[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", ] @@ -4859,9 +5135,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" @@ -4875,19 +5151,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "solana-account-decoder" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a23e502edf181ac548d05d0e0c29aa4285e05cf082fcc66c9315fe3d1a89bc" +checksum = "21ed570fba6f909f69c888b48b39c7e61b454e3594e448d0dad9d973f27f5668" dependencies = [ "Inflector", "base64 0.21.7", @@ -4898,47 +5174,25 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-address-lookup-table-program", "solana-config-program", "solana-sdk", "spl-token", "spl-token-2022", + "spl-token-group-interface", "spl-token-metadata-interface", "thiserror", "zstd", ] -[[package]] -name = "solana-address-lookup-table-program" -version = "1.16.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf919521094c0489485589d01674e993ebb1d518abc8da439e31d7f5a72406f" -dependencies = [ - "bincode", - "bytemuck", - "log", - "num-derive 0.3.3", - "num-traits", - "rustc_version", - "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-program", - "solana-program-runtime", - "solana-sdk", - "thiserror", -] - [[package]] name = "solana-clap-utils" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af272571b1bcdbcd0096ba11b8f37f0d79d7118aedca753b8c4c52e1a69e8bf" +checksum = "b4729fec3c2ac37b7daaf24c1ef879bbedbff3495b1ac728d9b627282d878753" dependencies = [ "chrono", "clap 2.34.0", "rpassword", - "solana-perf", "solana-remote-wallet", "solana-sdk", "thiserror", @@ -4949,19 +5203,19 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e626e74c0084e7d42d232aa1c20afcd78663abab19f19f349aba714f82aa13a" +checksum = "2da13019a833940af2edebda969db4337ab11c6fb220eb0d4c02d79c83ae8034" dependencies = [ "async-trait", "bincode", + "dashmap", "futures", "futures-util", - "indexmap 1.9.3", + "indexmap 2.2.6", "indicatif", "log", "quinn", - "rand 0.7.3", "rayon", "solana-connection-cache", "solana-measure", @@ -4982,9 +5236,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f0a4b21581e08419c27458f275bb24bf9b70990756d1ca13c3ef21c56f2750" +checksum = "04b91ca968a63946e7513a1de20188e6e917f09136339ee3bec247aa0e985d36" dependencies = [ "bincode", "chrono", @@ -4996,16 +5250,17 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e97478ee9a977079863c2530fd4543d9a6e5f1c7fb306fa1557d9c72506b5" +checksum = "49a850c0122f094efb83df00ab080ab6ace0dcd8dbf91240f91832157ee6d460" dependencies = [ "async-trait", "bincode", + "crossbeam-channel", "futures-util", - "indexmap 1.9.3", + "indexmap 2.2.6", "log", - "rand 0.7.3", + "rand 0.8.5", "rayon", "rcgen", "solana-measure", @@ -5017,11 +5272,11 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a96a0a64cbc75e06c8eb49d6cd0a87054c48dbf59100d9959389aba51fb501" +checksum = "8e2c5e5dde22cac045d29675b3fefa84817e1f63b0b911d094c599e80c0c07d9" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.11", "blake3", "block-buffer 0.10.4", "bs58 0.4.0", @@ -5030,13 +5285,10 @@ dependencies = [ "cc", "either", "generic-array", - "getrandom 0.1.16", "im", "lazy_static", "log", "memmap2", - "once_cell", - "rand_core 0.6.4", "rustc_version", "serde", "serde_bytes", @@ -5050,21 +5302,33 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae9765c963bebbf609dcdc82cfb969746cbdf6d65cfc9d94112e984475ae4ac" +checksum = "296e4cf0e2479e4c21afe4d17e32526f71f1bcd93b1c7c660900bc3e4233447a" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "rustc_version", - "syn 2.0.50", + "syn 2.0.68", +] + +[[package]] +name = "solana-geyser-plugin-interface" +version = "1.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d910a096a3eac2546c4d654158c2fad8b88d5b733c1225d876de144a09fa262" +dependencies = [ + "log", + "solana-sdk", + "solana-transaction-status", + "thiserror", ] [[package]] name = "solana-logger" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c039e4336890c7a0667207caeb452187efa0602d80eca61a854f2e39915e972" +checksum = "d37a1b1a383a01039afbc6447a1712fb2a1a73a5ba8916762e693e8e492fabf3" dependencies = [ "env_logger", "lazy_static", @@ -5073,9 +5337,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f188a8cf40c33a33cdf5396af88df194648ef52914fcfb5ce81e4cf03ab99ff" +checksum = "19831a93d760205f5c3e20d05a37b0e533caa1889e48041648ad0859e68ec336" dependencies = [ "log", "solana-sdk", @@ -5083,9 +5347,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0e6253c106dbe0a6736c9b6929b8284aa959f79486006330884252e486b515" +checksum = "f63c23a8db755b2903262ad473e32cbf0093e2d3a0a7b8183d797a182c08326a" dependencies = [ "crossbeam-channel", "gethostname", @@ -5093,23 +5357,24 @@ dependencies = [ "log", "reqwest", "solana-sdk", + "thiserror", ] [[package]] name = "solana-net-utils" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d33faefadf14f19710e5229a2f812a4ea3c1b173ca0043225761de663feab35" +checksum = "29ac1afc7feb590b45fd72bee0ca4c4f24b2386184d7e00d9f0d17913655bb4a" dependencies = [ "bincode", "clap 3.2.25", "crossbeam-channel", "log", "nix", - "rand 0.7.3", + "rand 0.8.5", "serde", "serde_derive", - "socket2 0.4.10", + "socket2 0.5.7", "solana-logger", "solana-sdk", "solana-version", @@ -5119,25 +5384,27 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a8b766c9ee568681462592f89161628e0a4493ed1b6a5a5c8cf3b94c80b98" +checksum = "bfdf5a429e018e8ba693f4c43f833192db421fe97b88dfaf97041aa258e4b191" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.11", "bincode", "bv", "caps", "curve25519-dalek", - "dlopen", - "dlopen_derive", + "dlopen2", "fnv", "lazy_static", "libc", "log", "nix", - "rand 0.7.3", + "rand 0.8.5", "rayon", + "rustc_version", "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", @@ -5146,18 +5413,17 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c16757f43d4384907f6e81924e155b8bd8228efcdcf9ea38bd4e18ea100804" +checksum = "8e3a3b9623f09e2c480b4e129c92d7a036f8614fd0fc7519791bd44e64061ce8" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", "ark-serialize", - "array-bytes", "base64 0.21.7", "bincode", - "bitflags 1.3.2", + "bitflags 2.6.0", "blake3", "borsh 0.10.3", "borsh 0.9.3", @@ -5168,20 +5434,20 @@ dependencies = [ "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.2.12", - "itertools 0.10.5", + "getrandom 0.2.15", + "itertools", "js-sys", "lazy_static", "libc", "libsecp256k1", + "light-poseidon", "log", - "memoffset 0.9.0", - "num-bigint 0.4.4", + "memoffset 0.9.1", + "num-bigint 0.4.6", "num-derive 0.3.3", "num-traits", - "parking_lot 0.12.1", - "rand 0.7.3", - "rand_chacha 0.2.2", + "parking_lot 0.12.3", + "rand 0.8.5", "rustc_version", "rustversion", "serde", @@ -5201,21 +5467,21 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd12d233724478c7ccf84d953a4abe891552d78bd41e2c594c098f76dde5a06" +checksum = "9d5dbb56d36cc15b4cf5a71c0ce6262a263212f7a312b0dbc41b226654329c37" dependencies = [ "base64 0.21.7", "bincode", "eager", "enum-iterator", - "itertools 0.10.5", + "itertools", "libc", "log", "num-derive 0.3.3", "num-traits", "percentage", - "rand 0.7.3", + "rand 0.8.5", "rustc_version", "serde", "solana-frozen-abi", @@ -5229,9 +5495,9 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "319fcfa162e2d01baf890986d1c9fc549acfdac19e0de07da80a7ef591aa1241" +checksum = "2c22290c0d296a6a250a8d5b680797f12138a81af9c403a6ce62bd3ddad307e6" dependencies = [ "crossbeam-channel", "futures-util", @@ -5254,21 +5520,20 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88f6f70a2b0541855467cd3ead7631e7c4132e7f327965dae0d9649522acb3a" +checksum = "f924d8722f9e910d790678a79c2a0bfed786dffe1aefa5d769f8548679794263" dependencies = [ "async-mutex", "async-trait", "futures", - "itertools 0.10.5", + "itertools", "lazy_static", "log", "quinn", "quinn-proto", - "quinn-udp", "rcgen", - "rustls 0.20.9", + "rustls 0.21.12", "solana-connection-cache", "solana-measure", "solana-metrics", @@ -5282,9 +5547,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba72e563531528d6e36090477d6b638ba1dfdbe04e6a97bf9e5a9f46e90837" +checksum = "bc0a2e484e5b272690ac1431a6821f2b5180149d67c56934d9e007224ced15d0" dependencies = [ "lazy_static", "num_cpus", @@ -5292,16 +5557,16 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1924c23c5a1ca48e1d285b43d2bfe351ac9de3a93b46a2f34972cba8a3688934" +checksum = "fb9a96d1c001d07a0abb08e05b92ff6528b2d9239d03c57f99f738527839eb12" dependencies = [ "console", "dialoguer", "log", "num-derive 0.3.3", "num-traits", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "qstring", "semver", "solana-sdk", @@ -5311,9 +5576,9 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981e4cd5d5184e566568e16ed59dde6ee9f88b62fe2f0b25d72873af0302b354" +checksum = "91503edfdb2ba9c5e0127048e7795f22e050cf2bcee1259361af113d533b4b26" dependencies = [ "async-trait", "base64 0.21.7", @@ -5337,9 +5602,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e894b7a7025c782f97967b071d84f9219bb6ce53ef0794c756af9cc8bde5833e" +checksum = "131662e5eea4fa5fc88b01f07d9e430315c0976be848ba3994244249c5fb033a" dependencies = [ "base64 0.21.7", "bs58 0.4.0", @@ -5359,9 +5624,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d69307d1f7fb297ea82e25d9cd1d83297140ce872009557fd57763511497cb5" +checksum = "f67cdff955b9994ae240f6f287420c6727a581120c02ccc4f2fa535886732a1d" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -5372,14 +5637,14 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf179cda65775982e5f31da8d58dfa4349c3b5ae1573ceb507c7fc91df66690" +checksum = "cb34583922c5e79004ad8d8d69f333d274d21b614f0e1a575f325fc29a104ec2" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", - "bitflags 1.3.2", + "bitflags 2.6.0", "borsh 0.10.3", "bs58 0.4.0", "bytemuck", @@ -5391,7 +5656,7 @@ dependencies = [ "ed25519-dalek-bip32", "generic-array", "hmac 0.12.1", - "itertools 0.10.5", + "itertools", "js-sys", "lazy_static", "libsecp256k1", @@ -5402,8 +5667,9 @@ dependencies = [ "num_enum 0.6.1", "pbkdf2 0.11.0", "qstring", + "qualifier_attr", "rand 0.7.3", - "rand_chacha 0.2.2", + "rand 0.8.5", "rustc_version", "rustversion", "serde", @@ -5425,30 +5691,36 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f188d6fe76183137a4922274acbe76fc4fcc474b3fad860bb2612cb225e6e2a" +checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "rustversion", - "syn 2.0.50", + "syn 2.0.68", ] +[[package]] +name = "solana-security-txt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" + [[package]] name = "solana-streamer" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69308e9f79ff1e7ca55d32e5521ab1b11e5812830c8e3733dfa0a7021c6ddb24" +checksum = "efe4c33e0f68ea7a3701650badf6753b85fef2100cac6bc187c8e443e61c53da" dependencies = [ "async-channel 1.9.0", "bytes", "crossbeam-channel", "futures-util", "histogram", - "indexmap 1.9.3", - "itertools 0.10.5", + "indexmap 2.2.6", + "itertools", "libc", "log", "nix", @@ -5457,10 +5729,9 @@ dependencies = [ "pkcs8", "quinn", "quinn-proto", - "quinn-udp", - "rand 0.7.3", + "rand 0.8.5", "rcgen", - "rustls 0.20.9", + "rustls 0.21.12", "solana-metrics", "solana-perf", "solana-sdk", @@ -5471,9 +5742,9 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27fe04e9479ba32c2b07a75975458b71abefde1a0a2ba23bbcdb4a0134234ef2" +checksum = "54e782aabf9443a36d65e74d70ce732cc844707a5fec5a498bcbd81d3de7598c" dependencies = [ "bincode", "log", @@ -5486,17 +5757,16 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5abc9d7b1071f978d231bcb184c801008415d9917bad48c08b09c538a9ee30" +checksum = "980bee30cbfe3c51f973da7fdcccb9df2c2d9b9175c06066b293499e02108fd4" dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 1.9.3", + "indexmap 2.2.6", "indicatif", "log", - "rand 0.7.3", "rayon", "solana-connection-cache", "solana-measure", @@ -5511,9 +5781,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff2f528270e438cd30b47ae81fc14b40162bc67794a65e30abec769322daeaf" +checksum = "4c180013e406418d593ce7b51da7007a638ace18261de14901b090e53a1d7025" dependencies = [ "Inflector", "base64 0.21.7", @@ -5526,7 +5796,6 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", - "solana-address-lookup-table-program", "solana-sdk", "spl-associated-token-account", "spl-memo", @@ -5537,9 +5806,9 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8723428368dd66b462a1e8ccd98c8d8bb020ad9dfbd051ed31a39857f89c17d" +checksum = "ab995970a424c89b7966a01aec90cdf1685c49aacf38a5f463200fc273a7d86b" dependencies = [ "async-trait", "solana-connection-cache", @@ -5552,9 +5821,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20bbadb84e29d645ae2d252a28eb0c7a98d656608ffcbab5a3b18cf5d509733" +checksum = "b32cc394aa7132ab7f270801b98bf47fa585ab93f1038e5be27e480d7b5b2dca" dependencies = [ "log", "rustc_version", @@ -5568,9 +5837,9 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee544e1d43fde9135dfbc43707f1874dd2c44f5d786d9fd6077b5e57ae602109" +checksum = "589cad4dccb4392e23f5ae4ccdd1f0aaa10f2823b264b27c4feb6382f40f4fd4" dependencies = [ "bincode", "log", @@ -5590,9 +5859,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.16.27" +version = "1.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9513c4f1595b61f8a39a14c27535da2b2145c20c6b35ab491a1830981972b2bd" +checksum = "03d932d7b13a223a6c1068d7061df7e9d2de14bfc0a874350eef19d59086b04a" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -5601,7 +5870,7 @@ dependencies = [ "byteorder", "curve25519-dalek", "getrandom 0.1.16", - "itertools 0.10.5", + "itertools", "lazy_static", "merlin", "num-derive 0.3.3", @@ -5619,9 +5888,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d4ba1e58947346e360fabde0697029d36ba83c42f669199b16a8931313cf29" +checksum = "3d457cc2ba742c120492a64b7fa60e22c575e891f6b55039f4d736568fb112a3" dependencies = [ "byteorder", "combine", @@ -5660,9 +5929,9 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df052fd79e45c75c84ef20f5f2dcf037aeed230d0f2400bf68fa388cc0ece240" +checksum = "85c43bd4455d9fb29b9e4f83c087ccffa2f6f41fecfc0549932ae391d00f3378" dependencies = [ "anchor-lang", "bytemuck", @@ -5672,9 +5941,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", @@ -5714,9 +5983,9 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ - "quote 1.0.35", + "quote", "spl-discriminator-syn", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -5725,10 +5994,10 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "sha2 0.10.8", - "syn 2.0.50", + "syn 2.0.68", "thiserror", ] @@ -5756,8 +6025,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" dependencies = [ + "base64 0.21.7", "borsh 0.10.3", "bytemuck", + "serde", "solana-program", "solana-zk-token-sdk", "spl-program-error", @@ -5782,17 +6053,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "sha2 0.10.8", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "spl-tlv-account-resolution" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" dependencies = [ "bytemuck", "solana-program", @@ -5819,9 +6090,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", @@ -5829,16 +6100,31 @@ dependencies = [ "num-traits", "num_enum 0.7.2", "solana-program", + "solana-security-txt", "solana-zk-token-sdk", "spl-memo", "spl-pod", "spl-token", + "spl-token-group-interface", "spl-token-metadata-interface", "spl-transfer-hook-interface", "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "spl-token-metadata-interface" version = "0.2.0" @@ -5855,9 +6141,9 @@ dependencies = [ [[package]] name = "spl-transfer-hook-interface" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" dependencies = [ "arrayref", "bytemuck", @@ -5884,11 +6170,10 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools 0.12.1", "nom", "unicode_categories", ] @@ -5905,12 +6190,12 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ - "sqlx-core 0.7.2", - "sqlx-macros 0.7.2", + "sqlx-core 0.7.4", + "sqlx-macros 0.7.4", "sqlx-postgres", ] @@ -5947,7 +6232,7 @@ dependencies = [ "log", "md-5", "memchr", - "num-bigint 0.4.4", + "num-bigint 0.4.6", "once_cell", "paste", "percent-encoding", @@ -5974,17 +6259,16 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.11", "atoi 2.0.0", "byteorder", "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener 2.5.3", "futures-channel", @@ -5994,13 +6278,13 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.2.3", + "indexmap 2.2.6", "log", "memchr", "once_cell", "paste", "percent-encoding", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-pemfile", "serde", "serde_json", @@ -6012,7 +6296,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.24.0", + "webpki-roots 0.25.4", ] [[package]] @@ -6025,8 +6309,8 @@ dependencies = [ "either", "heck 0.4.1", "once_cell", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "serde_json", "sha2 0.10.8", "sqlx-core 0.6.3", @@ -6037,34 +6321,34 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "sqlx-core 0.7.2", + "proc-macro2", + "quote", + "sqlx-core 0.7.4", "sqlx-macros-core", "syn 1.0.109", ] [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", "heck 0.4.1", "hex", "once_cell", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "serde", "serde_json", "sha2 0.10.8", - "sqlx-core 0.7.2", + "sqlx-core 0.7.4", "sqlx-postgres", "syn 1.0.109", "tempfile", @@ -6074,13 +6358,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi 2.0.0", "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.6.0", "byteorder", "crc", "dotenvy", @@ -6101,10 +6385,9 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha1", "sha2 0.10.8", "smallvec", - "sqlx-core 0.7.2", + "sqlx-core 0.7.4", "stringprep", "thiserror", "tracing", @@ -6130,13 +6413,13 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] @@ -6153,9 +6436,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[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 = "strum" @@ -6169,36 +6452,25 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] - [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.50" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "unicode-ident", ] @@ -6209,9 +6481,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -6226,10 +6498,10 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2", + "quote", "syn 1.0.109", - "unicode-xid 0.2.4", + "unicode-xid", ] [[package]] @@ -6261,13 +6533,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", - "rustix 0.38.31", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -6297,22 +6569,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -6327,9 +6599,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", "itoa", @@ -6348,9 +6620,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -6377,9 +6649,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -6392,32 +6664,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -6447,15 +6719,15 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.12", "tokio", ] [[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", @@ -6464,32 +6736,30 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.20.9", + "rustls 0.21.12", "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls 0.24.1", "tungstenite", - "webpki", - "webpki-roots 0.22.6", + "webpki-roots 0.25.4", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -6503,9 +6773,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" @@ -6513,7 +6783,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -6524,7 +6794,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -6553,9 +6823,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -6590,24 +6860,23 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand 0.8.5", - "rustls 0.20.9", - "sha-1", + "rustls 0.21.12", + "sha1", "thiserror", "url", "utf-8", - "webpki", - "webpki-roots 0.22.6", + "webpki-roots 0.24.0", ] [[package]] @@ -6616,6 +6885,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.7.0" @@ -6646,6 +6924,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -6654,15 +6938,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "unicode-xid" -version = "0.1.0" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -6719,9 +6997,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -6736,25 +7014,25 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.7.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", "serde", ] [[package]] name = "value-bag" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" [[package]] name = "vcpkg" @@ -6782,9 +7060,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "want" @@ -6807,11 +7085,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[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", @@ -6819,24 +7103,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 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", "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", @@ -6846,38 +7130,38 @@ 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 1.0.35", + "quote", "wasm-bindgen-macro-support", ] [[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 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", "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", @@ -6919,11 +7203,12 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "wasm-bindgen", + "redox_syscall 0.4.1", + "wasite", "web-sys", ] @@ -6945,11 +7230,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -6964,22 +7249,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.5", ] [[package]] @@ -6997,7 +7267,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -7017,25 +7287,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "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]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7044,15 +7309,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -7062,15 +7321,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -7080,15 +7333,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -7098,15 +7351,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -7116,15 +7363,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -7134,15 +7375,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -7152,9 +7387,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -7213,22 +7448,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -7246,9 +7481,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.50", + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -7272,9 +7507,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 7010642..14f4eae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,15 @@ edition = "2021" members = [ "migration", "digital_asset_types", - "program_transformers" + "program_transformers", + "blockbuster", + "core", + "bubblegum-backfill" ] [dependencies] +anchor-lang = {workspace=true} anyhow = {workspace=true} -blockbuster = {workspace=true} digital_asset_types = { workspace = true, features = ["json_types", "sql_types"] } dotenv = {workspace=true} futures = {workspace=true} @@ -45,9 +48,34 @@ serde_json = {workspace=true} spl-concurrent-merkle-tree = {workspace=true} thiserror = {workspace=true} url = {workspace=true} -base64 = "0.22.0" -reqwest = "0.11.24" - +das-core = {workspace=true} +heck = {workspace=true} +backon = {workspace=true} +clap = {workspace=true} +derive_more = {workspace=true} +figment = {workspace=true} +indicatif = {workspace=true} +plerkle_messenger = {workspace=true} +reqwest = {workspace=true} +solana-account-decoder = {workspace=true} +bytemuck = {workspace=true} +mpl-core = {workspace=true} +mpl-token-metadata = {workspace=true} +solana-zk-token-sdk = {workspace=true} +spl-noop = {workspace=true} +spl-pod = {workspace=true} +anchor-client = {workspace=true} +spl-token-2022 = {workspace=true} +spl-token-group-interface = {workspace=true} +das-bubblegum-backfill = {workspace=true} +spl-token-metadata-interface = {workspace=true} +flatbuffers = {workspace=true} +rand = {workspace=true} +solana-geyser-plugin-interface = {workspace=true} +solana-program = {workspace=true} +blockbuster = {workspace=true} +signal-hook = {workspace=true} +base64 = {workspace=true} [[bin]] @@ -61,25 +89,26 @@ repository = "https://github.com/WilfredAlmeida/LightDAS" version = "0.1.0" [workspace.dependencies] -anyhow = "1.0.79" -blockbuster = {git="https://github.com/rpcpool/blockbuster", branch="rm-plerkle-101"} -cadence = "0.29.0" -cadence-macros = "0.29.0" +anyhow = "1.0.86" +base64 = "0.22.1" +blockbuster = { path = "blockbuster" } +cadence = "0.29.1" +cadence-macros = "0.29.1" dotenv = "0.15.0" digital_asset_types = { path = "digital_asset_types" } -futures = "0.3.28" -lazy_static = "1.4.0" -mpl-bubblegum = "1.2.0" -plerkle_serialization = "1.6.0" -sea-orm = { version = "0.10.6", features = ["macros", "runtime-tokio-rustls", "sqlx-postgres", "with-chrono", "mock"] } -solana-client = "1.16.2" -solana-rpc-client-api = "1.16.2" -solana-sdk = "1.16.27" -solana-transaction-status = "1.16.2" -sqlx = "0.6.2" -tokio = "1.30.0" -log = "0.4.17" -async-trait = "0.1.60" +futures = "0.3.30" +lazy_static = "1.5.0" +mpl-bubblegum = "1.4.0" +plerkle_serialization = "1.8.0" +sea-orm = { version = "0.10.7", features = ["macros", "runtime-tokio-rustls", "sqlx-postgres", "with-chrono", "mock"] } +solana-client = "~1.17" +solana-rpc-client-api = "~1.17" +solana-sdk = "~1.17" +solana-transaction-status = "~1.17" +sqlx = "0.6.3" +tokio = "1.38.0" +log = "0.4.22" +async-trait = "0.1.80" borsh = "~0.10.3" borsh-derive = "~0.10.3" bs58 = "0.4.0" @@ -87,19 +116,49 @@ indexmap = "1.9.3" jsonpath_lib = "0.3.0" mime_guess = "2.0.4" num-derive = "0.3.3" -num-traits = "0.2.15" +num-traits = "0.2.19" program_transformers = { path = "program_transformers" } -schemars = "0.8.6" -schemars_derive = "0.8.6" -sea-query = "0.28.1" -serde = "1.0.137" -serde_json = "1.0.81" +das-bubblegum-backfill = { path = "bubblegum-backfill" } +das-core = { path = "core" } +schemars = "0.8.21" +schemars_derive = "0.8.21" +sea-query = "0.28.5" +serde = "1.0.203" +serde_json = "1.0.118" spl-concurrent-merkle-tree = "0.2.0" -spl-account-compression = "0.2.0" +spl-account-compression = "0.3.0" spl-token = ">= 3.5.0, < 5.0" -thiserror = "1.0.31" -tracing = "0.1.35" -url = "2.3.1" +thiserror = "1.0.61" +tracing = "0.1.40" +url = "2.5.2" +heck = "0.5.0" +backon = "0.4.4" +clap = "4.5.7" +derive_more = { version = "0.99.18" } +figment = "0.10.19" +indicatif = "0.17.8" +plerkle_messenger = "1.8.0" +reqwest = "0.11.27" +signal-hook = "0.3.17" +solana-account-decoder = "~1.17" +bytemuck = { version = "1.16.1", features = ["derive"] } +mpl-core = { version = "0.7.1", features = ["serde"] } +mpl-token-metadata = "4.1.2" +solana-zk-token-sdk = "~1.17" +spl-noop = "0.2.0" +spl-pod = { version = "0.1.0", features = ["serde-traits"] } + + +spl-token-metadata-interface = "0.2.0" +flatbuffers = "23.5.26" +rand = "0.8.5" +solana-geyser-plugin-interface = "~1.17" +solana-program = "~1.17" +anchor-client = "0.29.0" +anchor-lang = "0.29.0" +spl-token-2022 = { version = "1.0", features = ["no-entrypoint"] } +spl-token-group-interface = "0.1.0" + [workspace.lints.clippy] clone_on_ref_ptr = "deny" diff --git a/README.md b/README.md index 80d1127..21026e0 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,6 @@ It allows you to index specific Merkle Trees that you care about. This repositor - Upsert the Metaplex's DAS database ![LightDAS drawio](https://github.com/WilfredAlmeida/LightDAS/assets/60785452/323da5a6-de11-45a0-bdd2-e5b28d547e71) - -### Backfilling and Live Transactions Indexing Process -![lightdas-queue](https://github.com/WilfredAlmeida/LightDAS/assets/60785452/e24fcc69-e5fa-406e-99a3-1cce22815740) -1. LightDAS is started and calls `getSignaturesForAddress` on the Merkle Tree -2. It returns transaction signatures from the latest to the first -3. These are put into the queue one by one via push front. So when step 1 is completed, the queue will have all transaction signatures in an order of first to latest for backfilling -4. LightDAS in parallel to step 1, also calls `logsSubscribe` on the Merkle Trees and listens to live transaction happening on them -5. These are put into the queue one by one via push-back -6. After step 1 is completed and all transaction signatures are present in the queue, a processes task starts in parallel and, processes transactions -7. These processed transactions are inserted into the Metaplex DAS Database -8. The Metaplex DAS API serves DAS requests by interacting with the DAS database - ### Reasons we are building LigthDAS - Running a standard DAS API is expensive and complicated - It gives you data off all of the NFTs on chain, but do you really need all of it? @@ -29,6 +17,7 @@ It allows you to index specific Merkle Trees that you care about. This repositor With LightDAS, you can have your own DAS API without the nft ingester or any other heavy lifting. The components you need to get your DAS running are: - LightDAS ingester (us) +- DAS Backfiller (Metaplex) - DAS API Handler (Metaplex) - DAS Database (Metaplex) - Graphite Monitoring (Metaplex) @@ -51,6 +40,7 @@ Follow the steps mentioned below - `DATABASE_URL`: Default is `postgres://solana:solana@localhost:5432/solana`, use this unless you changed anything - Execute `cargo run` - This will download and compile the code with all needed dependencies. Grab a coffee this takes a while +- The first run will fail and complain about no tree addresses being found to index, you need to add tree addresses to index in the database. See the `#trees config` section below - Once running, you'll see the logs of the tasks being performed - Under heavy loads, we have faced RPC rate limits - RPC Costs per NFT Mint: @@ -59,14 +49,33 @@ Follow the steps mentioned below - `getTransaction`: 50 credits - Overall, each NFT mint will cost you 100 RPC credits +### Trees Config +1. The address of the trees to be indexed needs to be provided via the database +2. LightDAS creates a table with the following schema + ``` + CREATE TABLE IF NOT EXISTS LD_MERKLE_TREES ( + ADDRESS VARCHAR(255), + TAG VARCHAR(255) NULL, + CAPACITY INT NULL, + MAX_DEPTH INT NULL, + CANOPY_DEPTH INT NULL, + MAX_BUFFER_SIZE INT NULL, + SHOULD_INDEX BOOLEAN DEFAULT TRUE, + CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UPDATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + ``` +3. You need to add your addresses in the table `ld_merkle_trees` +4. To update tree addresses dynamically: + 1. Update the above table + 2. Send a SIGHUP signal to the LightDAS process + 3. LightDAS handles the signal and update it's indexing without disrupting existing tasks + **Currently LightDAS supports only Compressed NFTs**: ### Testing If the program is running without any errors then the database is populated with information on new NFT mints. You can query the RPC API locally. It runs on the default URL `http://localhost:9090/` -### Note -1. Asynchronous backfilling is broken and the logic needs to be redone -2. Backfilling is disabled by default, enable it by uncommenting the block at `src/main.rs:148` ### Support If you need any help, have any thoughts, or need to get in touch, DM [Wilfred](https://twitter.com/WilfredAlmeida_) on Twitter/X or open an issue. @@ -77,7 +86,6 @@ We have some open [RFCs](https://github.com/WilfredAlmeida/LightDAS/labels/rfc) The following is our roadmap in decreasing order of priority: - Test API responses correctness against standard DAS API responses - Publish benchmarking results of testing with different RPC providers under various deployment environments -- Test out if LightDAS can work as a full fledged DAS. Since we're watching Merkle trees, we can also watch the Bubblegum program and index all NFT operations. ### The Future of LightDAS Our vision for LightDAS is to keep it an open-source public good for everyone. We aim to be a compliment to DAS, not to compete against it. Eventually, we would like to streamline the setup process to only need a single binary with minimal dependencies so it's easy for a project to setup a light DAS client to watch a tree and start serving requests. The future decisions for LightDAS will be based on community feedback and discussions. diff --git a/blockbuster/.gitignore b/blockbuster/.gitignore new file mode 100644 index 0000000..28aaccd --- /dev/null +++ b/blockbuster/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + + +# These are backup files generated by rustfmt +**/*.rs.bk + +*.iml +blockbuster/target +target/ diff --git a/blockbuster/Cargo.toml b/blockbuster/Cargo.toml new file mode 100644 index 0000000..00dd4b5 --- /dev/null +++ b/blockbuster/Cargo.toml @@ -0,0 +1,42 @@ +[package] +authors = ["Metaplex Developers "] +description = "Metaplex canonical program parsers, for indexing, analytics etc...." +edition = "2021" +license = "AGPL-3.0" +name = "blockbuster" +readme = "../README.md" + +version = "2.3.0" + +[dependencies] +anchor-lang = {workspace = true} +async-trait = {workspace = true} +borsh = {workspace = true} +bs58 = {workspace = true} +bytemuck = {workspace = true} +lazy_static = {workspace = true} +log = {workspace = true} +mpl-bubblegum = {workspace = true} +mpl-core = {workspace = true, features = ["serde"]} +mpl-token-metadata = {workspace = true, features = ["serde"]} +serde = {workspace = true} +solana-sdk = {workspace = true} +solana-transaction-status = {workspace = true} +solana-zk-token-sdk = {workspace = true} +spl-account-compression = {workspace = true, features = ["no-entrypoint"]} +spl-noop = {workspace = true, features = ["no-entrypoint"]} +spl-pod = {workspace = true, features = ["serde-traits"]} +spl-token = {workspace = true, features = ["no-entrypoint"]} +spl-token-2022 = {workspace = true, features = ["no-entrypoint"]} +spl-token-group-interface = {workspace = true} +spl-token-metadata-interface = {workspace = true} +thiserror = {workspace = true} + +[dev-dependencies] +flatbuffers = {workspace = true} +plerkle_serialization = {workspace = true} +rand = {workspace = true} +serde_json = {workspace = true} +solana-client = {workspace = true} +solana-geyser-plugin-interface = {workspace = true} +spl-concurrent-merkle-tree = {workspace = true} diff --git a/blockbuster/README.md b/blockbuster/README.md new file mode 100644 index 0000000..896d8bb --- /dev/null +++ b/blockbuster/README.md @@ -0,0 +1,22 @@ +# BlockBuster + +BlockBuster -> "Busting Solana blocks into little pieces to index and operate on the programs therein" - Noone - 1995 + +This repository is the home for Metaplex Program Parsers. Program parsers are canonical libraries that take a transaction or account update from a geyser plugin and parse them correctly according to Metaplex smart contracts. This sort of parsing is hard to automate as it must contain some knowledge of the API structure of the contract which is not fully describable yet via IDLs. Things like remaining accounts, optional accounts and complex instruction data are not always 100% clear what they mean without knowledge of the contract. + +## Mode of Operation +This library works best as a consumer of messages sent via a geyser plugin using the [Plerkle Serialization](https://github.com/metaplex-foundation/digital-asset-validator-plugin) library by metaplex. The types from that library are FlatBuffer based currently, and are the wire format of messages coming out of Plerkle into the rest of the infrastructure. +For more information about Plerkle and the [Digital Asset RPC infrastructure](https://github.com/metaplex-foundation/digital-asset-validator-plugin) It can however be used in any general programs provided you can create the data in the FlatBuffer types. + +## Scope + +This library contains parsers for the following programs and the parsers are specific to how these contracts relate to metaplex assets. + +* Gummyroll (Solana) +* Bubblegum (Metaplex) +* Spl Token (Solana) +* Token Metadata (Metaplex) +* Auction House (Metaplex) +* Candy Machine (Metaplex) +* Hydra (Metaplex) + diff --git a/blockbuster/src/error.rs b/blockbuster/src/error.rs new file mode 100644 index 0000000..199eb3b --- /dev/null +++ b/blockbuster/src/error.rs @@ -0,0 +1,34 @@ +use std::io::Error; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum BlockbusterError { + #[error("Instruction Data Parsing Error")] + InstructionParsingError, + #[error("IO Error {0}")] + IOError(String), + #[error("Could not deserialize data")] + DeserializationError, + #[error("Missing Bubblegum event data")] + MissingBubblegumEventData, + #[error("Data length is invalid.")] + InvalidDataLength, + #[error("Unknown anchor account discriminator.")] + UnknownAccountDiscriminator, + #[error("Account type is not valid")] + InvalidAccountType, + #[error("Master edition version is invalid")] + FailedToDeserializeToMasterEdition, + #[error("Uninitialized account type")] + UninitializedAccount, + #[error("Account Type Not implemented")] + AccountTypeNotImplemented, + #[error("Could not deserialize data: {0}")] + CustomDeserializationError(String), +} + +impl From for BlockbusterError { + fn from(err: Error) -> Self { + BlockbusterError::IOError(err.to_string()) + } +} diff --git a/blockbuster/src/instruction.rs b/blockbuster/src/instruction.rs new file mode 100644 index 0000000..4345e29 --- /dev/null +++ b/blockbuster/src/instruction.rs @@ -0,0 +1,89 @@ +use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; +use solana_transaction_status::InnerInstructions; +use std::collections::{HashSet, VecDeque}; + +pub type IxPair<'a> = (Pubkey, &'a CompiledInstruction); + +#[derive(Debug, Clone, Copy)] +pub struct InstructionBundle<'a> { + pub txn_id: &'a str, + pub program: Pubkey, + pub instruction: Option<&'a CompiledInstruction>, + pub inner_ix: Option<&'a [IxPair<'a>]>, + pub keys: &'a [Pubkey], + pub slot: u64, +} + +impl<'a> Default for InstructionBundle<'a> { + fn default() -> Self { + InstructionBundle { + txn_id: "", + program: Pubkey::new_from_array([0; 32]), + instruction: None, + inner_ix: None, + keys: &[], + slot: 0, + } + } +} + +pub fn order_instructions<'a>( + programs: &HashSet, + account_keys: &[Pubkey], + message_instructions: &'a [CompiledInstruction], + meta_inner_instructions: &'a [InnerInstructions], +) -> VecDeque<(IxPair<'a>, Option>>)> { + let mut ordered_ixs: VecDeque<(IxPair, Option>)> = VecDeque::new(); + + // Get inner instructions. + for (outer_instruction_index, message_instruction) in message_instructions.iter().enumerate() { + let non_hoisted_inner_instruction = meta_inner_instructions + .iter() + .filter_map(|ix| { + (ix.index == outer_instruction_index as u8).then_some(&ix.instructions) + }) + .flatten() + .map(|inner_ix| { + let cix = &inner_ix.instruction; + (account_keys[cix.program_id_index as usize], cix) + }) + .collect::>(); + + let hoisted = hoist_known_programs(programs, &non_hoisted_inner_instruction); + ordered_ixs.extend(hoisted); + + if let Some(outer_program_id) = + account_keys.get(message_instruction.program_id_index as usize) + { + if programs.contains(outer_program_id) { + ordered_ixs.push_back(( + (*outer_program_id, message_instruction), + Some(non_hoisted_inner_instruction), + )); + } + } else { + eprintln!("outer program id deserialization error"); + } + } + ordered_ixs +} + +fn hoist_known_programs<'a>( + programs: &HashSet, + ix_pairs: &[IxPair<'a>], +) -> Vec<(IxPair<'a>, Option>>)> { + ix_pairs + .iter() + .enumerate() + .filter(|&(_index, &(pid, _ci))| programs.contains(&pid)) + .map(|(index, &(pid, ci))| { + let inner_copy = ix_pairs + .iter() + .skip(index + 1) + .take_while(|&&(inner_pid, _)| inner_pid != pid) + .cloned() + .collect::>>(); + ((pid, ci), Some(inner_copy)) + }) + .collect() +} diff --git a/blockbuster/src/lib.rs b/blockbuster/src/lib.rs new file mode 100644 index 0000000..983c728 --- /dev/null +++ b/blockbuster/src/lib.rs @@ -0,0 +1,7 @@ +pub mod error; +pub mod instruction; +pub mod program_handler; +pub mod programs; + +pub use mpl_core; +pub use mpl_token_metadata as token_metadata; diff --git a/blockbuster/src/parsed_programs.rs b/blockbuster/src/parsed_programs.rs new file mode 100644 index 0000000..c14db38 --- /dev/null +++ b/blockbuster/src/parsed_programs.rs @@ -0,0 +1,9 @@ +pub enum Program { + Bubblegum { + parser: bubblegum::BubblegumParser, + instruction_result: BubblegumInstruction, + account_result: (), + }, +} + +impl ProgramParser for Program {} diff --git a/blockbuster/src/program_handler.rs b/blockbuster/src/program_handler.rs new file mode 100644 index 0000000..ac7bb3a --- /dev/null +++ b/blockbuster/src/program_handler.rs @@ -0,0 +1,52 @@ +use crate::{ + error::BlockbusterError, instruction::InstructionBundle, programs::ProgramParseResult, +}; +use solana_sdk::pubkey::Pubkey; + +pub trait ParseResult: Sync + Send { + fn result_type(&self) -> ProgramParseResult; + + fn result(&self) -> &Self + where + Self: Sized, + { + self + } +} + +pub struct NotUsed(()); + +impl NotUsed { + pub fn new() -> Self { + NotUsed(()) + } +} + +impl Default for NotUsed { + fn default() -> Self { + Self::new() + } +} + +impl ParseResult for NotUsed { + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::Unknown + } +} + +pub trait ProgramParser: Sync + Send { + fn key(&self) -> Pubkey; + fn key_match(&self, key: &Pubkey) -> bool; + fn handles_instructions(&self) -> bool; + fn handles_account_updates(&self) -> bool; + fn handle_account( + &self, + _account_data: &[u8], + ) -> Result, BlockbusterError>; + fn handle_instruction( + &self, + _bundle: &InstructionBundle, + ) -> Result, BlockbusterError> { + Ok(Box::new(NotUsed::new())) + } +} diff --git a/blockbuster/src/programs/account_closure/mod.rs b/blockbuster/src/programs/account_closure/mod.rs new file mode 100644 index 0000000..7586b7d --- /dev/null +++ b/blockbuster/src/programs/account_closure/mod.rs @@ -0,0 +1,73 @@ +use crate::{ + error::BlockbusterError, + program_handler::{ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use solana_sdk::{pubkey::Pubkey, pubkeys}; + +use plerkle_serialization::AccountInfo; + +pubkeys!(solana_program_id, "11111111111111111111111111111111"); + +pub struct ClosedAccountInfo { + pub pubkey: Vec, + pub owner: Vec, +} + +#[allow(clippy::large_enum_variant)] +pub enum AccountClosureData { + ClosedAccountInfo(ClosedAccountInfo), + EmptyAccount, +} + +impl ParseResult for AccountClosureData { + fn result(&self) -> &Self + where + Self: Sized, + { + self + } + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::AccountClosure(self) + } +} + +pub struct AccountClosureParser; + +impl ProgramParser for AccountClosureParser { + fn key(&self) -> Pubkey { + solana_program_id() + } + fn key_match(&self, key: &Pubkey) -> bool { + key == &solana_program_id() + } + + fn handles_account_updates(&self) -> bool { + true + } + + fn handles_instructions(&self) -> bool { + false + } + + fn handle_account( + &self, + account_info: &AccountInfo, + ) -> Result, BlockbusterError> { + let account_data: ClosedAccountInfo = match (account_info.pubkey(), account_info.owner()) { + (Some(pubkey), Some(owner)) => ClosedAccountInfo { + pubkey: pubkey.0.to_vec(), + owner: owner.0.to_vec(), + }, + _ => return Ok(Box::new(AccountClosureData::EmptyAccount)), + }; + + if account_info.lamports() == 0 { + Ok(Box::new(AccountClosureData::ClosedAccountInfo( + account_data, + ))) + } else { + Ok(Box::new(AccountClosureData::EmptyAccount)) + } + } +} diff --git a/blockbuster/src/programs/bubblegum/mod.rs b/blockbuster/src/programs/bubblegum/mod.rs new file mode 100644 index 0000000..faa1b8a --- /dev/null +++ b/blockbuster/src/programs/bubblegum/mod.rs @@ -0,0 +1,301 @@ +use crate::{ + error::BlockbusterError, + instruction::InstructionBundle, + program_handler::{NotUsed, ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use borsh::de::BorshDeserialize; +use log::warn; +use mpl_bubblegum::{ + get_instruction_type, + instructions::{ + UnverifyCreatorInstructionArgs, UpdateMetadataInstructionArgs, VerifyCreatorInstructionArgs, + }, + types::{BubblegumEventType, MetadataArgs, UpdateArgs}, +}; +pub use mpl_bubblegum::{ + types::{LeafSchema, UseMethod}, + InstructionName, LeafSchemaEvent, ID, +}; +use solana_sdk::pubkey::Pubkey; +pub use spl_account_compression::events::{ + AccountCompressionEvent::{self, ApplicationData, ChangeLog}, + ApplicationDataEvent, ChangeLogEvent, ChangeLogEventV1, +}; + +use spl_noop; + +#[derive(Eq, PartialEq)] +pub enum Payload { + Unknown, + MintV1 { + args: MetadataArgs, + authority: Pubkey, + tree_id: Pubkey, + }, + Decompress { + args: MetadataArgs, + }, + CancelRedeem { + root: Pubkey, + }, + CreatorVerification { + metadata: MetadataArgs, + creator: Pubkey, + verify: bool, + }, + CollectionVerification { + collection: Pubkey, + verify: bool, + }, + UpdateMetadata { + current_metadata: MetadataArgs, + update_args: UpdateArgs, + tree_id: Pubkey, + }, +} +//TODO add more of the parsing here to minimize program transformer code +pub struct BubblegumInstruction { + pub instruction: InstructionName, + pub tree_update: Option, + pub leaf_update: Option, + pub payload: Option, +} + +impl BubblegumInstruction { + pub fn new(ix: InstructionName) -> Self { + BubblegumInstruction { + instruction: ix, + tree_update: None, + leaf_update: None, + payload: None, + } + } +} + +impl ParseResult for BubblegumInstruction { + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::Bubblegum(self) + } + fn result(&self) -> &Self + where + Self: Sized, + { + self + } +} + +pub struct BubblegumParser; + +impl ProgramParser for BubblegumParser { + fn key(&self) -> Pubkey { + ID + } + + fn key_match(&self, key: &Pubkey) -> bool { + key == &ID + } + fn handles_account_updates(&self) -> bool { + false + } + + fn handles_instructions(&self) -> bool { + true + } + fn handle_account( + &self, + _account_data: &[u8], + ) -> Result, BlockbusterError> { + Ok(Box::new(NotUsed::new())) + } + + fn handle_instruction( + &self, + bundle: &InstructionBundle, + ) -> Result, BlockbusterError> { + let InstructionBundle { + txn_id, + instruction, + inner_ix, + keys, + .. + } = bundle; + let outer_ix_data = match instruction { + Some(cix) => cix.data.as_ref(), + _ => return Err(BlockbusterError::DeserializationError), + }; + let ix_type = get_instruction_type(outer_ix_data); + let mut b_inst = BubblegumInstruction::new(ix_type); + if let Some(ixs) = inner_ix { + for (pid, cix) in ixs.iter() { + if pid == &spl_noop::id() && !cix.data.is_empty() { + match AccountCompressionEvent::try_from_slice(&cix.data) { + Ok(result) => match result { + ChangeLog(changelog_event) => { + let ChangeLogEvent::V1(changelog_event) = changelog_event; + b_inst.tree_update = Some(changelog_event); + } + ApplicationData(app_data) => { + let ApplicationDataEvent::V1(app_data) = app_data; + let app_data = app_data.application_data; + + let event_type_byte = if !app_data.is_empty() { + &app_data[0..1] + } else { + return Err(BlockbusterError::DeserializationError); + }; + + match BubblegumEventType::try_from_slice(event_type_byte)? { + BubblegumEventType::Uninitialized => { + return Err(BlockbusterError::MissingBubblegumEventData); + } + BubblegumEventType::LeafSchemaEvent => { + b_inst.leaf_update = + Some(LeafSchemaEvent::try_from_slice(&app_data)?); + } + } + } + }, + Err(e) => { + warn!( + "Error while deserializing txn {:?} with noop data: {:?}", + txn_id, e + ); + } + } + } + } + } + + if outer_ix_data.len() >= 8 { + let ix_data = &outer_ix_data[8..]; + if !ix_data.is_empty() { + match b_inst.instruction { + InstructionName::MintV1 => { + b_inst.payload = Some(build_mint_v1_payload(keys, ix_data, false)?); + } + + InstructionName::MintToCollectionV1 => { + b_inst.payload = Some(build_mint_v1_payload(keys, ix_data, true)?); + } + InstructionName::DecompressV1 => { + let args: MetadataArgs = MetadataArgs::try_from_slice(ix_data)?; + b_inst.payload = Some(Payload::Decompress { args }); + } + InstructionName::CancelRedeem => { + let slice: [u8; 32] = ix_data + .try_into() + .map_err(|_e| BlockbusterError::InstructionParsingError)?; + let root = Pubkey::new_from_array(slice); + b_inst.payload = Some(Payload::CancelRedeem { root }); + } + InstructionName::VerifyCreator => { + b_inst.payload = + Some(build_creator_verification_payload(keys, ix_data, true)?); + } + InstructionName::UnverifyCreator => { + b_inst.payload = + Some(build_creator_verification_payload(keys, ix_data, false)?); + } + InstructionName::VerifyCollection | InstructionName::SetAndVerifyCollection => { + b_inst.payload = Some(build_collection_verification_payload(keys, true)?); + } + InstructionName::UnverifyCollection => { + b_inst.payload = Some(build_collection_verification_payload(keys, false)?); + } + InstructionName::UpdateMetadata => { + b_inst.payload = Some(build_update_metadata_payload(keys, ix_data)?); + } + _ => {} + }; + } + } + + Ok(Box::new(b_inst)) + } +} + +// See Bubblegum documentation for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md#-verify_creator-and-unverify_creator +fn build_creator_verification_payload( + keys: &[Pubkey], + ix_data: &[u8], + verify: bool, +) -> Result { + let metadata = if verify { + VerifyCreatorInstructionArgs::try_from_slice(ix_data)?.metadata + } else { + UnverifyCreatorInstructionArgs::try_from_slice(ix_data)?.metadata + }; + + let creator = *keys + .get(5) + .ok_or(BlockbusterError::InstructionParsingError)?; + + Ok(Payload::CreatorVerification { + metadata, + creator, + verify, + }) +} + +// See Bubblegum for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md#-verify_collection-unverify_collection-and-set_and_verify_collection +// This uses the account. The collection is only provided as an argument for `set_and_verify_collection`. +fn build_collection_verification_payload( + keys: &[Pubkey], + verify: bool, +) -> Result { + let collection = *keys + .get(8) + .ok_or(BlockbusterError::InstructionParsingError)?; + Ok(Payload::CollectionVerification { collection, verify }) +} + +// See Bubblegum for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md +fn build_mint_v1_payload( + keys: &[Pubkey], + ix_data: &[u8], + set_verify: bool, +) -> Result { + let mut args: MetadataArgs = MetadataArgs::try_from_slice(ix_data)?; + if set_verify { + if let Some(ref mut col) = args.collection { + col.verified = true; + } + } + + let authority = *keys + .first() + .ok_or(BlockbusterError::InstructionParsingError)?; + + let tree_id = *keys + .get(3) + .ok_or(BlockbusterError::InstructionParsingError)?; + + Ok(Payload::MintV1 { + args, + authority, + tree_id, + }) +} + +// See Bubblegum for offsets and positions: +// https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/README.md +fn build_update_metadata_payload( + keys: &[Pubkey], + ix_data: &[u8], +) -> Result { + let args = UpdateMetadataInstructionArgs::try_from_slice(ix_data)?; + + let tree_id = *keys + .get(8) + .ok_or(BlockbusterError::InstructionParsingError)?; + + Ok(Payload::UpdateMetadata { + current_metadata: args.current_metadata, + update_args: args.update_args, + tree_id, + }) +} diff --git a/blockbuster/src/programs/mod.rs b/blockbuster/src/programs/mod.rs new file mode 100644 index 0000000..8da2fee --- /dev/null +++ b/blockbuster/src/programs/mod.rs @@ -0,0 +1,34 @@ +use bubblegum::BubblegumInstruction; +use mpl_core_program::MplCoreAccountState; +use token_account::TokenProgramAccount; +use token_extensions::TokenExtensionsProgramAccount; +use token_metadata::TokenMetadataAccountState; + +pub mod bubblegum; +pub mod mpl_core_program; +pub mod token_account; +pub mod token_extensions; +pub mod token_metadata; + +// Note: `ProgramParseResult` used to contain the following variants that have been deprecated and +// removed from blockbuster since the `version-1.16` tag: +// CandyGuard(&'a CandyGuardAccountData), +// CandyMachine(&'a CandyMachineAccountData), +// CandyMachineCore(&'a CandyMachineCoreAccountData), +// +// Candy Machine V3 parsing was removed because Candy Guard (`mpl-candy-guard`) and +// Candy Machine Core (`mpl-candy-machine-core`) were dependent upon a specific Solana +// version (1.16), there was no Candy Machine parsing in DAS (`digital-asset-rpc-infrastructure`), +// and we wanted to use the Rust clients for Bubblegum and Token Metadata so that going forward we +// could more easily update blockbuster to new Solana versions. +// +// Candy Machine V2 (`mpl-candy-machine`) parsing was removed at the same time as V3 because even +// though it did not depend on the `mpl-candy-machine` crate, it was also not being used by DAS. +pub enum ProgramParseResult<'a> { + Bubblegum(&'a BubblegumInstruction), + MplCore(&'a MplCoreAccountState), + TokenMetadata(&'a TokenMetadataAccountState), + TokenProgramAccount(&'a TokenProgramAccount), + TokenExtensionsProgramAccount(&'a TokenExtensionsProgramAccount), + Unknown, +} diff --git a/blockbuster/src/programs/mpl_core_program/mod.rs b/blockbuster/src/programs/mpl_core_program/mod.rs new file mode 100644 index 0000000..cafbd0a --- /dev/null +++ b/blockbuster/src/programs/mpl_core_program/mod.rs @@ -0,0 +1,92 @@ +use crate::{ + error::BlockbusterError, + program_handler::{ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use borsh::BorshDeserialize; +use mpl_core::{types::Key, IndexableAsset}; +use solana_sdk::{pubkey::Pubkey, pubkeys}; + +pubkeys!(mpl_core_id, "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d"); + +#[derive(Clone, Debug, PartialEq)] +pub enum MplCoreAccountData { + Asset(IndexableAsset), + Collection(IndexableAsset), + HashedAsset, + EmptyAccount, +} + +pub struct MplCoreAccountState { + pub key: Key, + pub data: MplCoreAccountData, +} + +impl ParseResult for MplCoreAccountState { + fn result(&self) -> &Self + where + Self: Sized, + { + self + } + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::MplCore(self) + } +} + +pub struct MplCoreParser; + +impl ProgramParser for MplCoreParser { + fn key(&self) -> Pubkey { + mpl_core_id() + } + fn key_match(&self, key: &Pubkey) -> bool { + key == &mpl_core_id() + } + + fn handles_account_updates(&self) -> bool { + true + } + + fn handles_instructions(&self) -> bool { + false + } + + fn handle_account( + &self, + account_data: &[u8], + ) -> Result, BlockbusterError> { + if account_data.is_empty() { + return Ok(Box::new(MplCoreAccountState { + key: Key::Uninitialized, + data: MplCoreAccountData::EmptyAccount, + })); + } + let key = Key::try_from_slice(&account_data[0..1])?; + let mpl_core_account_state = match key { + Key::AssetV1 => { + let indexable_asset = IndexableAsset::fetch(key, account_data)?; + MplCoreAccountState { + key, + data: MplCoreAccountData::Asset(indexable_asset), + } + } + Key::CollectionV1 => { + let indexable_asset = IndexableAsset::fetch(key, account_data)?; + MplCoreAccountState { + key, + data: MplCoreAccountData::Collection(indexable_asset), + } + } + Key::Uninitialized => MplCoreAccountState { + key: Key::Uninitialized, + data: MplCoreAccountData::EmptyAccount, + }, + _ => { + return Err(BlockbusterError::AccountTypeNotImplemented); + } + }; + + Ok(Box::new(mpl_core_account_state)) + } +} diff --git a/blockbuster/src/programs/token_account/mod.rs b/blockbuster/src/programs/token_account/mod.rs new file mode 100644 index 0000000..298fa33 --- /dev/null +++ b/blockbuster/src/programs/token_account/mod.rs @@ -0,0 +1,77 @@ +use crate::{ + error::BlockbusterError, + program_handler::{ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use solana_sdk::{program_pack::Pack, pubkey::Pubkey, pubkeys}; +use spl_token::state::{Account as TokenAccount, Mint}; + +pubkeys!( + token_program_id, + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" +); + +pub struct TokenAccountParser; + +pub enum TokenProgramAccount { + Mint(Mint), + TokenAccount(TokenAccount), +} + +impl ParseResult for TokenProgramAccount { + fn result(&self) -> &Self + where + Self: Sized, + { + self + } + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::TokenProgramAccount(self) + } +} + +impl ProgramParser for TokenAccountParser { + fn key(&self) -> Pubkey { + token_program_id() + } + fn key_match(&self, key: &Pubkey) -> bool { + key == &token_program_id() + } + fn handles_account_updates(&self) -> bool { + true + } + + fn handles_instructions(&self) -> bool { + false + } + fn handle_account( + &self, + account_data: &[u8], + ) -> Result, BlockbusterError> { + let account_type = match account_data.len() { + 165 => { + let token_account = TokenAccount::unpack(account_data).map_err(|_| { + BlockbusterError::CustomDeserializationError( + "Token Account Unpack Failed".to_string(), + ) + })?; + + TokenProgramAccount::TokenAccount(token_account) + } + 82 => { + let mint = Mint::unpack(account_data).map_err(|_| { + BlockbusterError::CustomDeserializationError( + "Token MINT Unpack Failed".to_string(), + ) + })?; + + TokenProgramAccount::Mint(mint) + } + _ => { + return Err(BlockbusterError::InvalidDataLength); + } + }; + + Ok(Box::new(account_type)) + } +} diff --git a/blockbuster/src/programs/token_extensions/extension.rs b/blockbuster/src/programs/token_extensions/extension.rs new file mode 100644 index 0000000..c9a0b9b --- /dev/null +++ b/blockbuster/src/programs/token_extensions/extension.rs @@ -0,0 +1,542 @@ +use bytemuck::Zeroable; +use serde::{Deserialize, Serialize}; +use solana_zk_token_sdk::zk_token_elgamal::pod::{AeCiphertext, ElGamalCiphertext, ElGamalPubkey}; +use spl_pod::{ + optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, + primitives::{PodBool, PodI64, PodU16, PodU32, PodU64}, +}; + +use spl_token_2022::extension::{ + confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, + confidential_transfer_fee::{ConfidentialTransferFeeAmount, ConfidentialTransferFeeConfig}, + cpi_guard::CpiGuard, + default_account_state::DefaultAccountState, + group_member_pointer::GroupMemberPointer, + group_pointer::GroupPointer, + immutable_owner::ImmutableOwner, + interest_bearing_mint::{BasisPoints, InterestBearingConfig}, + memo_transfer::MemoTransfer, + metadata_pointer::MetadataPointer, + mint_close_authority::MintCloseAuthority, + permanent_delegate::PermanentDelegate, + transfer_fee::{TransferFee, TransferFeeAmount, TransferFeeConfig}, + transfer_hook::TransferHook, +}; +use std::fmt; + +use spl_token_group_interface::state::{TokenGroup, TokenGroupMember}; +use spl_token_metadata_interface::state::TokenMetadata; + +const AE_CIPHERTEXT_LEN: usize = 36; +const UNIT_LEN: usize = 32; +const RISTRETTO_POINT_LEN: usize = UNIT_LEN; +pub(crate) const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN; +pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN; +const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN; +const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN; +type PodAccountState = u8; +pub type UnixTimestamp = PodI64; +pub type EncryptedBalance = ShadowElGamalCiphertext; +pub type DecryptableBalance = ShadowAeCiphertext; +pub type EncryptedWithheldAmount = ShadowElGamalCiphertext; + +use serde::{ + de::{self, SeqAccess, Visitor}, + Deserializer, Serializer, +}; + +/// Bs58 encoded public key string. Used for storing Pubkeys in a human readable format. +/// Ideally we'd store them as is in the DB and later convert them to bs58 for display on the API. +/// But, +/// - We currently store them in DB as JSONB. +/// - `Pubkey` serializes to an u8 vector, unlike sth like `OptionalNonZeroElGamalPubkey` which serializes to a string. +/// So `Pubkey` is stored as a u8 vector in the DB. +/// - `Pubkey` doesn't implement something like `schemars::JsonSchema` so we can't convert them back to the rust struct either. +type PublicKeyString = String; + +struct ShadowAeCiphertextVisitor; + +struct ShadowElGamalCiphertextVisitor; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ShadowAeCiphertext(pub [u8; AE_CIPHERTEXT_LEN]); + +#[derive(Clone, Copy, Debug, PartialEq, Zeroable)] +pub struct ShadowElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]); + +#[derive(Clone, Copy, Debug, Default, Zeroable, PartialEq, Eq, Serialize, Deserialize)] +pub struct ShadowElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]); + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowCpiGuard { + pub lock_cpi: PodBool, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowDefaultAccountState { + pub state: PodAccountState, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowImmutableOwner; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowInterestBearingConfig { + pub rate_authority: OptionalNonZeroPubkey, + pub initialization_timestamp: UnixTimestamp, + pub pre_update_average_rate: BasisPoints, + pub last_update_timestamp: UnixTimestamp, + pub current_rate: BasisPoints, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowMemoTransfer { + /// Require transfers into this account to be accompanied by a memo + pub require_incoming_transfer_memos: PodBool, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowMetadataPointer { + pub authority: OptionalNonZeroPubkey, + pub metadata_address: OptionalNonZeroPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowGroupMemberPointer { + pub authority: OptionalNonZeroPubkey, + pub member_address: OptionalNonZeroPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowGroupPointer { + /// Authority that can set the group address + pub authority: OptionalNonZeroPubkey, + /// Account address that holds the group + pub group_address: OptionalNonZeroPubkey, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct ShadowTokenGroup { + /// The authority that can sign to update the group + pub update_authority: OptionalNonZeroPubkey, + /// The associated mint, used to counter spoofing to be sure that group + /// belongs to a particular mint + pub mint: PublicKeyString, + /// The current number of group members + pub size: PodU32, + /// The maximum number of group members + pub max_size: PodU32, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct ShadowTokenGroupMember { + /// The associated mint, used to counter spoofing to be sure that member + /// belongs to a particular mint + pub mint: PublicKeyString, + /// The pubkey of the `TokenGroup` + pub group: PublicKeyString, + /// The member number + pub member_number: PodU32, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowMintCloseAuthority { + pub close_authority: OptionalNonZeroPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct NonTransferableAccount; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowPermanentDelegate { + pub delegate: OptionalNonZeroPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowTransferFee { + pub epoch: PodU64, + pub maximum_fee: PodU64, + pub transfer_fee_basis_points: PodU16, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowTransferHook { + pub authority: OptionalNonZeroPubkey, + pub program_id: OptionalNonZeroPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowConfidentialTransferMint { + pub authority: OptionalNonZeroPubkey, + pub auto_approve_new_accounts: PodBool, + pub auditor_elgamal_pubkey: OptionalNonZeroElGamalPubkey, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct ShadowConfidentialTransferAccount { + pub approved: PodBool, + pub elgamal_pubkey: ShadowElGamalPubkey, + pub pending_balance_lo: EncryptedBalance, + pub pending_balance_hi: EncryptedBalance, + pub available_balance: EncryptedBalance, + pub decryptable_available_balance: DecryptableBalance, + pub allow_confidential_credits: PodBool, + pub allow_non_confidential_credits: PodBool, + pub pending_balance_credit_counter: PodU64, + pub maximum_pending_balance_credit_counter: PodU64, + pub expected_pending_balance_credit_counter: PodU64, + pub actual_pending_balance_credit_counter: PodU64, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowConfidentialTransferFeeConfig { + pub authority: OptionalNonZeroPubkey, + pub withdraw_withheld_authority_elgamal_pubkey: ShadowElGamalPubkey, + pub harvest_to_mint_enabled: PodBool, + pub withheld_amount: EncryptedWithheldAmount, +} + +pub struct ShadowConfidentialTransferFeeAmount { + pub withheld_amount: EncryptedWithheldAmount, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowTransferFeeAmount { + pub withheld_amount: PodU64, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable, Serialize, Deserialize)] +pub struct ShadowTransferFeeConfig { + pub transfer_fee_config_authority: OptionalNonZeroPubkey, + pub withdraw_withheld_authority: OptionalNonZeroPubkey, + pub withheld_amount: PodU64, + pub older_transfer_fee: ShadowTransferFee, + pub newer_transfer_fee: ShadowTransferFee, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct ShadowMetadata { + pub update_authority: OptionalNonZeroPubkey, + pub mint: PublicKeyString, + pub name: String, + pub symbol: String, + pub uri: String, + pub additional_metadata: Vec<(String, String)>, +} + +impl Serialize for ShadowAeCiphertext { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +impl<'de> Visitor<'de> for ShadowElGamalCiphertextVisitor { + type Value = ShadowElGamalCiphertext; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array of length ELGAMAL_CIPHERTEXT_LEN") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + if v.len() == ELGAMAL_CIPHERTEXT_LEN { + let mut arr = [0u8; ELGAMAL_CIPHERTEXT_LEN]; + arr.copy_from_slice(v); + Ok(ShadowElGamalCiphertext(arr)) + } else { + Err(E::invalid_length(v.len(), &self)) + } + } +} + +impl<'de> Deserialize<'de> for ShadowElGamalCiphertext { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(ShadowElGamalCiphertextVisitor) + } +} + +impl Default for ShadowElGamalCiphertext { + fn default() -> Self { + ShadowElGamalCiphertext([0u8; ELGAMAL_CIPHERTEXT_LEN]) + } +} + +impl Default for ShadowAeCiphertext { + fn default() -> Self { + ShadowAeCiphertext([0u8; AE_CIPHERTEXT_LEN]) + } +} + +impl<'de> Visitor<'de> for ShadowAeCiphertextVisitor { + type Value = ShadowAeCiphertext; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a byte array of length AE_CIPHERTEXT_LEN") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut arr = [0u8; AE_CIPHERTEXT_LEN]; + for (i, item) in arr.iter_mut().enumerate().take(AE_CIPHERTEXT_LEN) { + *item = seq + .next_element()? + .ok_or(de::Error::invalid_length(i, &self))?; + } + Ok(ShadowAeCiphertext(arr)) + } +} + +impl<'de> Deserialize<'de> for ShadowAeCiphertext { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_tuple(AE_CIPHERTEXT_LEN, ShadowAeCiphertextVisitor) + } +} + +impl Serialize for ShadowElGamalCiphertext { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +impl From for ShadowAeCiphertext { + fn from(original: AeCiphertext) -> Self { + ShadowAeCiphertext(original.0) + } +} + +impl From for ShadowElGamalCiphertext { + fn from(original: ElGamalCiphertext) -> Self { + ShadowElGamalCiphertext(original.0) + } +} + +impl From for ShadowElGamalPubkey { + fn from(original: ElGamalPubkey) -> Self { + ShadowElGamalPubkey(original.0) + } +} + +impl From for ShadowCpiGuard { + fn from(original: CpiGuard) -> Self { + ShadowCpiGuard { + lock_cpi: original.lock_cpi, + } + } +} + +impl From for ShadowDefaultAccountState { + fn from(original: DefaultAccountState) -> Self { + ShadowDefaultAccountState { + state: original.state, + } + } +} + +impl From for ShadowImmutableOwner { + fn from(_: ImmutableOwner) -> Self { + ShadowImmutableOwner + } +} + +impl From for ShadowConfidentialTransferFeeAmount { + fn from(original: ConfidentialTransferFeeAmount) -> Self { + ShadowConfidentialTransferFeeAmount { + withheld_amount: original.withheld_amount.into(), + } + } +} + +impl From for ShadowTransferFeeAmount { + fn from(original: TransferFeeAmount) -> Self { + ShadowTransferFeeAmount { + withheld_amount: original.withheld_amount, + } + } +} + +impl From for ShadowMemoTransfer { + fn from(original: MemoTransfer) -> Self { + ShadowMemoTransfer { + require_incoming_transfer_memos: original.require_incoming_transfer_memos, + } + } +} + +impl From for ShadowMetadataPointer { + fn from(original: MetadataPointer) -> Self { + ShadowMetadataPointer { + authority: original.authority, + metadata_address: original.metadata_address, + } + } +} + +impl From for ShadowGroupPointer { + fn from(original: GroupPointer) -> Self { + ShadowGroupPointer { + authority: original.authority, + group_address: original.group_address, + } + } +} + +impl From for ShadowTokenGroup { + fn from(original: TokenGroup) -> Self { + ShadowTokenGroup { + update_authority: original.update_authority, + mint: bs58::encode(original.mint).into_string(), + size: original.size, + max_size: original.max_size, + } + } +} + +impl From for ShadowTokenGroupMember { + fn from(original: TokenGroupMember) -> Self { + ShadowTokenGroupMember { + mint: bs58::encode(original.mint).into_string(), + group: bs58::encode(original.group).into_string(), + member_number: original.member_number, + } + } +} + +impl From for ShadowGroupMemberPointer { + fn from(original: GroupMemberPointer) -> Self { + ShadowGroupMemberPointer { + authority: original.authority, + member_address: original.member_address, + } + } +} + +impl From for ShadowTransferFee { + fn from(original: TransferFee) -> Self { + ShadowTransferFee { + epoch: original.epoch, + maximum_fee: original.maximum_fee, + transfer_fee_basis_points: original.transfer_fee_basis_points, + } + } +} + +impl From for ShadowTransferFeeConfig { + fn from(original: TransferFeeConfig) -> Self { + ShadowTransferFeeConfig { + transfer_fee_config_authority: original.transfer_fee_config_authority, + withdraw_withheld_authority: original.withdraw_withheld_authority, + withheld_amount: original.withheld_amount, + older_transfer_fee: ShadowTransferFee::from(original.older_transfer_fee), + newer_transfer_fee: ShadowTransferFee::from(original.newer_transfer_fee), + } + } +} + +impl From for ShadowInterestBearingConfig { + fn from(original: InterestBearingConfig) -> Self { + ShadowInterestBearingConfig { + rate_authority: original.rate_authority, + initialization_timestamp: original.initialization_timestamp, + pre_update_average_rate: original.pre_update_average_rate, + last_update_timestamp: original.last_update_timestamp, + current_rate: original.current_rate, + } + } +} + +impl From for ShadowMintCloseAuthority { + fn from(original: MintCloseAuthority) -> Self { + ShadowMintCloseAuthority { + close_authority: original.close_authority, + } + } +} + +impl From for ShadowPermanentDelegate { + fn from(original: PermanentDelegate) -> Self { + ShadowPermanentDelegate { + delegate: original.delegate, + } + } +} + +impl From for ShadowTransferHook { + fn from(original: TransferHook) -> Self { + ShadowTransferHook { + authority: original.authority, + program_id: original.program_id, + } + } +} + +impl From for ShadowConfidentialTransferMint { + fn from(original: ConfidentialTransferMint) -> Self { + ShadowConfidentialTransferMint { + authority: original.authority, + auto_approve_new_accounts: original.auto_approve_new_accounts, + auditor_elgamal_pubkey: original.auditor_elgamal_pubkey, + } + } +} + +impl From for ShadowConfidentialTransferAccount { + fn from(original: ConfidentialTransferAccount) -> Self { + ShadowConfidentialTransferAccount { + approved: original.approved, + elgamal_pubkey: original.elgamal_pubkey.into(), + pending_balance_lo: original.pending_balance_lo.into(), + pending_balance_hi: original.pending_balance_hi.into(), + available_balance: original.available_balance.into(), + decryptable_available_balance: original.decryptable_available_balance.into(), + allow_confidential_credits: original.allow_confidential_credits, + allow_non_confidential_credits: original.allow_non_confidential_credits, + pending_balance_credit_counter: original.pending_balance_credit_counter, + maximum_pending_balance_credit_counter: original.maximum_pending_balance_credit_counter, + expected_pending_balance_credit_counter: original + .expected_pending_balance_credit_counter, + actual_pending_balance_credit_counter: original.actual_pending_balance_credit_counter, + } + } +} + +impl From for ShadowConfidentialTransferFeeConfig { + fn from(original: ConfidentialTransferFeeConfig) -> Self { + ShadowConfidentialTransferFeeConfig { + authority: original.authority, + withdraw_withheld_authority_elgamal_pubkey: original + .withdraw_withheld_authority_elgamal_pubkey + .into(), + harvest_to_mint_enabled: original.harvest_to_mint_enabled, + withheld_amount: original.withheld_amount.into(), + } + } +} + +impl From for ShadowMetadata { + fn from(original: TokenMetadata) -> Self { + ShadowMetadata { + update_authority: original.update_authority, + mint: bs58::encode(original.mint).into_string(), + name: original.name, + symbol: original.symbol, + uri: original.uri, + additional_metadata: original.additional_metadata, + } + } +} diff --git a/blockbuster/src/programs/token_extensions/mod.rs b/blockbuster/src/programs/token_extensions/mod.rs new file mode 100644 index 0000000..ec1633c --- /dev/null +++ b/blockbuster/src/programs/token_extensions/mod.rs @@ -0,0 +1,210 @@ +pub mod extension; +use crate::{ + error::BlockbusterError, + program_handler::{ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use serde::{Deserialize, Serialize}; +use solana_sdk::{pubkey::Pubkey, pubkeys}; +use spl_token_2022::{ + extension::{ + confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, + confidential_transfer_fee::ConfidentialTransferFeeConfig, + cpi_guard::CpiGuard, + default_account_state::DefaultAccountState, + group_member_pointer::GroupMemberPointer, + group_pointer::GroupPointer, + interest_bearing_mint::InterestBearingConfig, + memo_transfer::MemoTransfer, + metadata_pointer::MetadataPointer, + mint_close_authority::MintCloseAuthority, + permanent_delegate::PermanentDelegate, + transfer_fee::{TransferFeeAmount, TransferFeeConfig}, + transfer_hook::TransferHook, + BaseStateWithExtensions, StateWithExtensions, + }, + state::{Account, Mint}, +}; +use spl_token_group_interface::state::{TokenGroup, TokenGroupMember}; +use spl_token_metadata_interface::state::TokenMetadata; + +use self::extension::{ + ShadowConfidentialTransferAccount, ShadowConfidentialTransferFeeConfig, + ShadowConfidentialTransferMint, ShadowCpiGuard, ShadowDefaultAccountState, + ShadowGroupMemberPointer, ShadowGroupPointer, ShadowInterestBearingConfig, ShadowMemoTransfer, + ShadowMetadata, ShadowMetadataPointer, ShadowMintCloseAuthority, ShadowPermanentDelegate, + ShadowTokenGroup, ShadowTokenGroupMember, ShadowTransferFeeAmount, ShadowTransferFeeConfig, + ShadowTransferHook, +}; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct MintAccountExtensions { + pub default_account_state: Option, + pub confidential_transfer_mint: Option, + pub confidential_transfer_account: Option, + pub confidential_transfer_fee_config: Option, + pub interest_bearing_config: Option, + pub transfer_fee_config: Option, + pub mint_close_authority: Option, + pub permanent_delegate: Option, + pub metadata_pointer: Option, + pub metadata: Option, + pub transfer_hook: Option, + pub group_pointer: Option, + pub token_group: Option, + pub group_member_pointer: Option, + pub token_group_member: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct TokenAccountExtensions { + pub confidential_transfer: Option, + pub cpi_guard: Option, + pub memo_transfer: Option, + pub transfer_fee_amount: Option, +} +#[derive(Debug, PartialEq)] +pub struct TokenAccount { + pub account: Account, + pub extensions: TokenAccountExtensions, +} + +#[derive(Debug, PartialEq)] +pub struct MintAccount { + pub account: Mint, + pub extensions: MintAccountExtensions, +} + +pubkeys!( + token_program_id, + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" +); + +pub struct Token2022AccountParser; + +#[allow(clippy::large_enum_variant)] +pub enum TokenExtensionsProgramAccount { + TokenAccount(TokenAccount), + MintAccount(MintAccount), + EmptyAccount, +} + +impl ParseResult for TokenExtensionsProgramAccount { + fn result(&self) -> &Self + where + Self: Sized, + { + self + } + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::TokenExtensionsProgramAccount(self) + } +} + +impl ProgramParser for Token2022AccountParser { + fn key(&self) -> Pubkey { + token_program_id() + } + fn key_match(&self, key: &Pubkey) -> bool { + key == &token_program_id() + } + fn handles_account_updates(&self) -> bool { + true + } + + fn handles_instructions(&self) -> bool { + false + } + + fn handle_account( + &self, + account_data: &[u8], + ) -> Result, BlockbusterError> { + if account_data.is_empty() { + return Ok(Box::new(TokenExtensionsProgramAccount::EmptyAccount)); + } + + let result: TokenExtensionsProgramAccount; + + if let Ok(account) = StateWithExtensions::::unpack(account_data) { + let confidential_transfer = account + .get_extension::() + .ok() + .copied(); + let cpi_guard = account.get_extension::().ok().copied(); + let memo_transfer = account.get_extension::().ok().copied(); + let transfer_fee_amount = account.get_extension::().ok().copied(); + + // Create a structured account with extensions + let structured_account = TokenAccount { + account: account.base, + extensions: TokenAccountExtensions { + confidential_transfer: confidential_transfer + .map(ShadowConfidentialTransferAccount::from), + cpi_guard: cpi_guard.map(ShadowCpiGuard::from), + memo_transfer: memo_transfer.map(ShadowMemoTransfer::from), + transfer_fee_amount: transfer_fee_amount.map(ShadowTransferFeeAmount::from), + }, + }; + + result = TokenExtensionsProgramAccount::TokenAccount(structured_account); + } else if let Ok(mint) = StateWithExtensions::::unpack(account_data) { + let confidential_transfer_mint = mint + .get_extension::() + .ok() + .copied(); + let confidential_transfer_account = mint + .get_extension::() + .ok() + .copied(); + let confidential_transfer_fee_config = mint + .get_extension::() + .ok() + .copied(); + let default_account_state = mint.get_extension::().ok().copied(); + let interest_bearing_config = + mint.get_extension::().ok().copied(); + let transfer_fee_config = mint.get_extension::().ok().copied(); + let mint_close_authority = mint.get_extension::().ok().copied(); + let permanent_delegate = mint.get_extension::().ok().copied(); + let metadata_pointer = mint.get_extension::().ok().copied(); + let metadata = mint.get_variable_len_extension::().ok(); + let group_pointer = mint.get_extension::().ok().copied(); + let token_group = mint.get_extension::().ok().copied(); + let group_member_pointer = mint.get_extension::().ok().copied(); + let token_group_member = mint.get_extension::().ok().copied(); + let transfer_hook = mint.get_extension::().ok().copied(); + + let structured_mint = MintAccount { + account: mint.base, + extensions: MintAccountExtensions { + confidential_transfer_mint: confidential_transfer_mint + .map(ShadowConfidentialTransferMint::from), + confidential_transfer_account: confidential_transfer_account + .map(ShadowConfidentialTransferAccount::from), + confidential_transfer_fee_config: confidential_transfer_fee_config + .map(ShadowConfidentialTransferFeeConfig::from), + default_account_state: default_account_state + .map(ShadowDefaultAccountState::from), + interest_bearing_config: interest_bearing_config + .map(ShadowInterestBearingConfig::from), + transfer_fee_config: transfer_fee_config.map(ShadowTransferFeeConfig::from), + mint_close_authority: mint_close_authority.map(ShadowMintCloseAuthority::from), + permanent_delegate: permanent_delegate.map(ShadowPermanentDelegate::from), + metadata_pointer: metadata_pointer.map(ShadowMetadataPointer::from), + metadata: metadata.map(ShadowMetadata::from), + transfer_hook: transfer_hook.map(ShadowTransferHook::from), + group_pointer: group_pointer.map(ShadowGroupPointer::from), + token_group: token_group.map(ShadowTokenGroup::from), + group_member_pointer: group_member_pointer.map(ShadowGroupMemberPointer::from), + token_group_member: token_group_member.map(ShadowTokenGroupMember::from), + }, + }; + result = TokenExtensionsProgramAccount::MintAccount(structured_mint); + } else { + return Err(BlockbusterError::InvalidDataLength); + }; + + Ok(Box::new(result)) + } +} diff --git a/blockbuster/src/programs/token_metadata/mod.rs b/blockbuster/src/programs/token_metadata/mod.rs new file mode 100644 index 0000000..e19ee21 --- /dev/null +++ b/blockbuster/src/programs/token_metadata/mod.rs @@ -0,0 +1,147 @@ +use crate::{ + error::BlockbusterError, + program_handler::{ParseResult, ProgramParser}, + programs::ProgramParseResult, +}; +use borsh::BorshDeserialize; +use solana_sdk::{borsh0_10::try_from_slice_unchecked, pubkey::Pubkey, pubkeys}; + +use mpl_token_metadata::{ + accounts::{ + CollectionAuthorityRecord, DeprecatedMasterEditionV1, Edition, EditionMarker, + MasterEdition, Metadata, UseAuthorityRecord, + }, + types::Key, +}; + +pubkeys!( + token_metadata_id, + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" +); + +#[allow(clippy::large_enum_variant)] +pub enum TokenMetadataAccountData { + EditionV1(Edition), + MasterEditionV1(DeprecatedMasterEditionV1), + MetadataV1(Metadata), + MasterEditionV2(MasterEdition), + EditionMarker(EditionMarker), + UseAuthorityRecord(UseAuthorityRecord), + CollectionAuthorityRecord(CollectionAuthorityRecord), + EmptyAccount, +} + +pub struct TokenMetadataAccountState { + pub key: Key, + pub data: TokenMetadataAccountData, +} + +impl ParseResult for TokenMetadataAccountState { + fn result(&self) -> &Self + where + Self: Sized, + { + self + } + fn result_type(&self) -> ProgramParseResult { + ProgramParseResult::TokenMetadata(self) + } +} + +pub struct TokenMetadataParser; + +impl ProgramParser for TokenMetadataParser { + fn key(&self) -> Pubkey { + token_metadata_id() + } + fn key_match(&self, key: &Pubkey) -> bool { + key == &token_metadata_id() + } + + fn handles_account_updates(&self) -> bool { + true + } + + fn handles_instructions(&self) -> bool { + false + } + + fn handle_account( + &self, + account_data: &[u8], + ) -> Result, BlockbusterError> { + if account_data.is_empty() { + return Ok(Box::new(TokenMetadataAccountState { + key: Key::Uninitialized, + data: TokenMetadataAccountData::EmptyAccount, + })); + } + let key = Key::try_from_slice(&account_data[0..1])?; + let token_metadata_account_state = match key { + Key::EditionV1 => { + let account: Edition = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::EditionV1(account), + } + } + Key::MasterEditionV1 => { + let account: DeprecatedMasterEditionV1 = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::MasterEditionV1(account), + } + } + Key::MasterEditionV2 => { + let account: MasterEdition = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::MasterEditionV2(account), + } + } + Key::UseAuthorityRecord => { + let account: UseAuthorityRecord = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::UseAuthorityRecord(account), + } + } + Key::EditionMarker => { + let account: EditionMarker = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::EditionMarker(account), + } + } + Key::CollectionAuthorityRecord => { + let account: CollectionAuthorityRecord = try_from_slice_unchecked(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::CollectionAuthorityRecord(account), + } + } + Key::MetadataV1 => { + let account = Metadata::safe_deserialize(account_data)?; + + TokenMetadataAccountState { + key: account.key, + data: TokenMetadataAccountData::MetadataV1(account), + } + } + Key::Uninitialized => { + return Err(BlockbusterError::UninitializedAccount); + } + _ => { + return Err(BlockbusterError::AccountTypeNotImplemented); + } + }; + + Ok(Box::new(token_metadata_account_state)) + } +} diff --git a/blockbuster/tests/bubblegum_parser_test.rs b/blockbuster/tests/bubblegum_parser_test.rs new file mode 100644 index 0000000..5c2afdf --- /dev/null +++ b/blockbuster/tests/bubblegum_parser_test.rs @@ -0,0 +1,222 @@ +#[cfg(test)] +use blockbuster::{ + program_handler::ProgramParser, + programs::{bubblegum::BubblegumParser, ProgramParseResult}, +}; +use flatbuffers::FlatBufferBuilder; +use helpers::*; +use mpl_bubblegum::{ + instructions::{MintV1InstructionArgs, TransferInstructionArgs}, + types::{BubblegumEventType, Creator, LeafSchema, MetadataArgs, TokenProgramVersion, Version}, + LeafSchemaEvent, +}; +use spl_account_compression::{ + events::{AccountCompressionEvent, ChangeLogEvent}, + state::PathNode, +}; + +mod helpers; + +#[test] +fn test_setup() { + let subject = BubblegumParser {}; + assert_eq!(subject.key(), mpl_bubblegum::ID); + assert!(subject.key_match(&mpl_bubblegum::ID)); +} + +#[test] +fn test_mint() { + let subject = BubblegumParser {}; + + let accounts = random_list_of(9, |_i| random_pubkey()); + let fb_accounts = accounts.clone(); + let fb_account_indexes: Vec = fb_accounts + .iter() + .enumerate() + .map(|(i, _)| i as u8) + .collect(); + + let metadata = MetadataArgs { + name: "test".to_string(), + symbol: "test".to_string(), + uri: "www.solana.pos".to_owned(), + seller_fee_basis_points: 0, + primary_sale_happened: false, + is_mutable: false, + edition_nonce: None, + token_standard: None, + token_program_version: TokenProgramVersion::Original, + collection: None, + uses: None, + creators: vec![Creator { + address: random_pubkey(), + verified: false, + share: 20, + }], + }; + + // We are only using this to get the instruction data, so the accounts don't actually matter + // here. + let mut accounts_iter = accounts.iter(); + let ix = mpl_bubblegum::instructions::MintV1 { + tree_config: *accounts_iter.next().unwrap(), + leaf_owner: *accounts_iter.next().unwrap(), + leaf_delegate: *accounts_iter.next().unwrap(), + merkle_tree: *accounts_iter.next().unwrap(), + payer: *accounts_iter.next().unwrap(), + tree_creator_or_delegate: *accounts_iter.next().unwrap(), + log_wrapper: *accounts_iter.next().unwrap(), + compression_program: *accounts_iter.next().unwrap(), + system_program: *accounts_iter.next().unwrap(), + }; + let ix_data = ix.instruction(MintV1InstructionArgs { metadata }).data; + + let lse = LeafSchemaEvent { + event_type: BubblegumEventType::LeafSchemaEvent, + version: Version::V1, + schema: LeafSchema::V1 { + id: random_pubkey(), + owner: random_pubkey(), + delegate: random_pubkey(), + nonce: 0, + data_hash: [0; 32], + creator_hash: [0; 32], + }, + leaf_hash: [0; 32], + }; + + let cs = ChangeLogEvent::new( + random_pubkey(), + vec![PathNode { + node: [0; 32], + index: 0, + }], + 0, + 0, + ); + let cs_event = AccountCompressionEvent::ChangeLog(cs); + + let mut fbb1 = FlatBufferBuilder::new(); + let mut fbb2 = FlatBufferBuilder::new(); + let mut fbb3 = FlatBufferBuilder::new(); + let mut fbb4 = FlatBufferBuilder::new(); + + let ix_b = build_bubblegum_bundle( + &mut fbb1, + &mut fbb2, + &mut fbb3, + &mut fbb4, + &fb_accounts, + &fb_account_indexes, + &ix_data, + lse, + cs_event, + ); + + let result = subject.handle_instruction(&ix_b); + + if let ProgramParseResult::Bubblegum(b) = result.unwrap().result_type() { + let matched = match b.instruction { + mpl_bubblegum::InstructionName::MintV1 => Ok(()), + _ => Err(()), + }; + assert!(matched.is_ok()); + assert!(b.payload.is_some()); + assert!(b.leaf_update.is_some()); + assert!(b.tree_update.is_some()); + } else { + panic!("Unexpected ProgramParseResult variant"); + } +} + +#[test] +fn test_basic_success_parsing() { + let subject = BubblegumParser {}; + + let accounts = random_list_of(8, |_i| random_pubkey()); + let fb_accounts = accounts.clone(); + let fb_account_indexes: Vec = fb_accounts + .iter() + .enumerate() + .map(|(i, _)| i as u8) + .collect(); + + // We are only using this to get the instruction data, so the accounts don't actually matter + // here. + let mut accounts_iter = accounts.iter(); + let ix = mpl_bubblegum::instructions::Transfer { + tree_config: *accounts_iter.next().unwrap(), + leaf_owner: (*accounts_iter.next().unwrap(), true), + leaf_delegate: (*accounts_iter.next().unwrap(), false), + merkle_tree: *accounts_iter.next().unwrap(), + log_wrapper: *accounts_iter.next().unwrap(), + compression_program: *accounts_iter.next().unwrap(), + system_program: *accounts_iter.next().unwrap(), + new_leaf_owner: *accounts_iter.next().unwrap(), + }; + let ix_data = ix + .instruction(TransferInstructionArgs { + root: [0; 32], + data_hash: [0; 32], + creator_hash: [0; 32], + nonce: 0, + index: 0, + }) + .data; + + let lse = LeafSchemaEvent { + event_type: BubblegumEventType::LeafSchemaEvent, + version: Version::V1, + schema: LeafSchema::V1 { + id: random_pubkey(), + owner: random_pubkey(), + delegate: random_pubkey(), + nonce: 0, + data_hash: [0; 32], + creator_hash: [0; 32], + }, + leaf_hash: [0; 32], + }; + + let cs = ChangeLogEvent::new( + random_pubkey(), + vec![PathNode { + node: [0; 32], + index: 0, + }], + 0, + 0, + ); + let cs_event = AccountCompressionEvent::ChangeLog(cs); + + let mut fbb1 = FlatBufferBuilder::new(); + let mut fbb2 = FlatBufferBuilder::new(); + let mut fbb3 = FlatBufferBuilder::new(); + let mut fbb4 = FlatBufferBuilder::new(); + + let ix_b = build_bubblegum_bundle( + &mut fbb1, + &mut fbb2, + &mut fbb3, + &mut fbb4, + &fb_accounts, + &fb_account_indexes, + &ix_data, + lse, + cs_event, + ); + let result = subject.handle_instruction(&ix_b); + + if let ProgramParseResult::Bubblegum(b) = result.unwrap().result_type() { + assert!(b.payload.is_none()); + let matched = match b.instruction { + mpl_bubblegum::InstructionName::Transfer => Ok(()), + _ => Err(()), + }; + assert!(matched.is_ok()); + assert!(b.leaf_update.is_some()); + assert!(b.tree_update.is_some()); + } else { + panic!("Unexpected ProgramParseResult variant"); + } +} diff --git a/blockbuster/tests/fixtures/double_bubblegum_mint.json b/blockbuster/tests/fixtures/double_bubblegum_mint.json new file mode 100644 index 0000000..70318af --- /dev/null +++ b/blockbuster/tests/fixtures/double_bubblegum_mint.json @@ -0,0 +1,162 @@ +{ + "blockTime": 1675092216, + "meta": { + "computeUnitsConsumed": 141280, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 0, + "instructions": [ + { + "accounts": [ + 1, + 0, + 9, + 5 + ], + "data": "Tc14EPctAN3Z", + "programIdIndex": 11 + }, + { + "accounts": [], + "data": "2GJh7oUmkZKowPMPT4SvaD5QV55UkcqB2PpZ2BZ1W7sjJMC8uggZASX6NokKX4h5pmQnSa6YyoSR9jFVbmr8bb8yxP4MavzdWEt4X2RHiJCZa4dZe1hHo2DsMXPNDATWPJQDEQDGJ6pL7ejbVwScEoDLAzgYCPv7Tqi6vFcWRfHsy9Az6BFETLyFuszod6tyGrCXwoRXyNaVKGzggGToXK1sAdiCixsLKNhfNjYnKKb1kDTvCV7n9cNeug4PPvTmaEB3tUkgWEPpi14StUju4W4UUdrSk", + "programIdIndex": 12 + }, + { + "accounts": [ + 2, + 3, + 12 + ], + "data": "8RkZ9BWdS73MdDxbzibxQiF8EL66q8CbKTi4Hhn1kLVKZTDqNdyfqKe", + "programIdIndex": 8 + }, + { + "accounts": [], + "data": "11XERtpD5QHDbyeBPMqzJYcChfbt8smXYiKDE6UUcGzUrQnr4vJdDATGtCDGexvDG34Fyzp7xiBvzHxqaqKi6Pn7gQEL2psAjKSp6wHwP7Rn97VDq3u9eu6mdP7gQuUCoyJc5vS9t5TmCao8vmus5oisFiPNvkrKUoN7AXjc83TmeFAxqEJjuTTFcK4kupsBirUPscDPRurvtqf3wUWdj9G8CmWNZYoBwTCAHmJ6WFeAVJjEz3GToB4X9sRgbDASTqqYdzmKr6xHX7VrsZ1GDSwxtZK5VVKjZtBNKBS1D1obbXwt49cBVVoPQJzXBEWgCrvjsB3s8hBTaz7Eqt4QYVFznuT4uLbZf4C3MEQG4KeVKh86zsaK9EN4eutQE2VAbJgSCpV2hXGoxP9jAoPbu57TQekEAv23QtqzwrPW1iUQUrmoARro4tmqFegdBs8JCKbF5MAftjrii3HvN7z9z9ArihvBTYxz4eemmYdv8TbkkWkKmqaazMrRQK2Ad4UPhdvhmmnJ7Ap7UELPGjaSUEjUhzByrETwAsG3tSkvgsDadVKV39qRafWiABGR7zcR5am1MTGN5RFu3SbHEX7E7RLwFan2ax2wPu5mjE2BmFb42srnhjDxrAYpJ1KMxg6sz5QWv92VTT1CoheDqAyv29iGLzz1quAZvFAA2EQogGFWto4FuTqDRbXUk9uTcYh8b9T5ThDc89ZECfxPHBm5UNGzjTZYEYumBmPwZPQfA1DEpU5PYQBAaCtvJZKU3bgGa6bWLazRoftMuoh7e4q1RKETZQNznGVEowiNeE66byfpkjUu495ZrAZRCaxzHinFgck545HYycniFoGrNKxWxaS8YitAYitqvn4A5oRKE7ZSE3kA6VawMykq894pRF1UPD6NmjiDpYkbvc17D58uXMz3wZNPnRJiPHm9Y42nMfbQikuUuHhkzZrYQGeKpAXYQuqCukTjoxk8cnF74VrMbuRFmJ7LvzdDS4Yhjho5LdpYXjt783n256qv8Ca7qnEhAnF75KLLPWFGSSb3kXNaR3MrUHvK52Eq9vgHDwYa4AAF2Bp8To69nmP69M5DKJtm7d8gwVycBezb", + "programIdIndex": 12 + } + ] + }, + { + "index": 1, + "instructions": [ + { + "accounts": [ + 1, + 0, + 9, + 5 + ], + "data": "TcAkuQRheyK5", + "programIdIndex": 11 + }, + { + "accounts": [], + "data": "2GJh7oUmkZKoTNgp3ScqBk7HXTfuLs2mJqYQ5UEijKbh5nSzFkKz4hb3nBEFiUKNrZ7KSWVjQPAaAaSNpo2MDe3nXnCp2JWxsgwwxWEFMBLD2hHSDAJBp3q9NUPghUgedmiy2stwuswB62T9yzCZ52aNTWgGf5rnjw7cF7LgpKrmgSyLgYHcEV6GJqputph1toXRQ3Fwx3mxGaWP14vaQp8us3xUbKKNXTd7CMWumY21odXrQyvzBPXbegtYhRksx4gPw9NwK5SQ7ZWxkoFfGf9taHWQL", + "programIdIndex": 12 + }, + { + "accounts": [ + 2, + 3, + 12 + ], + "data": "8RkZ9BWdS73KRTUfXCGaVsDdpgjwac9RAFPUC3fXePXqB3vTrMy6gFW", + "programIdIndex": 8 + }, + { + "accounts": [], + "data": "11XERtpD5QHDbyeBPMqzJYcChfbt8smXYiKDE6UUcGzUrQnr4vJZZNofWv4Sg3EyxdT6FfPv68VapsveBkXgnLkS3KkaFixHgfzfGUSsX15rPZrREQbQ2mzhu3vt8GFTQbTR58ynr3GtiRqfh5WtCykDvbYYyLXwJmH3ccsuxTxYAbE9jvqSm2NATxnyYCGU1fc7cfKWw8rTePM1WmzFERveMNaAaj5uquNWLE85fMxFrgsR5Y7rVrrcpQC2kCdXcajrCsQHVZwUzoJRJxGs7smARkSNbuq4rgPNqasdqxWoc9wE8AC3sZdDSodARjHqKanvyyU9C134mdGZ4QSR5qsdPLmiQ6ZsVxnKpmLLm1oyJwGT8mjZn1XR9tWM6mE1dpKN1Abi3NxpodqMTq3NQ7YVfsk9KicnfhGvRRx1mY3uWgs6cLWETq18B5DTjrXqAC3EXhjGnUHpxHU3TB7Msb5SVrvMnBAgLseBBetmJxPGm3YZ2mnZcSfZB7wUuF8o8FPdsb5kZDQUmQL7ksxLz8dRZc2vfynUxr5qCEGnLLZvJofRXDPgre7gw4vzdubWZNkAtsvtPZDNR51Rw1gAwHAGduH72yLJmJFPvcJBB9v4642ccEsrsFnb7XMsnasM6w9w86qgv2AtgfvW52c7AH3ovHC2tXw5g5oU7jAn17tWGyjF6mCGJm7bPyGtp3M3zGszoiJarAzM9CNjKvYtvjz3R2s16GLVemozmsHqw7JRKhGZYbQpNEzU1fvKRbcW2KfmB2W7cLMU1BqMbfa3xK9HMeWh8XZM2bwMzXo5cgjGEHG88aZ4WLz3f5qyDrGT9aifGJKr4op6L2sfMrZStySxCySW2RSGWZb86hQ3ewbYWf7vZG1KRa6rb8wL4WgERfzPv8jZs2oRoYH1j4UDvNqVRdEezKxL54iufbYNKYZJRz8EqQdA2soPR5JhUwbDkEJ18aFJVn1AkQgKAwkxm98JwWJELMVanXofNM41YkB6KxHjaZiyv5d9b9fTDjy4spmQBztesUsV6eQ8xwpgo7Mxyf9Tfnhw66uNMJaDLfwRKiKEd5aERi8boHLnfsPWVcJ9ATzqUnuD", + "programIdIndex": 12 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY invoke [1]", + "Program log: Instruction: MintToCollectionV1", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [2]", + "Program log: Instruction: Bubblegum Program Set Collection Size", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 18849 of 374956 compute units", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [2]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 346956 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK invoke [2]", + "Program log: Instruction: Append", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [3]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 332669 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK consumed 11188 of 343413 compute units", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY consumed 69776 of 400000 compute units", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY invoke [1]", + "Program log: Instruction: MintToCollectionV1", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [2]", + "Program log: Instruction: Bubblegum Program Set Collection Size", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 18849 of 305180 compute units", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [2]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 274180 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK invoke [2]", + "Program log: Instruction: Append", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [3]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 261165 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK consumed 9916 of 270637 compute units", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY consumed 71504 of 330224 compute units", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY success" + ], + "postBalances": [ + 22941440, + 5616720, + 309079680, + 1559040, + 1, + 0, + 4000000000, + 1141440, + 1141440, + 1461600, + 2853600, + 1141440, + 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 22946440, + 5616720, + 309079680, + 1559040, + 1, + 0, + 4000000000, + 1141440, + 1141440, + 1461600, + 2853600, + 1141440, + 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 192425259, + "transaction": [ + "AUiK0mY5PMz5PmL2AN54NLy3BrLi7aK9zD0WUgYHp5ulX04Xw6gCDSdQ/ftteGfMlESd0Ix3puDNA7FM89TzDAkBAAkNO4HP7Fnu2yFZWCS/3W3YbyPBkD2wPz5OJmjV5HUPoPAYHppoOOkD5TuhY/KCn2Irylt4d5Ji1O8v7odiypZRZYtnklxge77v9Uc2nmOA03u1hm7P+wBaMoy+kUpYenTTnFzQXHWdpvFM3DMM0zgOht3QLvpreuqP5+G/V8OKqP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADZLL1Rhodf4wAu7ZXc4ZiZjkwHTJUvMr2m/ME7O6J68Ab5le7POYPqScPWw2sE25de1jhCY6bqrk6CQQwAzLfqYi4DreTUoabIkdF9Z3b+KJljKE9xogSEmNRyuB8GlpQkqE+6VxBy6CKZ/WsZ+jffh2hFiXh1kE3+PTyODA38UzVj/xF4n8CMtj/RqlHJamnxyGTP4UMxLp40TamGf+5LnxMlEJ5+6+nPHSRw9nqfMjicZXux8QrnpSnlcGogghQtwZbHj0XxFOJ1Sf2sEw81YuGxzGqD9tUm20bwD+ClGC7wPwLtHyi90xBEulKsTz6PGNOXcF+rLA80aI81+eHxWuf+0epsjKc4q703M+16AHhFQNILcp6oXi1rUeaz3NwIHEAMGBgIAAAAHCQEKBQwICwThAZkSsi/FnlYPDwAAAFRlc3QgU3RpY2tlciAjMQMAAABUUDFpAAAAaHR0cHM6Ly9kaWFsZWN0LWZpbGUtc3RvcmFnZS5zMy51cy13ZXN0LTIuYW1hem9uYXdzLmNvbS9zdGlja2VyLXBhY2tzL3Rlc3QtcGFjay0xL3N0aWNrZXItMS1tZXRhZGF0YS5qc29uAAAAAQEAAQABAM1Y/8ReJ/AjLY/0apRyWpp8chkz+FDMS6eNE2phn/uSAAABAAAAO4HP7Fnu2yFZWCS/3W3YbyPBkD2wPz5OJmjV5HUPoPABZAcQAwYGAgAAAAcJAQoFDAgLBOEBmRKyL8WeVg8PAAAAVGVzdCBTdGlja2VyICMyAwAAAFRQMWkAAABodHRwczovL2RpYWxlY3QtZmlsZS1zdG9yYWdlLnMzLnVzLXdlc3QtMi5hbWF6b25hd3MuY29tL3N0aWNrZXItcGFja3MvdGVzdC1wYWNrLTEvc3RpY2tlci0yLW1ldGFkYXRhLmpzb24AAAABAQABAAEAzVj/xF4n8CMtj/RqlHJamnxyGTP4UMxLp40TamGf+5IAAAEAAAA7gc/sWe7bIVlYJL/dbdhvI8GQPbA/Pk4maNXkdQ+g8AFk", + "base64" + ], + "version": "legacy" +} \ No newline at end of file diff --git a/blockbuster/tests/fixtures/helium_mint_double_tree.json b/blockbuster/tests/fixtures/helium_mint_double_tree.json new file mode 100644 index 0000000..c7689ff --- /dev/null +++ b/blockbuster/tests/fixtures/helium_mint_double_tree.json @@ -0,0 +1,393 @@ + { + "blockTime": 1676150526, + "meta": { + "computeUnitsConsumed": 493369, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [ + 0, + 1 + ], + "data": "11115iGgYn1iB2MUEQn96GLGpRFmmMBVQo3myRzELdi4Q6GYCJcjUW74EXyBynLYEHdJeM", + "programIdIndex": 28 + }, + { + "accounts": [ + 18, + 21, + 14, + 30, + 22, + 8, + 31, + 32, + 2, + 3, + 19, + 9, + 20, + 33, + 34, + 35, + 36, + 37, + 28 + ], + "data": "P127ZWPqFCnngvAodxeqzoi83Vj9xLjpoLaAhi3YiHUUyNiieU4B3N77noSKC7fZvk5SME5cwirhUm6KQhRtFEcD2pHJQ3unDM", + "programIdIndex": 29 + }, + { + "accounts": [ + 18, + 2 + ], + "data": "11115jJ3wfqpy1rBHuw3dUPuJHNEtEGk3VTUqjUZn3SiFTZBpQtLCdH6DA8gjCfWnQ4tEG", + "programIdIndex": 28 + }, + { + "accounts": [ + 18, + 3 + ], + "data": "1111cSRr6MFDeX4jK6Ks3qj3Q3VevDadC5dJg6EF283WhGy7JgZaH3VycWXz2pvcrPY5J", + "programIdIndex": 28 + }, + { + "accounts": [ + 19, + 9, + 9, + 20, + 18, + 22, + 22, + 36, + 21, + 14, + 30, + 33, + 35, + 37, + 34, + 28, + 31 + ], + "data": "6PxggWMmLXh3GufgEQVnxYEDNEQo3S6eC4T6uXZuxhAa86fae5FG17KXd4iYSz2HFzv87hq2P4DgUxbgz2YERwAqCuZPx1WhCqpqsA8FnhSzN43WsT4mg9aQFVHC5RvWuX7xQQd9pK4id2T5jsmo5L4PJg72ib58jniEg4incgiaNKPpHvnkWhj4kAbC2RJCJAn9d6u3NrF634SVCPEQUBqDaiXPed5LEyUcsBKiuXkZg63b2ktfpyN4uxTBRtkDd6e2xtVkr3EGixQjtQHr1rdxYMfxaCi5B9x81VAobwjJF76yH", + "programIdIndex": 36 + }, + { + "accounts": [ + 14, + 22, + 21, + 33 + ], + "data": "Tp3YfPjqg9Jo", + "programIdIndex": 34 + }, + { + "accounts": [], + "data": "2GJh7oUmkZKnwfzmSY57AHYZ18ksm4SNYjoqv14aNYTBYSVqXqs3YsQedsfDtGwvVwB26FiHy9wuhQhwawV2VhcTJARicaKLNn3tCgo8y3SpCxQVAzhYAigwtxfRTttmfnrmkcrUbHE5Mo1RzAdxY9FxeBUMwaV5Bh5So4LzVLCj5ttPCewfnsA7JAQzGW5DEWB5iYEGrTvPZErU7R327ruapUripe9fPfUSHz4isbAZzDreZsXAfjGtFQzqFRfSm4AGMtcqhUD7aNGGWAxEaEeufdQJr", + "programIdIndex": 35 + }, + { + "accounts": [ + 20, + 19, + 35 + ], + "data": "8RkZ9BWdS73BAyXDMiAn6BNQ8sRJbDhx9LfKqDUzRwUwHZ3SdbYPpNA", + "programIdIndex": 37 + }, + { + "accounts": [], + "data": "11cvjWXwfL69CPWmmyhtDQXFCUVF38S8oJLjxNaFVtMn5YZjfuRtcy5MC63cjAmJYV4ywaHS665MC7G59aR3ixF6Thbqbexcs2zrL9Ac2HGy3Z412EYx2gPNaeJsTGS7RvFfwYugyz4B2Nt1yGEdiTnLV3EN6qPQZHmvrTkn6uNAoEZu4eZ1v1TvKhgQdk4mREBKfKCNkXZi7CcbrkYDVBMZsYk9ncH77JmxeT1U6JJtVrCeEyv5ypXAbmA5H1A8xjEHCk5WQLLLxYawkzqxHkG8ip4NXCahjEGj4H2WcNMPho4wWFL3itFxeK4P2yxtU67hf8uU5vaQb235p3TuWN3Xxjrr9YqEKCexE7cKF6p7CTg6AmvafRuWAhWs741dQSvsNZiEu27WdiEAccp4nLdgwg4cMJHuzVftfj1VKjZj4Qk8SJRNmQaXGzmF3ZkrEQcap81H5j7PdybSJALa3L7zZRs2v8VnypqbGuE9oW2GhiesY7rXXcToEK2LaT2ZtX6CHhYsHFZzdetd7r7z26eMGtTw1ej25NpW9DMG68fqhg7qPjS4QRVy77QRDfhJtdgXypwDPyG7zy5RMKwX3xm26C5cBZ85ToZ5J6nRAmyhL3LmYhzXWiHtSoiyy3FVLpoMSgZ7T4L4ehyvPK5Cdt55JtnG47Pwgzr9hpA3aNiC2sJYJEuVoZSNUDduCWoQUXJf4v41KMqv6DhfJ8SZREbF2JmFK97wLiC3LPFTt1bG6bLnp9M6ELuudJPGzL2diPd5PtdGS7xtDnYYRQLhHWnUSoSCkDjiVGuuv2yksdX8dBhTgNL22ktY9r2BnY1rnRxRMcooGgCKh8ZLG1tiyKSihLJNgZKX7G5nQGhcUtBisFig2NeCUCyFNLNvCmTs8Fnf3jZkRpBhVQUGq3eq3S6PCFU6PdXeRTzPRL1SotV9VepJ4nHQsTAz7Uj5JHdL5fBAszB6m8VUeE59sZACQ6kmVBCvw6wiNX1Cq332CFBZ3QU3DfSU6gJgUGgGtQDA9kYdUNKWGVkSiWSjn5nmVJkXz1ruGr8gawNktYszjaNrNL81M255uyLg3i2RpRm118hRjctSKffD", + "programIdIndex": 35 + }, + { + "accounts": [ + 18, + 23, + 15, + 24, + 25, + 8, + 31, + 32, + 4, + 5, + 16, + 10, + 17, + 33, + 34, + 35, + 36, + 37, + 28 + ], + "data": "P127ZWPqFCnngvAodxfgpna5Vcdv6bvghkUsaVmYB7Jd7tGzuc9PHodg2QpSQUgej93f7krLKik2in93UKsgCJf9TQoeozETWo", + "programIdIndex": 29 + }, + { + "accounts": [ + 18, + 4 + ], + "data": "11115jJ3wfqpy1rBHuw3dUPuJHNEtEGk3VTUqjUZn3SiFTZBpQtLCdH6DA8gjCfWnQ4tEG", + "programIdIndex": 28 + }, + { + "accounts": [ + 18, + 5 + ], + "data": "1111cSRr6MFDeX4jK6Ks3qj3Q3VevDadC5dJg6EF283WhGy7JgZaH3VycWXz2pvcrPY5J", + "programIdIndex": 28 + }, + { + "accounts": [ + 16, + 10, + 10, + 17, + 18, + 25, + 25, + 36, + 23, + 15, + 24, + 33, + 35, + 37, + 34, + 28, + 31 + ], + "data": "H4jhg7xGfzqFsRjjboyfADudDG2ghfjkbvNCdQse6B9y4LcZPXhpLXadsQ3gmwE5hvhvJmdo6h4C3wkqbfgsawY8GdrMDwSQmb6XEjEv3fam1LiQPrZqjL6GFSyvcUV8cGYS8UT98Q22BQHBzLTDbaBC9LqXcVPMFEEVmJiCotoNPP8pGYWNj4qkMSnGbDLDED74q3Y1ZR1Hk8ip5cZJqYz9fkYky3itVaFnXtj9UhAzCqY9d5WLCBsnPeeCq3GpAnZQCZwrtkqZ75VxiVTVnh9vvkqaBALDrqJKoUYG6Sr2Ry", + "programIdIndex": 36 + }, + { + "accounts": [ + 15, + 25, + 23, + 33 + ], + "data": "TbfetN1FB9WX", + "programIdIndex": 34 + }, + { + "accounts": [], + "data": "2GJh7oUmkZKnNeiK3mrfqNXJFiqQ5nvbxSPFeap5nNdWgiJfs6iHDVrHt32wjmdigfiPzZkFeuDoesWN2oG6AEpk4TJ1grroCfx4U1YkYEPxhRzmjQhh9iDwXfCLgeM9dqLE2YdaTBNLrS8vohgr6xyhcurxdCJjPC6J4hFeHrdG43jA3GipyrcrR2W28fkGsS2TxC46bx2zEckrY6H5S4mQaLnUCMDHtN8ywgpkLAELUzyYQzK1P9ewHPMEkoTUNYHtFYkR9K2Axx3drHqcrG1bEPqaL", + "programIdIndex": 35 + }, + { + "accounts": [ + 17, + 16, + 35 + ], + "data": "8RkZ9BWdS73A6y6am8ubeh1UKvLMH4HKQK53BqCRGRUtiaEPzDLrQip", + "programIdIndex": 37 + }, + { + "accounts": [], + "data": "112vtEkQ8GUhgay9HR98m8N4F35daB4XSC8F2AVLX2T7JU9HGHBeVGv2Bo3mpQ5REae9FwDiTCpzNE7GYN1Ub76dqvTRdTT66HjCGPMw2NwnQFfeGNeauZWCEMCcJumGLY9A5b3Lg9gsNVf8Zswy7Jst6HdJmTH2k671gPb5MeQD78oc3aheK9bLXPYR1JfUqA4xXF9S1oqktfG9Ftnae7kXy5mm81Kf9a5CDoR2PN8HYKZcQ3MfC9SgV92QfyzYPJUXmtHuoZoPQnEefHAwJkTPSTXw8dbLxbWS27JsbkZ6HdtFEKh1eeRXE4hxZdD4HcCnsdCX7UKbVhic791hNCorcZiDRFewVZxvNrY86xJC1Rejp1uehPpFxiu1X27SXJR5KPPiV3v3RZ5yiQzCWrM99nuY4Hnm55DSkLdp7oyeSxecLzAkmnnFYbRaTSX6ukLn9KxMWAv3gZvGDdcK1VE16kGvkrKrHZD4D4M2o8enzfz2BHpd1vyASuYJneXucCF8yco5WypeBPZhdHaRBcvrcv74UKLFXW2pn6pvSGcjZPwCdARja2FjYPBZs3zmUJrfSdDEhGmh3s8fZoXeDeyQRcyQAMVQSWjGrERdiRbunfkjCCDgxHPh5xmY9VYwHzB5ZbcvTFSKXNnnTFPPqSfdDvqNTNHMruAbso1vAvziH8jYCGhKK9jsqmidytBdDkwoNrE9NmH2M7m9bZv9Ubf9vfxDx2doDSCdCyAGhY1upTCESCFMdzLorkFHkDxuuMLEAE7kBE5n4qjNx6WBfKcKhkCQr9TLKQ617anr8p2sBE1Crf5Hso43rFFpT79QcD9kg4FDWkBtaVNJxUwPK61JDGrXj7VUpz3bPLEDdvduHVWsekDgTDGZeUPc3fwk79k3bCmm", + "programIdIndex": 35 + } + ] + } + ], + "loadedAddresses": { + "readonly": [ + "3hCnRKCDDwjrjT9xSKnUf6wQW1k5CwiHwVs57Syh4LPc", + "6dJKZKdrqJ6oPse63ADMSe1m9Bjemv6r5b5PZfaYm8ts", + "HXxZRpcwf5oHHcPQMLZ7KWPJJ3WZaNughAPqWv1LX3UR", + "HarYhsPxpM8Qu1F7nxNQaSZJZpZJ4odxYSopqykXDzSG", + "7MjNh26LUJgwfM4XgPyGCzZd8tbH3ZqYhsjpjDQ1m4ki", + "34rsLFmHgPwncdKKa55T5w9CKAvd3xMYz5eAMZnyjEHF", + "canSFmMSWjTnKn5yfMb6VQ83AbpSpmo9GW7ctwy5UL3", + "11111111111111111111111111111111", + "hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8", + "2oQ6R7SW88dTFGZDkSCkjoK2evembZtA6tcxvC3ve3CT", + "Fv5hf1Fg58htfC7YEXKNEfkpuogUUQDDTLgjGWxxv48H", + "BQ3MCuTT5zVBhNfQ4SjMh3NPVhFy73MPV8rjfq5d1zie", + "4ewWZC5gT6TGpm5LZNDs9wVonfUT2q5PP5sc9kVbwMAK", + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", + "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" + ], + "writable": [ + "8n64SmPfKRf6ZR3AH3WtXBax6o9HN95B17sk8qbWu9us", + "BrX6opz1Qo2Yw86wVrnqyzpAfwoeT7mAQqrjV9crnE3y", + "g245XJ43RySEchJYnqkZAyPm5CCwdxRLKpn592uNLHh", + "9Yn32WbXK8JTVFpx5CGFrPKqbeQ7LE1UZi5bG7k9P4Rv", + "GLXbPYM459CUHX4h2uSBDR7ATv1UdCkje5Y7UaguBHhW", + "FPBicBBbcqtc9JK3xAv7j2hPmiPqLesXdeq3N6VyAbuT", + "C9gqeeZZFMUFQBZ2X6MoFVn1sUhphkwQ4BKwGqktu9RR" + ] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 1atrmQs3eq1N2FEYWu6tyTXbCjP4uQwExpjtnhXtS8h invoke [1]", + "Program log: Instruction: ExecuteTransactionV0", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 invoke [2]", + "Program log: Instruction: GenesisIssueHotspotV0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY invoke [3]", + "Program log: Instruction: MintToCollectionV1", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [4]", + "Program log: Instruction: Bubblegum Program Set Collection Size", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 17078 of 466049 compute units", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [4]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 442836 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK invoke [4]", + "Program log: Instruction: Append", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [5]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 393650 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK consumed 12533 of 405739 compute units", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY consumed 105173 of 496381 compute units", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY success", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 consumed 213090 of 600524 compute units", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 success", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 invoke [2]", + "Program log: Instruction: GenesisIssueHotspotV0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY invoke [3]", + "Program log: Instruction: MintToCollectionV1", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [4]", + "Program log: Instruction: Bubblegum Program Set Collection Size", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 17078 of 253432 compute units", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [4]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 230236 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK invoke [4]", + "Program log: Instruction: Append", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [5]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 215529 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK consumed 9545 of 224630 compute units", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY consumed 73573 of 286660 compute units", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY success", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 consumed 150201 of 359514 compute units", + "Program hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8 success", + "Program 1atrmQs3eq1N2FEYWu6tyTXbCjP4uQwExpjtnhXtS8h consumed 493369 of 700000 compute units", + "Program 1atrmQs3eq1N2FEYWu6tyTXbCjP4uQwExpjtnhXtS8h success" + ], + "postBalances": [ + 13615531680, + 946560, + 3730560, + 1746960, + 3730560, + 1746960, + 1, + 1141440, + 2199360, + 1001390880, + 0, + 0, + 0, + 0, + 5616720, + 5616720, + 1559040, + 3899771520, + 97236498080, + 1559040, + 58693345920, + 1461600, + 2644800, + 1461600, + 2853600, + 2644800, + 2199360, + 58385164080, + 1, + 1141440, + 2853600, + 0, + 3034560, + 0, + 1141440, + 1141440, + 1141440, + 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 13616483240, + 0, + 0, + 0, + 0, + 0, + 1, + 1141440, + 2199360, + 1001390880, + 0, + 0, + 0, + 0, + 5616720, + 5616720, + 1559040, + 3899771520, + 97247453120, + 1559040, + 58693345920, + 1461600, + 2644800, + 1461600, + 2853600, + 2644800, + 2199360, + 58385164080, + 1, + 1141440, + 2853600, + 0, + 3034560, + 0, + 1141440, + 1141440, + 1141440, + 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 195109311, + "transaction": [ + "AbLXN7V98aktKsi6aL91p3RmkyQFdyYGNHYMpJk5ySOLcdUclkjHIINJntucxWJ3XFW9yZP2aO4SS5esMpQqKQeAAQAIDg08T8z9CrdlPF7csXVhEB0ffCNBeZOhcp8Xz8p6pKrJanSfCQgbNLH2NDfLwRI4Q/Y597Jh2Eh02uRQrYUteJYm4QxTDNG1wGPkkJ4XVbMzGP49rIHwMQM2hLbmfsMd7XOYxZrmlbu/aBXUENICPfGpr5riRwOakVG7czObFPKdTP3ZK1eZVoJwYlzFhM2Oi3jJ4hwZIitCed2Jj5Q5Te7F5B+4N/guZt81iEQYRiVVIXYmt9WMCXFhYCe+a6VYxwMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAACZS/dKSS7FFnW51YwDm0qD9peZ2i0G+WoSTQ5C3zdoez/obcN1L9l3KoKOrQnrJfIERkbKkP0FX2DMFaVvq8JSIS6K3fj/wnJTXTIdiQe27s2rqOdDgATNm2IYs1IxxMGfh5ntG89DuUDImkHVWLGHLwtMtcpR48k63L2mQme9optTCnQ7oW8a1ujOu7Gj0aQkF3yx4XoGT7noKgXbzB09kLG0wiKRpl4FTjaATJXguia9lNqQm83VzRbBdrFV5JoajO4911f9lM3rS6Zt2IZeIw6HYE2UkExcmGWtv2qvtbTnrKopvMBp0R4vlKuMhKPkIEIa+BS5kFTQPSxP3JgIGAAUCYK4KAAcmABobEgEcHRIVDh4WCB8gAgMTCRQhIiMkJRwXDxgZBAUQChELDA3cAdldrmGCt34sAgAAAAATAAAAAQIDBAUGBwgJCgsMDQ4PEBESE0gAAAAZss4XKNQ5XyYAAAAAACaRcQ2qDEFGNYivXJfQ6B1rszzJBDfKaT+how6qQg0tKxJouAH/mwqL1PHBCAEKAAAAASgAAAABAQAAEwAAAAEUFRYXBgcIGBkaGxwODxAREhNIAAAAGbLOFyjUOV8mAAAAAACDHcRONA+8/xvGqrTrfWUSwrDoDw27TyRvh4CQ3N5CuRjPndAB/xsjFtxGxAgBEAAAAAEoAAAAAQEAAAAAANEjBQACe4SwDquYEP3ABgS/kuJV5iTb9hz1ZN27iyS/4fOcdRwE/woMDQX+/AkLBwiE3SwMB5CNMJr6sUgqvnym/0x5ZYFCo0ZsCFawzBZDAyIBAgwgERYjABcdGBITLBQ=", + "base64" + ], + "version": 0 + } \ No newline at end of file diff --git a/blockbuster/tests/fixtures/helium_nested.json b/blockbuster/tests/fixtures/helium_nested.json new file mode 100644 index 0000000..7ea1159 --- /dev/null +++ b/blockbuster/tests/fixtures/helium_nested.json @@ -0,0 +1,257 @@ +{ + "blockTime": 1670988259, + "meta": { + "computeUnitsConsumed": 192758, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [ + 0, + 1 + ], + "data": "111183WoV6ghGmq1bAjAaExRNVss7TWYU13qHQDCyEEG3XQCD2mmVTJb1Ktzgt9ZKiEqNX", + "programIdIndex": 8 + }, + { + "accounts": [ + 13, + 18, + 4, + 0, + 6, + 12, + 25, + 8, + 24 + ], + "data": "7DQYMN554iaKFme3Q3weXMABVTFgifLCWyshjXFPbPwbhTre4ocuBABc3jK3YXsmFjGgF9JFszj2oHcmT", + "programIdIndex": 17 + }, + { + "accounts": [ + 4, + 6, + 18 + ], + "data": "C", + "programIdIndex": 25 + }, + { + "accounts": [ + 4, + 6, + 0 + ], + "data": "6vPfxaE6dMFu", + "programIdIndex": 25 + }, + { + "accounts": [ + 4, + 6, + 18 + ], + "data": "B", + "programIdIndex": 25 + }, + { + "accounts": [ + 2, + 0, + 0, + 3, + 0, + 13, + 13, + 14, + 19, + 7, + 11, + 9, + 22, + 15, + 21, + 8 + ], + "data": "TvtTj5iYFZtqetS8Y3aVanTB4F5svNs5UMdAUzpp4ZN9ajuqKJZkLwkeUwqziPetCRtDGJLREBvbJSMexX8FC7tBYzswhAyUmzCki8AqRLqbPvFCx7kRJqFHXJE3xYhRmgjCwyqfUFZ5ugcc72B7VU7g1vddeoEe2tvfxjhYHob57dNdjMyvXNb7QJEHhPihgCMHE5WPUMAf92pLyHccqoCDrDRPjspDhNcyip7TBDbKC36iJeCLUwzQ8eqFfaD8uF7y53JCF", + "programIdIndex": 14 + }, + { + "accounts": [ + 7, + 13, + 19, + 9 + ], + "data": "TazrCJmyCiST", + "programIdIndex": 21 + }, + { + "accounts": [], + "data": "2GJh7oUmkZKoxWwpm7fkx3PY8ZyA9RWzNfYwWWNxQ9B5Y3fv8WZLz2hknnhgKTGvLmjNqeSmexVAkbhTuAoz7mYgQ7vD5nVZmVH4XEr2sRUzGkNdeaNReXKUtLZnrL7n1TCXHS347zDpdh7iXzkPZujWTU5jXLYq9RNVx7mgkuNxtKzWvEn2FZkhbaj1rQfEsCs3pznjb1CntWwheQVMxEi3UzkNKxmACV4umkANNmdgZt2Vhub7UPWHXfJKio8xNc1ne7YMbk1vQaxtPsTpSZWc9VsWR", + "programIdIndex": 22 + }, + { + "accounts": [ + 3, + 2, + 22 + ], + "data": "8RkZ9BWdS73BHnVYLoXxr4cHnXaYPYgiP1KumFycRWihSyJjYAcCuYM", + "programIdIndex": 15 + }, + { + "accounts": [], + "data": "11SNhoEi4fQkop41Rapc8vMYZBUQ7PGEH8E15mji6m3PSQYAT6paXf8o78TVXRHCGBKrT5ZQsBzaApavTsrgYjpeG9FJvQCP6p6tvuvP4dXK7RYguXkACiTWWKwqTFYcxDFdeX8Ucs5stwRgwKShFnZzSFPwpg9py4DM5qQgpESQ8xvQUXgfxD1VNA2wZJXqoEeTWEaCg4nqwboz8sh29gtgcZ29S2bej8CjFMBMvuNyPAdy4cyDKvEC16pfvNNbQLErdoLXe1HUhTD5mhMkNHbDpSDv1ewBpTmEJRxgJoqtV7TGGNYFciVKUfoQKoXPNaNg5E9ZV8h2UbHtgyLzTAG7yZvtXLLNBVh8DZn6GtvUSjEFGjkubh4MDjSxHNGFaTtVANJEvySkd6W8spmWCKAn4swCXZESY1KZfFrXqJq8gDtTwcESsMzoN1hDLVG6z8aTxntDmNkR2Kjv3oiLBFcVNyrCKHYM3MA6ucrtjxZUMc7fiAzPbVL53F3G1W6xiJqA1poEwyaDmjsRHpw3svbGTu9bqpev2hgo7h73dgZLQaGmNNj4fcrKQui75dGhpZzAgwk5qCPwouXRVYg8BPaPTrr1ViNB1rz85SKWyi1op81ZFaEQjAzeRnvnwK2kub5tfA7MH9qJHiSh2X17B4Mo1LGFASZKbGQHMgsqfTkJ36X3sSH46dvNoqH1S31FhGShFwXmAqxgjaoE3REvy3rLh7qjeok6jcbZrx3dt9MdYuwNBrtWD914GkT4EkgAj9xSyvmHjiiEra9AQ7yHta3USxhYPcgztYgvdsTJKeSAnKLrgnpzKcp3uPHTemkrByLZQgngMqaiBUwF4eMb4GducWjdgtth6JpVoMiujpUs3Ww1X8HFB521164tzGGLboAfJqYT3gpKjRzzbxMScSEJQFAySQu66BjXC4MYcdfkTFqNFdCfo47veKtBvtQi2QYvNYmHQkG57EVghvH1XEfMs5ejhGAL2CL4bv7ooLSyGXkdhs2mwNru6g4Yk4KhwiZcPgK8yV6oENuo5UJqeNWSX2p82JK49PpEjX6kHU7ejYnbnDhLxTYpofjgA4KviGVCntEq8yLopo2sNHW54HZSo514pBEbQ64dFC6K8Mm9dxCvnMCfGHJRe2XWTo53PJMZBkchH73PPuosvk8HrHd9Nwwgyke47Yhf3LJajYySRmfRyXXL7CvDvf8zVgchizrCnghjwhLxjxpWSmJaR4FfxXNAArfrq2xxZTiP8aytn9U53SY2KCk4TpxnQnH94UtbwKaN1Z6eqEq6KDpfPUXtQ6ugEjXiPha6nYYz8jZC5iiRVyHS9cCn3yrdZM5oac35EZrcQphWyAipSf662PxnH96eu8FyiAYH2iY4jQWiGmS6Mh9", + "programIdIndex": 22 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program hemABtqNUst4MmqsVcuN217ZzBspENbGt9uueSe5jts invoke [1]", + "Program log: Instruction: IssueIotHotspotV0", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program credacwrBVewZAgCwNgowCSMbCiepuesprUWPBeLTSg invoke [2]", + "Program log: Instruction: BurnFromIssuanceV0", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", + "Program log: Instruction: ThawAccount", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4267 of 260868 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", + "Program log: Instruction: Burn", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4753 of 253735 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", + "Program log: Instruction: FreezeAccount", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4265 of 246133 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program credacwrBVewZAgCwNgowCSMbCiepuesprUWPBeLTSg consumed 46042 of 287052 compute units", + "Program credacwrBVewZAgCwNgowCSMbCiepuesprUWPBeLTSg success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY invoke [2]", + "Program log: Instruction: MintToCollectionV1", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [3]", + "Program log: Instruction: Bubblegum Program Set Collection Size", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 5378 of 194964 compute units", + "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [3]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 181511 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK invoke [3]", + "Program log: Instruction: Append", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV invoke [4]", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV consumed 97 of 163110 compute units", + "Program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV success", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK consumed 11904 of 174570 compute units", + "Program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK success", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY consumed 61791 of 222636 compute units", + "Program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY success", + "Program hemABtqNUst4MmqsVcuN217ZzBspENbGt9uueSe5jts consumed 192758 of 350000 compute units", + "Program hemABtqNUst4MmqsVcuN217ZzBspENbGt9uueSe5jts success" + ], + "postBalances": [ + 21224879240, + 2032320, + 1559040, + 6222295680, + 2039280, + 2088000, + 1461600, + 5616720, + 1, + 0, + 3312960, + 2853600, + 731913600, + 3034560, + 1141440, + 1141440, + 1, + 1141440, + 2436000, + 1461600, + 1141440, + 1141440, + 1141440, + 1169280, + 1009200, + 934087680 + ], + "postTokenBalances": [ + { + "accountIndex": 4, + "mint": "EQistL7vTTXGUMu873Ff2sHDppPg7rYXrKG1D6LEBcvm", + "owner": "devTUdgycPzE5WKyeokLp9txGo7ohmxZxNYUkVDpwuZ", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "985", + "decimals": 0, + "uiAmount": 985.0, + "uiAmountString": "985" + } + } + ], + "preBalances": [ + 21226916560, + 0, + 1559040, + 6222295680, + 2039280, + 2088000, + 1461600, + 5616720, + 1, + 0, + 3312960, + 2853600, + 731913600, + 3034560, + 1141440, + 1141440, + 1, + 1141440, + 2436000, + 1461600, + 1141440, + 1141440, + 1141440, + 1169280, + 1009200, + 934087680 + ], + "preTokenBalances": [ + { + "accountIndex": 4, + "mint": "EQistL7vTTXGUMu873Ff2sHDppPg7rYXrKG1D6LEBcvm", + "owner": "devTUdgycPzE5WKyeokLp9txGo7ohmxZxNYUkVDpwuZ", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "990", + "decimals": 0, + "uiAmount": 990.0, + "uiAmountString": "990" + } + } + ], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 181981476, + "transaction": [ + "Ac/AYoM8uDF6r1DKMjZnN5NI9EnL1pnoner9RHcKHGK12MNZaTUA/8lZjuwNUg2+R/wxnq/6LPqlZPCqSSakkQkBABIaCWPJi7x87iRK6BaHT3Jj4G1BcfgNw4rtd0w68Tp2zlgzCpdD+bm32zuDU5+OX6NQLXZ3FmTcrq36zX9rJEU/gWoVdpVys/m1EqOX7oV4leO1Ba6grTmOizQVxzzIfPQ0fclkduBfngDTC1kVqOtIJmxv75OMSIRMzwUaQPcJMfGGxqjL9T/ACPO0uQlJsb6Xlf5ztej1X1PWvIOVD7+Nr5gH2MzW2HUkRGRNqmFzSit0kfgLNNI4wsDolzlESB87xzujtgoAjaM3JBqoQOVH6xl0XtiVXBDyBuXFO3np9ZrN80oOcB0/i10lD1zDXDBelEu8N/xlBXbloj8xICGdDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANksvVGGh1/jAC7tldzhmJmOTAdMlS8yvab8wTs7onrxfn8+mCiILSR01EKbqWOYujG9PH00S8UotQ/YATlutw4QTKWsrYj/qkdAKFCdgZSZ0JXMC1mucMVI5k+tZ6U5rjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FmXhHYt9eZ9mniNnUsIo4px+LmpIQbH1AvQpV5zAY3SGZiLgOt5NShpsiR0X1ndv4omWMoT3GiBISY1HK4HwaWlCSoT7pXEHLoIpn9axn6N9+HaEWJeHWQTf49PI4MDfxQDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAkvd2YCW4+Zl6Dmr5L6ugzgb+c+Mb1Ire6+F+LeI/MBCXjmaL47M8hm/k8W+M48mGKwD5tZ8Y6r2F2Xy1aISUjF2mxj+7t0xH1rCjnGXZwQVE2Q1OK3ieYUU4QD6lWKlgpp7eP3ITZacHbGLIivBHhmYKQNTed2JfbSMFQUag14C3BlsePRfEU4nVJ/awTDzVi4bHMaoP21SbbRvAP4KUYLvA/Au0fKL3TEES6UqxPPo8Y05dwX6ssDzRojzX54fAan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqelSauQAMeqBvJ9LaWRxT7COxhZhsFMxr6pLH0M5CtwCAhAABQIwVwUAFBsAAAATBwsNCgUBAgADCRIGBBURFg4PDAgZFxhA/usuIvJbj50zAAAAMTF0MVl2bTdRYnlWbm1xZENVcGZBOFhVaUdWYnBIUFZuYU50UjI1Z2I4cDJkNER6anhpAQ==", + "base64" + ] +} \ No newline at end of file diff --git a/blockbuster/tests/helpers.rs b/blockbuster/tests/helpers.rs new file mode 100644 index 0000000..953dad3 --- /dev/null +++ b/blockbuster/tests/helpers.rs @@ -0,0 +1,396 @@ +// Workaround since this module is only used for testing. +#![allow(dead_code)] +use blockbuster::{ + error::BlockbusterError, + instruction::{InstructionBundle, IxPair}, +}; +use borsh::ser::BorshSerialize; +use flatbuffers::{FlatBufferBuilder, WIPOffset}; +use mpl_bubblegum::LeafSchemaEvent; +use plerkle_serialization::{ + root_as_account_info, root_as_compiled_instruction, + serializer::seralize_encoded_transaction_with_status, AccountInfo, AccountInfoArgs, + CompiledInstruction as FBCompiledInstruction, CompiledInstructionBuilder, + InnerInstructionsBuilder, Pubkey as FBPubkey, TransactionInfo, TransactionInfoBuilder, +}; +use rand::Rng; +use solana_geyser_plugin_interface::geyser_plugin_interface::ReplicaAccountInfo; +use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; +use solana_transaction_status::{ + EncodedConfirmedTransactionWithStatusMeta, InnerInstruction, InnerInstructions, +}; +use spl_account_compression::events::{ + AccountCompressionEvent, ApplicationDataEvent, ApplicationDataEventV1, +}; +use std::{fs::File, io::BufReader}; + +pub fn random_program() -> Pubkey { + Pubkey::new_unique() +} + +pub fn random_pubkey() -> Pubkey { + random_program() +} + +pub fn random_data(max: usize) -> Vec { + let mut s = rand::thread_rng(); + let x = s.gen_range(1..max); + let mut data: Vec = Vec::with_capacity(x); + for i in 0..x { + let d: u8 = s.gen_range(0..255); + data.insert(i, d); + } + data +} + +pub fn random_u8() -> u8 { + let mut s = rand::thread_rng(); + s.gen() +} + +pub fn random_u8_bound(min: u8, max: u8) -> u8 { + let mut s = rand::thread_rng(); + s.gen_range(min..max) +} + +pub fn random_list(size: usize, elem_max: u8) -> Vec { + let mut s = rand::thread_rng(); + let mut data: Vec = Vec::with_capacity(size); + for i in 0..size { + let d: u8 = s.gen_range(0..elem_max); + data.insert(i, d); + } + data +} + +pub fn random_list_of(size: usize, fun: FN) -> Vec +where + FN: Fn(u8) -> T, +{ + let mut s = rand::thread_rng(); + let mut data: Vec = Vec::with_capacity(size); + for i in 0..size { + data.insert(i, fun(s.gen())); + } + data +} + +pub fn build_random_instruction<'a>( + fbb: &mut FlatBufferBuilder<'a>, + accounts_number_in_transaction: usize, + number_of_accounts: usize, +) -> WIPOffset> { + let accounts = random_list(5, random_u8_bound(1, number_of_accounts as u8)); + let accounts = fbb.create_vector(&accounts); + let data = random_data(10); + let data = fbb.create_vector(&data); + let mut s = rand::thread_rng(); + let mut builder = CompiledInstructionBuilder::new(fbb); + builder.add_data(data); + builder.add_program_id_index(s.gen_range(0..accounts_number_in_transaction) as u8); + builder.add_accounts(accounts); + builder.finish() +} + +pub fn build_random_transaction(mut fbb: FlatBufferBuilder) -> FlatBufferBuilder { + let mut s = rand::thread_rng(); + let mut outer_instructions = vec![]; + let mut inner_instructions = vec![]; + for _ in 0..s.gen_range(2..7) { + outer_instructions.push(build_random_instruction(&mut fbb, 10, 3)); + + let mut indexed_inner_instructions = vec![]; + for _ in 0..s.gen_range(2..7) { + let ix = build_random_instruction(&mut fbb, 10, 3); + indexed_inner_instructions.push(ix); + } + + let indexed_inner_instructions = fbb.create_vector(&indexed_inner_instructions); + let mut builder = InnerInstructionsBuilder::new(&mut fbb); + builder.add_index(s.gen_range(0..7)); + builder.add_instructions(indexed_inner_instructions); + inner_instructions.push(builder.finish()); + } + + let outer_instructions = fbb.create_vector(&outer_instructions); + let inner_instructions = fbb.create_vector(&inner_instructions); + let account_keys = random_list_of(10, |_| FBPubkey(random_pubkey().to_bytes())); + let account_keys = fbb.create_vector(&account_keys); + let mut builder = TransactionInfoBuilder::new(&mut fbb); + let slot = s.gen(); + builder.add_outer_instructions(outer_instructions); + builder.add_is_vote(false); + builder.add_inner_instructions(inner_instructions); + builder.add_account_keys(account_keys); + builder.add_slot(slot); + builder.add_seen_at(s.gen()); + let builder = builder.finish(); + fbb.finish_minimal(builder); + fbb +} + +pub fn get_programs(txn_info: TransactionInfo) -> Vec { + let mut outer_keys: Vec = txn_info + .outer_instructions() + .unwrap() + .iter() + .map(|ix| { + Pubkey::new_from_array( + txn_info + .account_keys() + .unwrap() + .iter() + .collect::>() + .get(ix.program_id_index() as usize) + .unwrap() + .0, + ) + }) + .collect(); + let mut inner = vec![]; + let inner_keys = txn_info + .inner_instructions() + .unwrap() + .iter() + .fold(&mut inner, |ix, curr| { + for p in curr.instructions().unwrap() { + ix.push(Pubkey::new_from_array( + txn_info + .account_keys() + .unwrap() + .iter() + .collect::>() + .get(p.program_id_index() as usize) + .unwrap() + .0, + )) + } + ix + }); + outer_keys.append(inner_keys); + outer_keys.dedup(); + outer_keys +} + +pub fn build_instruction<'a>( + fbb: &'a mut FlatBufferBuilder<'a>, + data: &[u8], + account_indexes: &[u8], +) -> Result { + let accounts_vec = fbb.create_vector(account_indexes); + let ix_data = fbb.create_vector(data); + let mut builder = CompiledInstructionBuilder::new(fbb); + builder.add_accounts(accounts_vec); + builder.add_program_id_index(0); + builder.add_data(ix_data); + let offset = builder.finish(); + fbb.finish_minimal(offset); + let data = fbb.finished_data(); + + let cix = root_as_compiled_instruction(data)?; + Ok(CompiledInstruction { + program_id_index: cix.program_id_index(), + accounts: cix + .accounts() + .expect("failed to deserialize accounts") + .bytes() + .to_vec(), + data: cix + .data() + .expect("failed to deserialize data") + .bytes() + .to_vec(), + }) +} + +pub fn build_account_update<'a>( + fbb: &'a mut FlatBufferBuilder<'a>, + account: &ReplicaAccountInfo, + slot: u64, + is_startup: bool, +) -> Result, flatbuffers::InvalidFlatbuffer> { + // Serialize vector data. + let pubkey = FBPubkey::from(account.pubkey); + let owner = FBPubkey::from(account.owner); + + // Don't serialize a zero-length data slice. + let data = if !account.data.is_empty() { + Some(fbb.create_vector(account.data)) + } else { + None + }; + + // Serialize everything into Account Info table. + let account_info = AccountInfo::create( + fbb, + &AccountInfoArgs { + pubkey: Some(&pubkey), + lamports: account.lamports, + owner: Some(&owner), + executable: account.executable, + rent_epoch: account.rent_epoch, + data, + write_version: account.write_version, + slot, + is_startup, + seen_at: 0, + }, + ); + + // Finalize buffer + fbb.finish(account_info, None); + let data = fbb.finished_data(); + root_as_account_info(data) +} + +pub fn build_random_account_update<'a>( + fbb: &'a mut FlatBufferBuilder<'a>, + data: &[u8], +) -> Result, flatbuffers::InvalidFlatbuffer> { + // Create a `ReplicaAccountInfo` to store the account update. + // All fields except caller-specified `data` are just random values. + let replica_account_info = ReplicaAccountInfo { + pubkey: &random_pubkey().to_bytes()[..], + lamports: 1, + owner: &random_pubkey().to_bytes()[..], + executable: false, + rent_epoch: 1000, + data, + write_version: 1, + }; + + // Flatbuffer serialize the `ReplicaAccountInfo`. + build_account_update(fbb, &replica_account_info, 0, false) +} + +pub fn build_txn_from_fixture( + fixture_name: String, + fbb: FlatBufferBuilder, +) -> Result { + let file = File::open(format!( + "{}/tests/fixtures/{}.json", + env!("CARGO_MANIFEST_DIR"), + fixture_name + )) + .unwrap(); + let reader = BufReader::new(file); + let ectxn: EncodedConfirmedTransactionWithStatusMeta = serde_json::from_reader(reader).unwrap(); + Ok(seralize_encoded_transaction_with_status(fbb, ectxn) + .expect("failed serialize encoded tx with status")) +} + +#[allow(clippy::too_many_arguments)] +pub fn build_bubblegum_bundle<'a>( + fbb1: &'a mut FlatBufferBuilder<'a>, + fbb2: &'a mut FlatBufferBuilder<'a>, + fbb3: &'a mut FlatBufferBuilder<'a>, + fbb4: &'a mut FlatBufferBuilder<'a>, + accounts: &'a [Pubkey], + account_indexes: &'a [u8], + ix_data: &'a [u8], + lse: LeafSchemaEvent, + cs_event: AccountCompressionEvent, +) -> InstructionBundle<'a> { + let lse_versioned = ApplicationDataEventV1 { + application_data: lse.try_to_vec().unwrap(), + }; + let lse_event = + AccountCompressionEvent::ApplicationData(ApplicationDataEvent::V1(lse_versioned)); + let outer_ix = build_instruction(fbb1, ix_data, account_indexes).unwrap(); + + let lse = lse_event.try_to_vec().unwrap(); + let noop_bgum = spl_noop::instruction(lse).data; + let ix = build_instruction(fbb2, &noop_bgum, account_indexes).unwrap(); + let noop_bgum_ix: IxPair = (spl_noop::id(), Box::leak(Box::new(ix))); + + // The Compression Instruction here doesnt matter only the noop but we add it here to ensure we are validating that one Account compression event is happening after Bubblegum + let ix = build_instruction(fbb3, &[0; 0], account_indexes).unwrap(); + let gummy_roll_ix: IxPair = (spl_account_compression::id(), Box::leak(Box::new(ix))); + + let cs = cs_event.try_to_vec().unwrap(); + let noop_compression = spl_noop::instruction(cs).data; + let ix = build_instruction(fbb4, &noop_compression, account_indexes).unwrap(); + let noop_compression_ix: IxPair = (spl_noop::id(), Box::leak(Box::new(ix))); + + let inner_ix = vec![noop_bgum_ix, gummy_roll_ix, noop_compression_ix]; + + // `Box::leak` is ok for tests + InstructionBundle { + program: mpl_bubblegum::ID, + inner_ix: Some(Box::leak(Box::new(inner_ix))), + keys: accounts, + instruction: Some(Box::leak(Box::new(outer_ix))), + ..Default::default() + } +} + +pub fn parse_fb( + tx_info: &TransactionInfo, +) -> ( + Vec, + Vec, + Vec, +) { + let mut account_keys = vec![]; + for key in tx_info.account_keys().iter().flatten() { + account_keys.push(Pubkey::try_from(key.0.as_slice()).expect("valid key from FlatBuffer")); + } + + let mut message_instructions = vec![]; + for cix in tx_info + .outer_instructions() + .expect("valid outer_instructions") + { + message_instructions.push(CompiledInstruction { + program_id_index: cix.program_id_index(), + accounts: cix.accounts().expect("valid accounts").bytes().to_vec(), + data: cix.data().expect("valid data").bytes().to_vec(), + }); + } + + let mut meta_inner_instructions = vec![]; + if let Some(ixs) = tx_info.compiled_inner_instructions() { + for ix in ixs { + let mut instructions = vec![]; + for ix in ix.instructions().expect("valid instructions") { + let cix = ix.compiled_instruction().expect("valid instruction"); + instructions.push(InnerInstruction { + instruction: CompiledInstruction { + program_id_index: cix.program_id_index(), + accounts: cix.accounts().expect("valid accounts").bytes().to_vec(), + data: cix.data().expect("valid data").bytes().to_vec(), + }, + stack_height: Some(ix.stack_height() as u32), + }) + } + + meta_inner_instructions.push(InnerInstructions { + index: ix.index(), + instructions, + }) + } + } else if let Some(ixs) = tx_info.inner_instructions() { + for ix in ixs { + let mut instructions = vec![]; + for cix in ix.instructions().expect("valid instructions") { + instructions.push(InnerInstruction { + instruction: CompiledInstruction { + program_id_index: cix.program_id_index(), + accounts: cix.accounts().expect("valid accounts").bytes().to_vec(), + data: cix.data().expect("valid data").bytes().to_vec(), + }, + stack_height: Some(0), + }) + } + + meta_inner_instructions.push(InnerInstructions { + index: ix.index(), + instructions, + }) + } + } else { + panic!("expect valid compiled_inner_instructions/inner_instructions") + } + + (account_keys, message_instructions, meta_inner_instructions) +} diff --git a/blockbuster/tests/instructions_test.rs b/blockbuster/tests/instructions_test.rs new file mode 100644 index 0000000..b13a9d7 --- /dev/null +++ b/blockbuster/tests/instructions_test.rs @@ -0,0 +1,227 @@ +#[cfg(test)] +mod helpers; +use anchor_lang::AnchorDeserialize; +use blockbuster::{ + instruction::{order_instructions, InstructionBundle}, + program_handler::ProgramParser, + programs::{ + bubblegum::{BubblegumParser, LeafSchemaEvent, Payload}, + ProgramParseResult, + }, +}; +use flatbuffers::FlatBufferBuilder; +use helpers::*; +use plerkle_serialization::root_as_transaction_info; +use rand::prelude::IteratorRandom; +use spl_account_compression::events::{ + AccountCompressionEvent::{self}, + ApplicationDataEvent, ApplicationDataEventV1, ChangeLogEvent, ChangeLogEventV1, +}; +use std::{collections::HashSet, env}; + +#[test] +fn test_filter() { + let mut rng = rand::thread_rng(); + let fbb = FlatBufferBuilder::new(); + let fbb = build_random_transaction(fbb); + let data = fbb.finished_data(); + let txn = root_as_transaction_info(data).expect("TODO: panic message"); + let programs = get_programs(txn); + let hs = programs + .iter() + .choose_multiple(&mut rng, 3) + .into_iter() + .copied() + .collect::>(); + let (account_keys, message_instructions, meta_inner_instructions) = parse_fb(&txn); + let res = order_instructions( + &hs, + &account_keys, + &message_instructions, + &meta_inner_instructions, + ); + + for (ib, _inner) in res.iter() { + let public_key_matches = hs.contains(&ib.0); + assert!(public_key_matches); + } + + let res = order_instructions( + &HashSet::new(), + &account_keys, + &message_instructions, + &meta_inner_instructions, + ); + assert_eq!(res.len(), 0); +} + +fn prepare_fixture<'a>(fbb: FlatBufferBuilder<'a>, fixture: &'a str) -> FlatBufferBuilder<'a> { + println!("{:?}", env::current_dir()); + let name = fixture.to_string(); + let fbb = build_txn_from_fixture(name, fbb).unwrap(); + fbb +} + +#[test] +fn helium_nested() { + let fbb = FlatBufferBuilder::new(); + let txn = prepare_fixture(fbb, "helium_nested"); + let txn = root_as_transaction_info(txn.finished_data()).expect("Fail deser"); + let mut prog = HashSet::new(); + let id = mpl_bubblegum::ID; + let slot = txn.slot(); + prog.insert(id); + let (account_keys, message_instructions, meta_inner_instructions) = parse_fb(&txn); + let res = order_instructions( + &prog, + &account_keys, + &message_instructions, + &meta_inner_instructions, + ); + + let _ix = 0; + + let contains = res.iter().any(|(ib, _inner)| ib.0 == mpl_bubblegum::ID); + assert!(contains, "Must containe bgum at hoisted root"); + let subject = BubblegumParser {}; + for (outer_ix, inner_ix) in res.into_iter() { + let (program, instruction) = outer_ix; + // let ix_accounts = instruction.accounts.iter().collect::>(); + let ix_account_len = instruction.accounts.len(); + // let _max = ix_accounts.iter().max().copied().unwrap_or(0) as usize; + let ix_accounts = + instruction + .accounts + .iter() + .fold(Vec::with_capacity(ix_account_len), |mut acc, a| { + if let Some(key) = account_keys.get(*a as usize) { + acc.push(*key); + } + //else case here is handled on 272 + acc + }); + let bundle = InstructionBundle { + txn_id: "", + program, + instruction: Some(instruction), + inner_ix: inner_ix.as_deref(), + keys: ix_accounts.as_slice(), + slot, + }; + let result = subject.handle_instruction(&bundle).unwrap(); + let res_type = result.result_type(); + let parse_result = match res_type { + ProgramParseResult::Bubblegum(parse_result) => parse_result, + _ => panic!("Wrong type"), + }; + + if let ( + Some(_le), + Some(_cl), + Some(Payload::MintV1 { + args: _, + authority: _, + tree_id: _, + }), + ) = ( + &parse_result.leaf_update, + &parse_result.tree_update, + &parse_result.payload, + ) { + } else { + panic!("Failed to parse instruction"); + } + } +} + +#[test] +fn test_double_mint() { + let fbb = FlatBufferBuilder::new(); + let txn = prepare_fixture(fbb, "double_bubblegum_mint"); + let txn = root_as_transaction_info(txn.finished_data()).expect("Fail deser"); + let mut programs = HashSet::new(); + let subject = BubblegumParser {}.key(); + programs.insert(subject); + let (account_keys, message_instructions, meta_inner_instructions) = parse_fb(&txn); + let ix = order_instructions( + &programs, + &account_keys, + &message_instructions, + &meta_inner_instructions, + ); + assert_eq!(ix.len(), 2); + let contains = ix.iter().filter(|(ib, _inner)| ib.0 == mpl_bubblegum::ID); + let mut count = 0; + contains.for_each(|(_pk, cix)| { + count += 1; + if let Some(inner) = &cix { + println!("{}", inner.len()); + for ii in inner { + println!("pp{} {:?}", count, ii.0); + } + println!("------"); + let cl = AccountCompressionEvent::try_from_slice(&inner[1].1.data).unwrap(); + if let AccountCompressionEvent::ApplicationData(ApplicationDataEvent::V1( + ApplicationDataEventV1 { application_data }, + )) = cl + { + let lse = LeafSchemaEvent::try_from_slice(&application_data).unwrap(); + println!("1 pp{} NONCE {:?}\n end", count, lse.schema.nonce()); + } + let cl = AccountCompressionEvent::try_from_slice(&inner[3].1.data).unwrap(); + if let AccountCompressionEvent::ChangeLog(ChangeLogEvent::V1(ChangeLogEventV1 { + id, + .. + })) = cl + { + println!("2 pp{} Merkle Tree {:?} \n end", count, id); + } + } + }); + assert_eq!(count, 2); +} + +#[test] +fn test_double_tree() { + let fbb = FlatBufferBuilder::new(); + let txn = prepare_fixture(fbb, "helium_mint_double_tree"); + let txn = root_as_transaction_info(txn.finished_data()).expect("Fail deser"); + let mut programs = HashSet::new(); + let subject = BubblegumParser {}.key(); + programs.insert(subject); + let (account_keys, message_instructions, meta_inner_instructions) = parse_fb(&txn); + let ix = order_instructions( + &programs, + &account_keys, + &message_instructions, + &meta_inner_instructions, + ); + let contains = ix.iter().filter(|(ib, _inner)| ib.0 == mpl_bubblegum::ID); + let mut count = 0; + contains.for_each(|(_pk, cix)| { + if let Some(inner) = &cix { + for ii in inner { + println!("pp{} {:?}", count, ii.0); + } + println!("------"); + let cl = AccountCompressionEvent::try_from_slice(&inner[1].1.data).unwrap(); + if let AccountCompressionEvent::ApplicationData(ApplicationDataEvent::V1( + ApplicationDataEventV1 { application_data }, + )) = cl + { + let lse = LeafSchemaEvent::try_from_slice(&application_data).unwrap(); + println!("1 pp{} NONCE {:?}\n end", count, lse.schema.nonce()); + } + let cl = AccountCompressionEvent::try_from_slice(&inner[3].1.data).unwrap(); + if let AccountCompressionEvent::ChangeLog(ChangeLogEvent::V1(ChangeLogEventV1 { + id, + .. + })) = cl + { + println!("2 pp{} Merkle Tree {:?} \n end", count, id); + } + } + count += 1; + }); + assert_eq!(count, 2); +} diff --git a/bubblegum-backfill/Cargo.toml b/bubblegum-backfill/Cargo.toml new file mode 100644 index 0000000..8b49f75 --- /dev/null +++ b/bubblegum-backfill/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "das-bubblegum-backfill" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +publish = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +blockbuster = { workspace = true } +bs58 = { workspace = true } +das-core = { workspace = true } +solana-client = { workspace = true } +borsh = { workspace = true } +digital_asset_types = { workspace = true, features = [ + "json_types", + "sql_types", +] } +anchor-client = { workspace = true } +futures = { workspace = true } +clap = { workspace = true } +log = { workspace = true } +solana-program = { workspace = true } +program_transformers = { workspace = true } +heck = { workspace = true } +mpl-bubblegum = { workspace = true } +num-traits = { workspace = true } +sea-orm = { workspace = true } +serde_json = { workspace = true } +solana-sdk = { workspace = true } +solana-transaction-status = { workspace = true } +spl-account-compression = { workspace = true, features = ["no-entrypoint"] } +spl-token = { workspace = true, features = ["no-entrypoint"] } +sqlx = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/bubblegum-backfill/src/error.rs b/bubblegum-backfill/src/error.rs new file mode 100644 index 0000000..420a15a --- /dev/null +++ b/bubblegum-backfill/src/error.rs @@ -0,0 +1,19 @@ +#[derive(Debug, thiserror::Error)] +pub enum ErrorKind { + #[error("anchor")] + Anchor(#[from] anchor_client::anchor_lang::error::Error), + #[error("solana rpc")] + Rpc(#[from] solana_client::client_error::ClientError), + #[error("parse pubkey")] + ParsePubkey(#[from] solana_sdk::pubkey::ParsePubkeyError), + #[error("serialize tree response")] + SerializeTreeResponse, + #[error("sea orm")] + Database(#[from] sea_orm::DbErr), + #[error("try from pubkey")] + TryFromPubkey, + #[error("try from signature")] + TryFromSignature, + #[error("generic error: {0}")] + Generic(String), +} diff --git a/bubblegum-backfill/src/gap.rs b/bubblegum-backfill/src/gap.rs new file mode 100644 index 0000000..feb523b --- /dev/null +++ b/bubblegum-backfill/src/gap.rs @@ -0,0 +1,140 @@ +use super::ErrorKind; +use crate::Rpc; +use anyhow::Result; +use clap::Args; +use sea_orm::{DatabaseConnection, DbBackend, FromQueryResult, Statement, Value}; +use solana_client::rpc_response::RpcConfirmedTransactionStatusWithSignature; +use solana_sdk::{pubkey::Pubkey, signature::Signature}; +use std::str::FromStr; +use tokio::sync::mpsc::Sender; + +const GET_SIGNATURES_FOR_ADDRESS_LIMIT: usize = 1000; + +#[derive(Debug, Clone, Args)] +pub struct ConfigBackfiller { + /// Solana RPC URL + #[arg(long, env)] + pub solana_rpc_url: String, +} + +const TREE_GAP_SQL: &str = r#" +WITH sequenced_data AS ( + SELECT + tree, + seq, + LEAD(seq) OVER (ORDER BY seq ASC) AS next_seq, + tx AS current_tx, + LEAD(tx) OVER (ORDER BY seq ASC) AS next_tx + FROM + cl_audits_v2 + WHERE + tree = $1 +), +gaps AS ( + SELECT + tree, + seq AS gap_start_seq, + next_seq AS gap_end_seq, + current_tx AS lower_bound_tx, + next_tx AS upper_bound_tx + FROM + sequenced_data + WHERE + next_seq IS NOT NULL AND + next_seq - seq > 1 +) +SELECT + tree, + gap_start_seq, + gap_end_seq, + lower_bound_tx, + upper_bound_tx +FROM + gaps +ORDER BY + gap_start_seq; +"#; + +#[derive(Debug, FromQueryResult, PartialEq, Clone)] +pub struct TreeGapModel { + pub tree: Vec, + pub gap_start_seq: i64, + pub gap_end_seq: i64, + pub lower_bound_tx: Vec, + pub upper_bound_tx: Vec, +} + +impl TreeGapModel { + pub async fn find(conn: &DatabaseConnection, tree: Pubkey) -> Result, ErrorKind> { + let statement = Statement::from_sql_and_values( + DbBackend::Postgres, + TREE_GAP_SQL, + vec![Value::Bytes(Some(Box::new(tree.as_ref().to_vec())))], + ); + + TreeGapModel::find_by_statement(statement) + .all(conn) + .await + .map_err(Into::into) + } +} + +impl TryFrom for TreeGapFill { + type Error = ErrorKind; + + fn try_from(model: TreeGapModel) -> Result { + let tree = Pubkey::try_from(model.tree).map_err(|_| ErrorKind::TryFromPubkey)?; + let upper = + Signature::try_from(model.upper_bound_tx).map_err(|_| ErrorKind::TryFromSignature)?; + let lower = + Signature::try_from(model.lower_bound_tx).map_err(|_| ErrorKind::TryFromSignature)?; + + Ok(Self::new(tree, Some(upper), Some(lower))) + } +} + +pub struct TreeGapFill { + tree: Pubkey, + before: Option, + until: Option, +} + +impl TreeGapFill { + pub const fn new(tree: Pubkey, before: Option, until: Option) -> Self { + Self { + tree, + before, + until, + } + } + + pub async fn crawl(&self, client: Rpc, sender: Sender) -> Result<()> { + let mut before = self.before; + + loop { + let sigs = client + .get_signatures_for_address(&self.tree, before, self.until) + .await?; + let sig_count = sigs.len(); + + let successful_transactions = sigs + .into_iter() + .filter(|transaction| transaction.err.is_none()) + .collect::>(); + + for sig in successful_transactions.iter() { + let sig = Signature::from_str(&sig.signature)?; + + sender.send(sig).await?; + + before = Some(sig); + } + + if sig_count < GET_SIGNATURES_FOR_ADDRESS_LIMIT { + break; + } + } + + Ok(()) + } +} diff --git a/bubblegum-backfill/src/lib.rs b/bubblegum-backfill/src/lib.rs new file mode 100644 index 0000000..3b5495e --- /dev/null +++ b/bubblegum-backfill/src/lib.rs @@ -0,0 +1,69 @@ +mod error; +mod gap; +mod tree; +pub mod worker; + +pub use error::ErrorKind; + +use anyhow::Result; +use clap::Parser; +use das_core::Rpc; +use futures::{stream::FuturesUnordered, StreamExt}; +use tree::TreeResponse; +use worker::TreeWorkerArgs; + +#[derive(Clone)] +pub struct BubblegumBackfillContext { + pub database_pool: sqlx::PgPool, + pub solana_rpc: Rpc, +} + +impl BubblegumBackfillContext { + pub const fn new(database_pool: sqlx::PgPool, solana_rpc: Rpc) -> Self { + Self { + database_pool, + solana_rpc, + } + } +} + +#[derive(Debug, Parser, Clone)] +pub struct BubblegumBackfillArgs { + /// Number of tree crawler workers + #[arg(long, env, default_value = "20")] + pub tree_crawler_count: usize, + + /// The list of trees to crawl. If not specified, all trees will be crawled. + #[arg(long, env, use_value_delimiter = true)] + pub only_trees: Option>, + + #[clap(flatten)] + pub tree_worker: TreeWorkerArgs, +} + +pub async fn start_bubblegum_backfill( + context: BubblegumBackfillContext, + args: BubblegumBackfillArgs, +) -> Result<()> { + let trees = if let Some(ref only_trees) = args.only_trees { + TreeResponse::find(&context.solana_rpc, only_trees.clone()).await? + } else { + TreeResponse::all(&context.solana_rpc).await? + }; + + let mut crawl_handles = FuturesUnordered::new(); + + for tree in trees { + if crawl_handles.len() >= args.tree_crawler_count { + crawl_handles.next().await; + } + let context = context.clone(); + let handle = args.tree_worker.start(context, tree); + + crawl_handles.push(handle); + } + + futures::future::try_join_all(crawl_handles).await?; + + Ok(()) +} diff --git a/bubblegum-backfill/src/tree.rs b/bubblegum-backfill/src/tree.rs new file mode 100644 index 0000000..efb5c1c --- /dev/null +++ b/bubblegum-backfill/src/tree.rs @@ -0,0 +1,118 @@ +use super::ErrorKind; +use anyhow::Result; +use borsh::BorshDeserialize; +use das_core::Rpc; +use solana_client::rpc_filter::{Memcmp, RpcFilterType}; +use solana_sdk::{account::Account, pubkey::Pubkey}; +use spl_account_compression::id; +use spl_account_compression::state::{ + merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1, +}; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub struct TreeHeaderResponse { + pub max_depth: u32, + pub max_buffer_size: u32, + pub creation_slot: u64, + pub size: usize, +} + +impl TryFrom for TreeHeaderResponse { + type Error = ErrorKind; + + fn try_from(payload: ConcurrentMerkleTreeHeader) -> Result { + let size = merkle_tree_get_size(&payload)?; + + Ok(Self { + max_depth: payload.get_max_depth(), + max_buffer_size: payload.get_max_buffer_size(), + creation_slot: payload.get_creation_slot(), + size, + }) + } +} + +#[derive(Debug, Clone)] +pub struct TreeResponse { + pub pubkey: Pubkey, + pub tree_header: TreeHeaderResponse, + pub seq: u64, +} + +impl TreeResponse { + pub fn try_from_rpc(pubkey: Pubkey, account: Account) -> Result { + let bytes = account.data.as_slice(); + + let (header_bytes, rest) = bytes.split_at(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); + let header: ConcurrentMerkleTreeHeader = + ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; + + let merkle_tree_size = merkle_tree_get_size(&header)?; + let (tree_bytes, _canopy_bytes) = rest.split_at(merkle_tree_size); + + let seq_bytes = tree_bytes[0..8].try_into()?; + let seq = u64::from_le_bytes(seq_bytes); + + let (auth, _) = Pubkey::find_program_address(&[pubkey.as_ref()], &mpl_bubblegum::ID); + + header.assert_valid_authority(&auth)?; + + let tree_header = header.try_into()?; + + Ok(Self { + pubkey, + tree_header, + seq, + }) + } + + pub async fn all(client: &Rpc) -> Result, ErrorKind> { + Ok(client + .get_program_accounts( + &id(), + Some(vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes( + 0, + vec![1u8], + ))]), + ) + .await? + .into_iter() + .filter_map(|(pubkey, account)| Self::try_from_rpc(pubkey, account).ok()) + .collect()) + } + + pub async fn find(client: &Rpc, pubkeys: Vec) -> Result, ErrorKind> { + let pubkeys: Vec = pubkeys + .into_iter() + .map(|p| Pubkey::from_str(&p)) + .collect::, _>>()?; + let pubkey_batches = pubkeys.chunks(100); + let pubkey_batches_count = pubkey_batches.len(); + + let mut gma_handles = Vec::with_capacity(pubkey_batches_count); + + for batch in pubkey_batches { + gma_handles.push(async move { + let accounts = client.get_multiple_accounts(batch).await?; + + let results: Vec<(&Pubkey, Option)> = batch.iter().zip(accounts).collect(); + + Ok::<_, ErrorKind>(results) + }) + } + + let result = futures::future::try_join_all(gma_handles).await?; + + let trees = result + .into_iter() + .flatten() + .filter_map(|(pubkey, account)| { + account.map(|account| Self::try_from_rpc(*pubkey, account)) + }) + .collect::, _>>() + .map_err(|_| ErrorKind::SerializeTreeResponse)?; + + Ok(trees) + } +} diff --git a/bubblegum-backfill/src/worker/gap.rs b/bubblegum-backfill/src/worker/gap.rs new file mode 100644 index 0000000..07b88f4 --- /dev/null +++ b/bubblegum-backfill/src/worker/gap.rs @@ -0,0 +1,65 @@ +use anyhow::Result; +use clap::Parser; +use das_core::Rpc; +use futures::{stream::FuturesUnordered, StreamExt}; +use log::error; +use solana_sdk::signature::Signature; +use tokio::{ + sync::mpsc::{channel, Sender}, + task::JoinHandle, +}; + +use crate::gap::TreeGapFill; +use crate::BubblegumBackfillContext; + +#[derive(Parser, Debug, Clone)] +pub struct GapWorkerArgs { + /// The size of the signature channel. + #[arg(long, env, default_value = "1000")] + pub gap_channel_size: usize, + + /// The number of gap workers. + #[arg(long, env, default_value = "25")] + pub gap_worker_count: usize, +} + +impl GapWorkerArgs { + pub fn start( + &self, + context: BubblegumBackfillContext, + forward: Sender, + ) -> Result<(JoinHandle<()>, Sender)> { + let (gap_sender, mut gap_receiver) = channel::(self.gap_channel_size); + let gap_worker_count = self.gap_worker_count; + + let handler = tokio::spawn(async move { + let mut handlers = FuturesUnordered::new(); + let sender = forward.clone(); + + while let Some(gap) = gap_receiver.recv().await { + if handlers.len() >= gap_worker_count { + handlers.next().await; + } + + let client = context.solana_rpc.clone(); + let sender = sender.clone(); + + let handle = spawn_crawl_worker(client, sender, gap); + + handlers.push(handle); + } + + futures::future::join_all(handlers).await; + }); + + Ok((handler, gap_sender)) + } +} + +fn spawn_crawl_worker(client: Rpc, sender: Sender, gap: TreeGapFill) -> JoinHandle<()> { + tokio::spawn(async move { + if let Err(e) = gap.crawl(client, sender).await { + error!("tree transaction: {:?}", e); + } + }) +} diff --git a/bubblegum-backfill/src/worker/mod.rs b/bubblegum-backfill/src/worker/mod.rs new file mode 100644 index 0000000..0bc7f9e --- /dev/null +++ b/bubblegum-backfill/src/worker/mod.rs @@ -0,0 +1,9 @@ +mod gap; +mod program_transformer; +mod transaction; +mod tree; + +pub use gap::GapWorkerArgs; +pub use program_transformer::ProgramTransformerWorkerArgs; +pub use transaction::SignatureWorkerArgs; +pub use tree::TreeWorkerArgs; diff --git a/bubblegum-backfill/src/worker/program_transformer.rs b/bubblegum-backfill/src/worker/program_transformer.rs new file mode 100644 index 0000000..da90182 --- /dev/null +++ b/bubblegum-backfill/src/worker/program_transformer.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use clap::Parser; +use das_core::{create_download_metadata_notifier, DownloadMetadataInfo}; +use log::error; +use program_transformers::{ProgramTransformer, TransactionInfo}; +use tokio::sync::mpsc::{channel, Sender, UnboundedSender}; +use tokio::task::JoinHandle; + +use crate::BubblegumBackfillContext; + +#[derive(Parser, Debug, Clone)] +pub struct ProgramTransformerWorkerArgs { + #[arg(long, env, default_value = "100000")] + pub program_transformer_channel_size: usize, +} + +impl ProgramTransformerWorkerArgs { + pub fn start( + &self, + context: BubblegumBackfillContext, + forwarder: UnboundedSender, + ) -> Result<(JoinHandle<()>, Sender)> { + let (sender, mut receiver) = + channel::(self.program_transformer_channel_size); + + let handle = tokio::spawn(async move { + let mut transactions = Vec::new(); + let pool = context.database_pool.clone(); + + let download_metadata_notifier = create_download_metadata_notifier(forwarder).await; + + let program_transformer = + ProgramTransformer::new(pool, download_metadata_notifier, true); + + while let Some(gap) = receiver.recv().await { + transactions.push(gap); + } + + transactions.sort_by(|a, b| b.signature.cmp(&a.signature)); + + for transaction in transactions { + if let Err(e) = program_transformer.handle_transaction(&transaction).await { + error!("handle transaction: {:?}", e) + }; + } + }); + + Ok((handle, sender)) + } +} diff --git a/bubblegum-backfill/src/worker/transaction.rs b/bubblegum-backfill/src/worker/transaction.rs new file mode 100644 index 0000000..0786b56 --- /dev/null +++ b/bubblegum-backfill/src/worker/transaction.rs @@ -0,0 +1,209 @@ +use crate::error::ErrorKind; +use anyhow::Result; +use clap::Parser; +use das_core::Rpc; +use futures::{stream::FuturesUnordered, StreamExt}; +use log::error; +use program_transformers::TransactionInfo; +use solana_program::pubkey::Pubkey; +use solana_sdk::instruction::CompiledInstruction; +use solana_sdk::signature::Signature; +use solana_sdk::transaction::VersionedTransaction; +use solana_transaction_status::{ + option_serializer::OptionSerializer, EncodedConfirmedTransactionWithStatusMeta, + InnerInstruction, InnerInstructions, UiInstruction, +}; +use tokio::{ + sync::mpsc::{channel, Sender}, + task::JoinHandle, +}; + +pub struct PubkeyString(pub String); + +impl TryFrom for Pubkey { + type Error = ErrorKind; + + fn try_from(value: PubkeyString) -> Result { + let decoded_bytes = bs58::decode(value.0) + .into_vec() + .map_err(|e| ErrorKind::Generic(e.to_string()))?; + + Pubkey::try_from(decoded_bytes) + .map_err(|_| ErrorKind::Generic("unable to convert pubkey".to_string())) + } +} + +pub struct FetchedEncodedTransactionWithStatusMeta(pub EncodedConfirmedTransactionWithStatusMeta); + +impl TryFrom for TransactionInfo { + type Error = ErrorKind; + + fn try_from( + fetched_transaction: FetchedEncodedTransactionWithStatusMeta, + ) -> Result { + let mut account_keys = Vec::new(); + let encoded_transaction_with_status_meta = fetched_transaction.0; + + let ui_transaction: VersionedTransaction = encoded_transaction_with_status_meta + .transaction + .transaction + .decode() + .ok_or(ErrorKind::Generic( + "unable to decode transaction".to_string(), + ))?; + + let signature = ui_transaction.signatures[0]; + + let msg = ui_transaction.message; + + let meta = encoded_transaction_with_status_meta + .transaction + .meta + .ok_or(ErrorKind::Generic( + "unable to get meta from transaction".to_string(), + ))?; + + for address in msg.static_account_keys().iter().copied() { + account_keys.push(address); + } + let ui_loaded_addresses = meta.loaded_addresses; + + let message_address_table_lookup = msg.address_table_lookups(); + + if message_address_table_lookup.is_some() { + if let OptionSerializer::Some(ui_lookup_table) = ui_loaded_addresses { + for address in ui_lookup_table.writable { + account_keys.push(PubkeyString(address).try_into()?); + } + + for address in ui_lookup_table.readonly { + account_keys.push(PubkeyString(address).try_into()?); + } + } + } + + let mut meta_inner_instructions = Vec::new(); + + let compiled_instruction = msg.instructions().to_vec(); + + let mut instructions = Vec::new(); + + for inner in compiled_instruction { + instructions.push(InnerInstruction { + stack_height: Some(0), + instruction: CompiledInstruction { + program_id_index: inner.program_id_index, + accounts: inner.accounts, + data: inner.data, + }, + }); + } + + meta_inner_instructions.push(InnerInstructions { + index: 0, + instructions, + }); + + if let OptionSerializer::Some(inner_instructions) = meta.inner_instructions { + for ix in inner_instructions { + let mut instructions = Vec::new(); + + for inner in ix.instructions { + if let UiInstruction::Compiled(compiled) = inner { + instructions.push(InnerInstruction { + stack_height: compiled.stack_height, + instruction: CompiledInstruction { + program_id_index: compiled.program_id_index, + accounts: compiled.accounts, + data: bs58::decode(compiled.data) + .into_vec() + .map_err(|e| ErrorKind::Generic(e.to_string()))?, + }, + }); + } + } + + meta_inner_instructions.push(InnerInstructions { + index: ix.index, + instructions, + }); + } + } + + Ok(Self { + slot: encoded_transaction_with_status_meta.slot, + account_keys, + signature, + message_instructions: msg.instructions().to_vec(), + meta_inner_instructions, + }) + } +} + +#[derive(Parser, Clone, Debug)] +pub struct SignatureWorkerArgs { + /// The size of the signature channel. + #[arg(long, env, default_value = "100000")] + pub signature_channel_size: usize, + /// The number of transaction workers. + #[arg(long, env, default_value = "50")] + pub signature_worker_count: usize, +} + +impl SignatureWorkerArgs { + pub fn start( + &self, + context: crate::BubblegumBackfillContext, + forwarder: Sender, + ) -> Result<(JoinHandle<()>, Sender)> { + let (sig_sender, mut sig_receiver) = channel::(self.signature_channel_size); + let worker_count = self.signature_worker_count; + + let handle = tokio::spawn(async move { + let mut handlers = FuturesUnordered::new(); + + while let Some(signature) = sig_receiver.recv().await { + if handlers.len() >= worker_count { + handlers.next().await; + } + + let solana_rpc = context.solana_rpc.clone(); + let transaction_sender = forwarder.clone(); + + let handle = spawn_transaction_worker(solana_rpc, transaction_sender, signature); + + handlers.push(handle); + } + + futures::future::join_all(handlers).await; + }); + + Ok((handle, sig_sender)) + } +} + +async fn queue_transaction<'a>( + client: Rpc, + sender: Sender, + signature: Signature, +) -> Result<(), ErrorKind> { + let transaction = client.get_transaction(&signature).await?; + sender + .send(FetchedEncodedTransactionWithStatusMeta(transaction).try_into()?) + .await + .map_err(|e| ErrorKind::Generic(e.to_string()))?; + + Ok(()) +} + +fn spawn_transaction_worker( + client: Rpc, + sender: Sender, + signature: Signature, +) -> JoinHandle<()> { + tokio::spawn(async move { + if let Err(e) = queue_transaction(client, sender, signature).await { + error!("queue transaction: {:?}", e); + } + }) +} diff --git a/bubblegum-backfill/src/worker/tree.rs b/bubblegum-backfill/src/worker/tree.rs new file mode 100644 index 0000000..f647e63 --- /dev/null +++ b/bubblegum-backfill/src/worker/tree.rs @@ -0,0 +1,115 @@ +use crate::{ + gap::{TreeGapFill, TreeGapModel}, + tree::TreeResponse, + BubblegumBackfillContext, +}; +use anyhow::Result; +use clap::Parser; +use das_core::MetadataJsonDownloadWorkerArgs; +use digital_asset_types::dao::cl_audits_v2; +use log::error; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, SqlxPostgresConnector}; +use solana_sdk::signature::Signature; +use tokio::task::JoinHandle; + +use super::{GapWorkerArgs, ProgramTransformerWorkerArgs, SignatureWorkerArgs}; + +#[derive(Debug, Clone, Parser)] +pub struct TreeWorkerArgs { + #[clap(flatten)] + pub metadata_json_download_worker: MetadataJsonDownloadWorkerArgs, + + #[clap(flatten)] + pub signature_worker: SignatureWorkerArgs, + + #[clap(flatten)] + pub gap_worker: GapWorkerArgs, + + #[clap(flatten)] + pub program_transformer_worker: ProgramTransformerWorkerArgs, +} +impl TreeWorkerArgs { + pub fn start( + &self, + context: BubblegumBackfillContext, + tree: TreeResponse, + ) -> JoinHandle> { + let db_pool = context.database_pool.clone(); + let metadata_json_download_db_pool = context.database_pool.clone(); + + let program_transformer_context = context.clone(); + let signature_context = context.clone(); + + let metadata_json_download_worker_args = self.metadata_json_download_worker.clone(); + let program_transformer_worker_args = self.program_transformer_worker.clone(); + let signature_worker_args = self.signature_worker.clone(); + let gap_worker_args = self.gap_worker.clone(); + + tokio::spawn(async move { + let (metadata_json_download_worker, metadata_json_download_sender) = + metadata_json_download_worker_args.start(metadata_json_download_db_pool)?; + + let (program_transformer_worker, transaction_info_sender) = + program_transformer_worker_args + .start(program_transformer_context, metadata_json_download_sender)?; + + let (signature_worker, signature_sender) = + signature_worker_args.start(signature_context, transaction_info_sender)?; + + let (gap_worker, tree_gap_sender) = gap_worker_args.start(context, signature_sender)?; + + { + let conn = SqlxPostgresConnector::from_sqlx_postgres_pool(db_pool); + + let mut gaps = TreeGapModel::find(&conn, tree.pubkey) + .await? + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + let upper_known_seq = cl_audits_v2::Entity::find() + .filter(cl_audits_v2::Column::Tree.eq(tree.pubkey.as_ref().to_vec())) + .order_by_desc(cl_audits_v2::Column::Seq) + .one(&conn) + .await?; + + let lower_known_seq = cl_audits_v2::Entity::find() + .filter(cl_audits_v2::Column::Tree.eq(tree.pubkey.as_ref().to_vec())) + .order_by_asc(cl_audits_v2::Column::Seq) + .one(&conn) + .await?; + + if let Some(upper_seq) = upper_known_seq { + let signature = Signature::try_from(upper_seq.tx.as_ref())?; + + gaps.push(TreeGapFill::new(tree.pubkey, None, Some(signature))); + } else if tree.seq > 0 { + gaps.push(TreeGapFill::new(tree.pubkey, None, None)); + } + + if let Some(lower_seq) = lower_known_seq.filter(|seq| seq.seq > 1) { + let signature = Signature::try_from(lower_seq.tx.as_ref())?; + + gaps.push(TreeGapFill::new(tree.pubkey, Some(signature), None)); + } + + for gap in gaps { + if let Err(e) = tree_gap_sender.send(gap).await { + error!("send gap: {:?}", e); + } + } + drop(tree_gap_sender); + } + + futures::future::try_join4( + gap_worker, + signature_worker, + program_transformer_worker, + metadata_json_download_worker, + ) + .await?; + + Ok(()) + }) + } +} diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..9ee91c6 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "das-core" +version.workspace = true +edition.workspace = true +repository.workspace = true +publish.workspace = true + +[dependencies] +anyhow = { workspace = true } +backon = { workspace = true } +borsh = { workspace = true } +bs58 = { workspace = true } +cadence = { workspace = true } +cadence-macros = { workspace = true } +clap = { workspace = true, features = ["derive", "cargo", "env"] } +derive_more = { workspace = true } +digital_asset_types = { workspace = true } +figment = { workspace = true } +futures = { workspace = true } +indicatif = { workspace = true } +log = { workspace = true } +plerkle_messenger = { workspace = true } +reqwest = { workspace = true } +sea-orm = { workspace = true, features = [ + "sqlx-postgres", + "with-chrono", + "runtime-tokio-rustls", +] } +serde_json = { workspace = true } +spl-account-compression = { workspace = true } +solana-account-decoder = { workspace = true } +solana-client = { workspace = true } +solana-sdk = { workspace = true } +solana-transaction-status = { workspace = true } +sqlx = { workspace = true, fatures = ["runtime-tokio-rustls", "postgres"] } +thiserror = { workspace = true } +tokio = { workspace = true } +url = { workspace = true } + +[lints] +workspace = true diff --git a/core/src/db.rs b/core/src/db.rs new file mode 100644 index 0000000..5c211a8 --- /dev/null +++ b/core/src/db.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use clap::Parser; +use sqlx::{ + postgres::{PgConnectOptions, PgPoolOptions}, + PgPool, +}; + +#[derive(Debug, Parser, Clone)] +pub struct PoolArgs { + /// The database URL. + #[arg(long, env)] + pub database_url: String, + /// The maximum number of connections to the database. + #[arg(long, env, default_value = "125")] + pub database_max_connections: u32, + /// The minimum number of connections to the database. + #[arg(long, env, default_value = "5")] + pub database_min_connections: u32, +} + +///// Establishes a connection to the database using the provided configuration. +///// +///// # Arguments +///// +///// * `config` - A `PoolArgs` struct containing the database URL and the minimum and maximum number of connections. +///// +///// # Returns +///// +///// * `Result` - On success, returns a `DatabaseConnection`. On failure, returns a `DbErr`. +pub async fn connect_db(config: &PoolArgs) -> Result { + let options: PgConnectOptions = config.database_url.parse()?; + + PgPoolOptions::new() + .min_connections(config.database_min_connections) + .max_connections(config.database_max_connections) + .connect_with(options) + .await +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..341c548 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,11 @@ +mod db; +mod metadata_json; +mod metrics; +mod plerkle_messenger_queue; +mod solana_rpc; + +pub use db::*; +pub use metadata_json::*; +pub use metrics::*; +pub use plerkle_messenger_queue::*; +pub use solana_rpc::*; diff --git a/core/src/metadata_json.rs b/core/src/metadata_json.rs new file mode 100644 index 0000000..b19c328 --- /dev/null +++ b/core/src/metadata_json.rs @@ -0,0 +1,223 @@ +use { + backon::{ExponentialBuilder, Retryable}, + clap::Parser, + digital_asset_types::dao::asset_data, + futures::{future::BoxFuture, stream::FuturesUnordered, StreamExt}, + indicatif::HumanDuration, + log::{debug, error}, + reqwest::{Client, Url as ReqwestUrl}, + sea_orm::{entity::*, SqlxPostgresConnector}, + tokio::{ + sync::mpsc::{error::SendError, unbounded_channel, UnboundedSender}, + task::JoinHandle, + time::Instant, + }, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DownloadMetadataInfo { + asset_data_id: Vec, + uri: String, +} + +impl DownloadMetadataInfo { + pub fn new(asset_data_id: Vec, uri: String) -> Self { + Self { + asset_data_id, + uri: uri.trim().replace('\0', ""), + } + } + + pub fn into_inner(self) -> (Vec, String) { + (self.asset_data_id, self.uri) + } +} + +pub type DownloadMetadataNotifier = Box< + dyn Fn( + DownloadMetadataInfo, + ) -> BoxFuture<'static, Result<(), Box>> + + Sync + + Send, +>; + +pub async fn create_download_metadata_notifier( + download_metadata_json_sender: UnboundedSender, +) -> DownloadMetadataNotifier { + Box::new(move |info: DownloadMetadataInfo| -> BoxFuture<'static, Result<(), Box>> + { + let task = download_metadata_json_sender.send(info).map_err(Into::into); + + Box::pin(async move { task }) + }) +} + +#[derive(Parser, Clone, Debug)] +pub struct MetadataJsonDownloadWorkerArgs { + /// The number of worker threads + #[arg(long, env, default_value = "25")] + pub metadata_json_download_worker_count: usize, + /// The request timeout in milliseconds + #[arg(long, env, default_value = "1000")] + pub metadata_json_download_worker_request_timeout: u64, +} + +impl MetadataJsonDownloadWorkerArgs { + pub fn start( + &self, + pool: sqlx::PgPool, + ) -> Result< + (JoinHandle<()>, UnboundedSender), + MetadataJsonDownloadWorkerError, + > { + let (sender, mut rx) = unbounded_channel::(); + let worker_count = self.metadata_json_download_worker_count; + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_millis( + self.metadata_json_download_worker_request_timeout, + )) + .build()?; + + let handle = tokio::spawn(async move { + let mut handlers = FuturesUnordered::new(); + + while let Some(download_metadata_info) = rx.recv().await { + if handlers.len() >= worker_count { + handlers.next().await; + } + + let pool = pool.clone(); + let client = client.clone(); + + handlers.push(spawn_task(client, pool, download_metadata_info)); + } + + while handlers.next().await.is_some() {} + }); + + Ok((handle, sender)) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum MetadataJsonDownloadWorkerError { + #[error("send error: {0}")] + Send(#[from] SendError), + #[error("join error: {0}")] + Join(#[from] tokio::task::JoinError), + #[error("reqwest: {0}")] + Reqwest(#[from] reqwest::Error), +} + +fn spawn_task( + client: Client, + pool: sqlx::PgPool, + download_metadata_info: DownloadMetadataInfo, +) -> JoinHandle<()> { + tokio::spawn(async move { + let timing = Instant::now(); + let asset_data_id = + bs58::encode(download_metadata_info.asset_data_id.clone()).into_string(); + + if let Err(e) = perform_metadata_json_task(client, pool, download_metadata_info).await { + error!("Asset {} failed: {}", asset_data_id, e); + } + + debug!( + "Asset {} finished in {}", + asset_data_id, + HumanDuration(timing.elapsed()) + ); + }) +} + +#[derive(thiserror::Error, Debug)] +pub enum FetchMetadataJsonError { + #[error("reqwest: {0}")] + GenericReqwest(#[from] reqwest::Error), + #[error("json parse for url({url}) with {source}")] + Parse { + source: reqwest::Error, + url: ReqwestUrl, + }, + #[error("response {status} for url ({url}) with {source}")] + Response { + source: reqwest::Error, + url: ReqwestUrl, + status: StatusCode, + }, + #[error("url parse: {0}")] + Url(#[from] url::ParseError), +} + +#[derive(Debug, derive_more::Display)] +pub enum StatusCode { + Unknown, + Code(reqwest::StatusCode), +} + +async fn fetch_metadata_json( + client: Client, + metadata_json_url: &str, +) -> Result { + (|| async { + let url = ReqwestUrl::parse(metadata_json_url)?; + + let response = client.get(url.clone()).send().await?; + + match response.error_for_status() { + Ok(res) => res + .json::() + .await + .map_err(|source| FetchMetadataJsonError::Parse { source, url }), + Err(source) => { + let status = source + .status() + .map(StatusCode::Code) + .unwrap_or(StatusCode::Unknown); + + Err(FetchMetadataJsonError::Response { + source, + url, + status, + }) + } + } + }) + .retry(&ExponentialBuilder::default()) + .await +} + +#[derive(thiserror::Error, Debug)] +pub enum MetadataJsonTaskError { + #[error("sea orm: {0}")] + SeaOrm(#[from] sea_orm::DbErr), + #[error("metadata json: {0}")] + Fetch(#[from] FetchMetadataJsonError), + #[error("asset not found in the db")] + AssetNotFound, +} + +pub async fn perform_metadata_json_task( + client: Client, + pool: sqlx::PgPool, + download_metadata_info: DownloadMetadataInfo, +) -> Result { + match fetch_metadata_json(client, &download_metadata_info.uri).await { + Ok(metadata) => { + let active_model = asset_data::ActiveModel { + id: Set(download_metadata_info.asset_data_id), + metadata: Set(metadata), + reindex: Set(Some(false)), + ..Default::default() + }; + + let conn = SqlxPostgresConnector::from_sqlx_postgres_pool(pool); + + let model = active_model.update(&conn).await?; + + Ok(model) + } + Err(e) => Err(MetadataJsonTaskError::Fetch(e)), + } +} diff --git a/core/src/metrics.rs b/core/src/metrics.rs new file mode 100644 index 0000000..13bf4e2 --- /dev/null +++ b/core/src/metrics.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use cadence::{BufferedUdpMetricSink, QueuingMetricSink, StatsdClient}; +use cadence_macros::set_global_default; +use clap::Parser; +use std::net::UdpSocket; + +#[derive(Clone, Parser, Debug)] +pub struct MetricsArgs { + #[arg(long, env, default_value = "127.0.0.1")] + pub metrics_host: String, + #[arg(long, env, default_value = "8125")] + pub metrics_port: u16, + #[arg(long, env, default_value = "das.backfiller")] + pub metrics_prefix: String, +} + +pub fn setup_metrics(config: &MetricsArgs) -> Result<()> { + let host = (config.metrics_host.clone(), config.metrics_port); + + let socket = UdpSocket::bind("0.0.0.0:0")?; + socket.set_nonblocking(true)?; + + let udp_sink = BufferedUdpMetricSink::from(host, socket)?; + let queuing_sink = QueuingMetricSink::from(udp_sink); + + let client = StatsdClient::from_sink(&config.metrics_prefix, queuing_sink); + + set_global_default(client); + + Ok(()) +} diff --git a/core/src/plerkle_messenger_queue.rs b/core/src/plerkle_messenger_queue.rs new file mode 100644 index 0000000..11b1166 --- /dev/null +++ b/core/src/plerkle_messenger_queue.rs @@ -0,0 +1,180 @@ +use anyhow::Result; +use clap::Parser; +use figment::value::{Dict, Value}; +use plerkle_messenger::{ + Messenger, MessengerConfig, MessengerType, ACCOUNT_BACKFILL_STREAM, ACCOUNT_STREAM, + TRANSACTION_BACKFILL_STREAM, TRANSACTION_STREAM, +}; +use std::num::TryFromIntError; +use std::sync::Arc; +use tokio::sync::mpsc; +use tokio::sync::{mpsc::error::TrySendError, Mutex}; + +#[derive(Clone, Debug, Parser)] +pub struct QueueArgs { + #[arg(long, env)] + pub messenger_redis_url: String, + #[arg(long, env, default_value = "100")] + pub messenger_redis_batch_size: String, + #[arg(long, env, default_value = "25")] + pub messenger_queue_connections: u64, +} + +impl From for MessengerConfig { + fn from(args: QueueArgs) -> Self { + let mut connection_config = Dict::new(); + + connection_config.insert( + "redis_connection_str".to_string(), + Value::from(args.messenger_redis_url), + ); + connection_config.insert( + "batch_size".to_string(), + Value::from(args.messenger_redis_batch_size), + ); + connection_config.insert( + "pipeline_size_bytes".to_string(), + Value::from(1u128.to_string()), + ); + + Self { + messenger_type: MessengerType::Redis, + connection_config, + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum QueuePoolError { + #[error("messenger")] + Messenger(#[from] plerkle_messenger::MessengerError), + #[error("tokio try send to channel")] + TrySendMessengerChannel(#[from] TrySendError>), + #[error("revc messenger connection")] + RecvMessengerConnection, + #[error("try from int")] + TryFromInt(#[from] TryFromIntError), + #[error("tokio send to channel")] + SendMessengerChannel(#[from] mpsc::error::SendError>), +} + +#[derive(Debug, Clone)] +pub struct QueuePool { + tx: mpsc::Sender>, + rx: Arc>>>, +} + +impl QueuePool { + pub async fn try_from_config(config: &QueueArgs) -> anyhow::Result { + let size = usize::try_from(config.messenger_queue_connections)?; + let (tx, rx) = mpsc::channel(size); + + for _ in 0..config.messenger_queue_connections { + let messenger_config: MessengerConfig = config.clone().into(); + let mut messenger = plerkle_messenger::select_messenger(messenger_config).await?; + + let streams = [ + (plerkle_messenger::ACCOUNT_STREAM, 100_000_000), + (plerkle_messenger::ACCOUNT_BACKFILL_STREAM, 100_000_000), + (plerkle_messenger::SLOT_STREAM, 100_000), + (plerkle_messenger::TRANSACTION_STREAM, 10_000_000), + (plerkle_messenger::TRANSACTION_BACKFILL_STREAM, 10_000_000), + (plerkle_messenger::BLOCK_STREAM, 100_000), + ]; + + for &(key, size) in &streams { + messenger.add_stream(key).await?; + messenger.set_buffer_size(key, size).await; + } + + tx.try_send(messenger)?; + } + + Ok(Self { + tx, + rx: Arc::new(Mutex::new(rx)), + }) + } + + /// Pushes account backfill data to the appropriate stream. + /// + /// This method sends account backfill data to the `ACCOUNT_BACKFILL_STREAM`. + /// It is used for backfilling account information in the system. + /// + /// # Arguments + /// + /// * `bytes` - A byte slice containing the account backfill data to be pushed. + /// + /// # Returns + /// + /// This method returns a `Result` which is `Ok` if the push is successful, + /// or an `Err` with a `QueuePoolError` if the push fails. + pub async fn push_account_backfill(&self, bytes: &[u8]) -> Result<(), QueuePoolError> { + self.push(ACCOUNT_BACKFILL_STREAM, bytes).await + } + + /// Pushes transaction backfill data to the appropriate stream. + /// + /// This method sends transaction backfill data to the `TRANSACTION_BACKFILL_STREAM`. + /// It is used for backfilling transaction information in the system. + /// + /// # Arguments + /// + /// * `bytes` - A byte slice containing the transaction backfill data to be pushed. + /// + /// # Returns + /// + /// This method returns a `Result` which is `Ok` if the push is successful, + /// or an `Err` with a `QueuePoolError` if the push fails. + pub async fn push_transaction_backfill(&self, bytes: &[u8]) -> Result<(), QueuePoolError> { + self.push(TRANSACTION_BACKFILL_STREAM, bytes).await + } + + /// Pushes account data to the appropriate stream. + /// + /// This method sends account data to the `ACCOUNT_STREAM`. + /// It is used for pushing real-time account updates to the system. + /// + /// # Arguments + /// + /// * `bytes` - A byte slice containing the account data to be pushed. + /// + /// # Returns + /// + /// This method returns a `Result` which is `Ok` if the push is successful, + /// or an `Err` with a `QueuePoolError` if the push fails. + pub async fn push_account(&self, bytes: &[u8]) -> Result<(), QueuePoolError> { + self.push(ACCOUNT_STREAM, bytes).await + } + + /// Pushes transaction data to the appropriate stream. + /// + /// This method sends transaction data to the `TRANSACTION_STREAM`. + /// It is used for pushing real-time transaction updates to the system. + /// + /// # Arguments + /// + /// * `bytes` - A byte slice containing the transaction data to be pushed. + /// + /// # Returns + /// + /// This method returns a `Result` which is `Ok` if the push is successful, + /// or an `Err` with a `QueuePoolError` if the push fails. + pub async fn push_transaction(&self, bytes: &[u8]) -> Result<(), QueuePoolError> { + self.push(TRANSACTION_STREAM, bytes).await + } + + async fn push(&self, stream_key: &'static str, bytes: &[u8]) -> Result<(), QueuePoolError> { + let mut rx = self.rx.lock().await; + let mut messenger = rx + .recv() + .await + .ok_or(QueuePoolError::RecvMessengerConnection)?; + + messenger.send(stream_key, bytes).await?; + + self.tx.send(messenger).await?; + + Ok(()) + } +} diff --git a/core/src/solana_rpc.rs b/core/src/solana_rpc.rs new file mode 100644 index 0000000..b517f14 --- /dev/null +++ b/core/src/solana_rpc.rs @@ -0,0 +1,160 @@ +use anyhow::Result; +use backon::ExponentialBuilder; +use backon::Retryable; +use clap::Parser; +use solana_account_decoder::UiAccountEncoding; +use solana_client::rpc_response::RpcConfirmedTransactionStatusWithSignature; +use solana_client::{ + client_error::ClientError, + nonblocking::rpc_client::RpcClient, + rpc_client::GetConfirmedSignaturesForAddress2Config, + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTransactionConfig}, + rpc_filter::RpcFilterType, +}; +use solana_sdk::{ + account::Account, + commitment_config::{CommitmentConfig, CommitmentLevel}, + pubkey::Pubkey, + signature::Signature, +}; +use solana_transaction_status::EncodedConfirmedTransactionWithStatusMeta; +use solana_transaction_status::UiTransactionEncoding; +use std::sync::Arc; + +#[derive(Clone, Parser, Debug)] +pub struct SolanaRpcArgs { + #[arg(long, env)] + pub solana_rpc_url: String, +} + +#[derive(Clone)] +pub struct Rpc(Arc); + +impl Rpc { + pub fn from_config(config: &SolanaRpcArgs) -> Self { + Rpc(Arc::new(RpcClient::new(config.solana_rpc_url.clone()))) + } + + pub async fn get_transaction( + &self, + signature: &Signature, + ) -> Result { + (|| async { + self.0 + .get_transaction_with_config( + signature, + RpcTransactionConfig { + encoding: Some(UiTransactionEncoding::Base58), + max_supported_transaction_version: Some(0), + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Finalized, + }), + }, + ) + .await + }) + .retry(&ExponentialBuilder::default()) + .await + } + + pub async fn get_signatures_for_address( + &self, + pubkey: &Pubkey, + before: Option, + until: Option, + ) -> Result, ClientError> { + (|| async { + self.0 + .get_signatures_for_address_with_config( + pubkey, + GetConfirmedSignaturesForAddress2Config { + before, + until, + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Finalized, + }), + ..GetConfirmedSignaturesForAddress2Config::default() + }, + ) + .await + }) + .retry(&ExponentialBuilder::default()) + .await + } + + pub async fn get_account( + &self, + pubkey: &Pubkey, + ) -> Result< + solana_client::rpc_response::Response>, + ClientError, + > { + (|| async { + self.0 + .get_account_with_config( + pubkey, + RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Finalized, + }), + ..RpcAccountInfoConfig::default() + }, + ) + .await + }) + .retry(&ExponentialBuilder::default()) + .await + } + + pub async fn get_program_accounts( + &self, + program: &Pubkey, + filters: Option>, + ) -> Result, ClientError> { + (|| async { + let filters = filters.clone(); + + self.0 + .get_program_accounts_with_config( + program, + RpcProgramAccountsConfig { + filters, + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Finalized, + }), + ..RpcAccountInfoConfig::default() + }, + ..RpcProgramAccountsConfig::default() + }, + ) + .await + }) + .retry(&ExponentialBuilder::default()) + .await + } + + pub async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + ) -> Result>, ClientError> { + Ok((|| async { + self.0 + .get_multiple_accounts_with_config( + pubkeys, + RpcAccountInfoConfig { + commitment: Some(CommitmentConfig { + commitment: CommitmentLevel::Finalized, + }), + ..RpcAccountInfoConfig::default() + }, + ) + .await + }) + .retry(&ExponentialBuilder::default()) + .await? + .value) + } +} diff --git a/digital_asset_types/src/dao/extensions/asset.rs b/digital_asset_types/src/dao/extensions/asset.rs index 8b5b8b8..18525e3 100644 --- a/digital_asset_types/src/dao/extensions/asset.rs +++ b/digital_asset_types/src/dao/extensions/asset.rs @@ -103,6 +103,13 @@ impl Default for asset::Model { owner_delegate_seq: None, leaf_seq: None, base_info_seq: None, + mpl_core_plugins: None, + mpl_core_unknown_plugins: None, + mpl_core_collection_current_size: None, + mpl_core_collection_num_minted: None, + mpl_core_plugins_json_version: None, + mpl_core_external_plugins: None, + mpl_core_unknown_external_plugins: None, } } } diff --git a/digital_asset_types/src/dao/extensions/asset_grouping.rs b/digital_asset_types/src/dao/extensions/asset_grouping.rs index 1d1bce8..49b091e 100644 --- a/digital_asset_types/src/dao/extensions/asset_grouping.rs +++ b/digital_asset_types/src/dao/extensions/asset_grouping.rs @@ -1,10 +1,11 @@ use sea_orm::{EntityTrait, EnumIter, Related, RelationDef, RelationTrait}; -use crate::dao::{asset, asset_grouping}; +use crate::dao::{asset, asset_authority, asset_grouping}; #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Asset, + AssetAuthority, } impl RelationTrait for Relation { @@ -14,6 +15,10 @@ impl RelationTrait for Relation { .from(asset_grouping::Column::AssetId) .to(asset::Column::Id) .into(), + Self::AssetAuthority => asset_grouping::Entity::belongs_to(asset_authority::Entity) + .from(asset_grouping::Column::AssetId) + .to(asset_authority::Column::Id) + .into(), } } } @@ -23,3 +28,9 @@ impl Related for asset_grouping::Entity { Relation::Asset.def() } } + +impl Related for asset_grouping::Entity { + fn to() -> RelationDef { + Relation::AssetAuthority.def() + } +} diff --git a/digital_asset_types/src/dao/generated/asset.rs b/digital_asset_types/src/dao/generated/asset.rs index fb2ef95..e4f4ab0 100644 --- a/digital_asset_types/src/dao/generated/asset.rs +++ b/digital_asset_types/src/dao/generated/asset.rs @@ -50,6 +50,13 @@ pub struct Model { pub owner_delegate_seq: Option, pub leaf_seq: Option, pub base_info_seq: Option, + pub mpl_core_plugins: Option, + pub mpl_core_unknown_plugins: Option, + pub mpl_core_collection_num_minted: Option, + pub mpl_core_collection_current_size: Option, + pub mpl_core_plugins_json_version: Option, + pub mpl_core_external_plugins: Option, + pub mpl_core_unknown_external_plugins: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] @@ -86,6 +93,13 @@ pub enum Column { OwnerDelegateSeq, LeafSeq, BaseInfoSeq, + MplCorePlugins, + MplCoreUnknownPlugins, + MplCoreCollectionNumMinted, + MplCoreCollectionCurrentSize, + MplCorePluginsJsonVersion, + MplCoreExternalPlugins, + MplCoreUnknownExternalPlugins, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] @@ -139,6 +153,13 @@ impl ColumnTrait for Column { Self::OwnerDelegateSeq => ColumnType::BigInteger.def().null(), Self::LeafSeq => ColumnType::BigInteger.def().null(), Self::BaseInfoSeq => ColumnType::BigInteger.def().null(), + Self::MplCorePlugins => ColumnType::JsonBinary.def().null(), + Self::MplCoreUnknownPlugins => ColumnType::JsonBinary.def().null(), + Self::MplCoreCollectionNumMinted => ColumnType::Integer.def().null(), + Self::MplCoreCollectionCurrentSize => ColumnType::Integer.def().null(), + Self::MplCorePluginsJsonVersion => ColumnType::Integer.def().null(), + Self::MplCoreExternalPlugins => ColumnType::JsonBinary.def().null(), + Self::MplCoreUnknownExternalPlugins => ColumnType::JsonBinary.def().null(), } } } diff --git a/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs b/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs index 7576ec2..e4d0e01 100644 --- a/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs +++ b/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs @@ -3,29 +3,41 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "owner_type")] -pub enum OwnerType { - #[sea_orm(string_value = "single")] - Single, - #[sea_orm(string_value = "token")] - Token, - #[sea_orm(string_value = "unknown")] - Unknown, -} #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm( rs_type = "String", db_type = "Enum", - enum_name = "royalty_target_type" + enum_name = "specification_versions" )] -pub enum RoyaltyTargetType { - #[sea_orm(string_value = "creators")] - Creators, - #[sea_orm(string_value = "fanout")] - Fanout, - #[sea_orm(string_value = "single")] - Single, +pub enum SpecificationVersions { + #[sea_orm(string_value = "unknown")] + Unknown, + #[sea_orm(string_value = "v0")] + V0, + #[sea_orm(string_value = "v1")] + V1, + #[sea_orm(string_value = "v2")] + V2, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "task_status")] +pub enum TaskStatus { + #[sea_orm(string_value = "failed")] + Failed, + #[sea_orm(string_value = "pending")] + Pending, + #[sea_orm(string_value = "running")] + Running, + #[sea_orm(string_value = "success")] + Success, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "chain_mutability")] +pub enum ChainMutability { + #[sea_orm(string_value = "immutable")] + Immutable, + #[sea_orm(string_value = "mutable")] + Mutable, #[sea_orm(string_value = "unknown")] Unknown, } @@ -58,6 +70,32 @@ pub enum V1AccountAttachments { Unknown, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "owner_type")] +pub enum OwnerType { + #[sea_orm(string_value = "single")] + Single, + #[sea_orm(string_value = "token")] + Token, + #[sea_orm(string_value = "unknown")] + Unknown, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm( + rs_type = "String", + db_type = "Enum", + enum_name = "royalty_target_type" +)] +pub enum RoyaltyTargetType { + #[sea_orm(string_value = "creators")] + Creators, + #[sea_orm(string_value = "fanout")] + Fanout, + #[sea_orm(string_value = "single")] + Single, + #[sea_orm(string_value = "unknown")] + Unknown, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -70,6 +108,10 @@ pub enum SpecificationAssetClass { FungibleToken, #[sea_orm(string_value = "IDENTITY_NFT")] IdentityNft, + #[sea_orm(string_value = "MPL_CORE_ASSET")] + MplCoreAsset, + #[sea_orm(string_value = "MPL_CORE_COLLECTION")] + MplCoreCollection, #[sea_orm(string_value = "NFT")] Nft, #[sea_orm(string_value = "NON_TRANSFERABLE_NFT")] @@ -86,44 +128,6 @@ pub enum SpecificationAssetClass { Unknown, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "chain_mutability")] -pub enum ChainMutability { - #[sea_orm(string_value = "immutable")] - Immutable, - #[sea_orm(string_value = "mutable")] - Mutable, - #[sea_orm(string_value = "unknown")] - Unknown, -} -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "task_status")] -pub enum TaskStatus { - #[sea_orm(string_value = "failed")] - Failed, - #[sea_orm(string_value = "pending")] - Pending, - #[sea_orm(string_value = "running")] - Running, - #[sea_orm(string_value = "success")] - Success, -} -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm( - rs_type = "String", - db_type = "Enum", - enum_name = "specification_versions" -)] -pub enum SpecificationVersions { - #[sea_orm(string_value = "unknown")] - Unknown, - #[sea_orm(string_value = "v0")] - V0, - #[sea_orm(string_value = "v1")] - V1, - #[sea_orm(string_value = "v2")] - V2, -} -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "instruction")] pub enum Instruction { #[sea_orm(string_value = "burn")] diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index 845de82..58e4cbb 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -391,19 +391,12 @@ pub async fn get_by_id( if !include_no_supply { asset_data = asset_data.filter(Condition::all().add(asset::Column::Supply.gt(0))); } - println!("ASSET DATA 1: {:?}", asset_data.clone()); - println!( - "ASSET DATA await: {:?}", - asset_data.clone().one(conn.clone()).await - ); let asset_data: (asset::Model, asset_data::Model) = asset_data.one(conn).await.and_then(|o| match o { Some((a, Some(d))) => Ok((a, d)), _ => Err(DbErr::RecordNotFound("Asset Not Found".to_string())), })?; - println!("ASSET DATA 2: {:?}", asset_data.clone()); - let (asset, data) = asset_data; let authorities: Vec = asset_authority::Entity::find() .filter(asset_authority::Column::AssetId.eq(asset.id.clone())) diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs index 655eefd..c9a9dd4 100644 --- a/digital_asset_types/src/dapi/common/asset.rs +++ b/digital_asset_types/src/dapi/common/asset.rs @@ -9,7 +9,7 @@ use crate::rpc::response::TransactionSignatureList; use crate::rpc::response::{AssetError, AssetList}; use crate::rpc::{ Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface, - MetadataMap, Ownership, Royalty, Scope, Supply, Uses, + MetadataMap, MplCoreInfo, Ownership, Royalty, Scope, Supply, Uses, }; use jsonpath_lib::JsonPathError; use log::warn; @@ -368,6 +368,15 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result Some(MplCoreInfo { + num_minted: asset.mpl_core_collection_num_minted, + current_size: asset.mpl_core_collection_current_size, + plugins_json_version: asset.mpl_core_plugins_json_version, + }), + _ => None, + }; + Ok(RpcAsset { interface: interface.clone(), id: bs58::encode(asset.id).into_string(), @@ -435,6 +444,11 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result Result { let asset = scopes::asset::get_by_id(db, id, false).await?; - print!("ASSET: {:?}", asset.clone()); asset_to_rpc(asset, options) } diff --git a/digital_asset_types/src/rpc/asset.rs b/digital_asset_types/src/rpc/asset.rs index 415b636..751ef38 100644 --- a/digital_asset_types/src/rpc/asset.rs +++ b/digital_asset_types/src/rpc/asset.rs @@ -6,6 +6,7 @@ use std::collections::BTreeMap; use crate::dao::sea_orm_active_enums::ChainMutability; use schemars::JsonSchema; +use serde_json::Value; use { serde::{Deserialize, Serialize}, std::collections::HashMap, @@ -42,6 +43,10 @@ pub enum Interface { Executable, #[serde(rename = "ProgrammableNFT")] ProgrammableNFT, + #[serde(rename = "MplCoreAsset")] + MplCoreAsset, + #[serde(rename = "MplCoreCollection")] + MplCoreCollection, } impl From<(&SpecificationVersions, &SpecificationAssetClass)> for Interface { @@ -53,6 +58,8 @@ impl From<(&SpecificationVersions, &SpecificationAssetClass)> for Interface { (SpecificationVersions::V1, SpecificationAssetClass::ProgrammableNft) => { Interface::ProgrammableNFT } + (_, SpecificationAssetClass::MplCoreAsset) => Interface::MplCoreAsset, + (_, SpecificationAssetClass::MplCoreCollection) => Interface::MplCoreCollection, _ => Interface::Custom, } } @@ -72,6 +79,14 @@ impl From for (SpecificationVersions, SpecificationAssetClass) { SpecificationVersions::V1, SpecificationAssetClass::FungibleAsset, ), + Interface::MplCoreAsset => ( + SpecificationVersions::V1, + SpecificationAssetClass::MplCoreAsset, + ), + Interface::MplCoreCollection => ( + SpecificationVersions::V1, + SpecificationAssetClass::MplCoreCollection, + ), _ => (SpecificationVersions::V1, SpecificationAssetClass::Unknown), } } @@ -342,6 +357,15 @@ pub struct Supply { pub edition_nonce: Option, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MplCoreInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub num_minted: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub current_size: Option, + pub plugins_json_version: Option, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Asset { pub interface: Interface, @@ -364,4 +388,14 @@ pub struct Asset { pub supply: Option, pub mutable: bool, pub burnt: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub plugins: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unknown_plugins: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mpl_core_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub external_plugins: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unknown_external_plugins: Option, } diff --git a/digital_asset_types/tests/common.rs b/digital_asset_types/tests/common.rs index 39d3211..f5b4664 100644 --- a/digital_asset_types/tests/common.rs +++ b/digital_asset_types/tests/common.rs @@ -162,6 +162,13 @@ pub fn create_asset( owner_delegate_seq: Some(0), leaf_seq: Some(0), base_info_seq: Some(0), + mpl_core_plugins: None, + mpl_core_unknown_plugins: None, + mpl_core_collection_current_size: None, + mpl_core_collection_num_minted: None, + mpl_core_plugins_json_version: None, + mpl_core_external_plugins: None, + mpl_core_unknown_external_plugins: None, }, ) } diff --git a/migration/Cargo.toml b/migration/Cargo.toml index e9de8bb..3f93e25 100644 --- a/migration/Cargo.toml +++ b/migration/Cargo.toml @@ -12,7 +12,7 @@ path = "src/lib.rs" async-std = { version = "1", features = ["attributes", "tokio1"] } [dependencies.sea-orm-migration] -version = "0.12.0" +version = "0.12.15" features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. diff --git a/program_transformers/Cargo.toml b/program_transformers/Cargo.toml index 5c58e7f..54ea735 100644 --- a/program_transformers/Cargo.toml +++ b/program_transformers/Cargo.toml @@ -10,8 +10,13 @@ blockbuster = { workspace = true } bs58 = { workspace = true } cadence = { workspace = true } cadence-macros = { workspace = true } -digital_asset_types = { workspace = true, features = ["json_types", "sql_types"] } +das-core = { workspace = true } +digital_asset_types = { workspace = true, features = [ + "json_types", + "sql_types", +] } futures = { workspace = true } +heck = { workspace = true } mpl-bubblegum = { workspace = true } num-traits = { workspace = true } sea-orm = { workspace = true } diff --git a/program_transformers/src/asset_upserts.rs b/program_transformers/src/asset_upserts.rs index 2d1d17e..a82923a 100644 --- a/program_transformers/src/asset_upserts.rs +++ b/program_transformers/src/asset_upserts.rs @@ -9,6 +9,7 @@ use { sea_query::OnConflict, ConnectionTrait, DbBackend, DbErr, EntityTrait, QueryTrait, Set, TransactionTrait, }, + serde_json::value::Value, }; pub struct AssetTokenAccountColumns { @@ -54,7 +55,7 @@ pub async fn upsert_assets_token_account_columns, pub supply: u64, - pub suppply_mint: Option>, + pub supply_mint: Option>, pub slot_updated_mint_account: u64, } @@ -65,7 +66,7 @@ pub async fn upsert_assets_mint_account_columns>, pub slot_updated_metadata_account: u64, + pub mpl_core_plugins: Option, + pub mpl_core_unknown_plugins: Option, + pub mpl_core_collection_num_minted: Option, + pub mpl_core_collection_current_size: Option, + pub mpl_core_plugins_json_version: Option, + pub mpl_core_external_plugins: Option, + pub mpl_core_unknown_external_plugins: Option, } pub async fn upsert_assets_metadata_account_columns( @@ -120,6 +128,13 @@ pub async fn upsert_assets_metadata_account_columns 1; - info!("Adding to backfill_items table at level {}", i - 1); + println!("Adding to backfill_items table at level {}", i - 1); let item = backfill_items::ActiveModel { tree: ActiveValue::Set(tree_id.to_vec()), seq: ActiveValue::Set(change_log_event.seq as i64), diff --git a/program_transformers/src/bubblegum/mod.rs b/program_transformers/src/bubblegum/mod.rs index 0765901..dc108a0 100644 --- a/program_transformers/src/bubblegum/mod.rs +++ b/program_transformers/src/bubblegum/mod.rs @@ -59,7 +59,7 @@ where InstructionName::SetDecompressibleState => "SetDecompressibleState", InstructionName::UpdateMetadata => "UpdateMetadata", }; - info!("BGUM instruction txn={:?}: {:?}", ix_str, bundle.txn_id); + println!("BGUM instruction txn={:?}: {:?}", ix_str, bundle.txn_id); match ix_type { InstructionName::Transfer => { diff --git a/program_transformers/src/lib.rs b/program_transformers/src/lib.rs index d544941..7abdd18 100644 --- a/program_transformers/src/lib.rs +++ b/program_transformers/src/lib.rs @@ -2,6 +2,7 @@ use { crate::{ bubblegum::handle_bubblegum_instruction, error::{ProgramTransformerError, ProgramTransformerResult}, + mpl_core_program::handle_mpl_core_account, token::handle_token_program_account, token_metadata::handle_token_metadata_account, }, @@ -9,69 +10,48 @@ use { instruction::{order_instructions, InstructionBundle, IxPair}, program_handler::ProgramParser, programs::{ - bubblegum::BubblegumParser, token_account::TokenAccountParser, - token_metadata::TokenMetadataParser, ProgramParseResult, + bubblegum::BubblegumParser, mpl_core_program::MplCoreParser, + token_account::TokenAccountParser, token_metadata::TokenMetadataParser, + ProgramParseResult, }, }, - futures::future::BoxFuture, - sea_orm::{DatabaseConnection, SqlxPostgresConnector}, + das_core::{DownloadMetadataInfo, DownloadMetadataNotifier}, + sea_orm::{ + entity::EntityTrait, query::Select, ConnectionTrait, DatabaseConnection, DbErr, + SqlxPostgresConnector, TransactionTrait, + }, solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey, signature::Signature}, solana_transaction_status::InnerInstructions, sqlx::PgPool, std::collections::{HashMap, HashSet, VecDeque}, + tokio::time::{sleep, Duration}, tracing::{debug, error, info}, }; mod asset_upserts; mod bubblegum; pub mod error; +mod mpl_core_program; mod token; mod token_metadata; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct AccountInfo<'a> { - pub slot: u64, - pub pubkey: &'a Pubkey, - pub owner: &'a Pubkey, - pub data: &'a [u8], -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct TransactionInfo<'a> { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountInfo { pub slot: u64, - pub signature: &'a Signature, - pub account_keys: &'a [Pubkey], - pub message_instructions: &'a [CompiledInstruction], - pub meta_inner_instructions: &'a [InnerInstructions], + pub pubkey: Pubkey, + pub owner: Pubkey, + pub data: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct DownloadMetadataInfo { - asset_data_id: Vec, - uri: String, -} - -impl DownloadMetadataInfo { - pub fn new(asset_data_id: Vec, uri: String) -> Self { - Self { - asset_data_id, - uri: uri.trim().replace('\0', ""), - } - } - - pub fn into_inner(self) -> (Vec, String) { - (self.asset_data_id, self.uri) - } +pub struct TransactionInfo { + pub slot: u64, + pub signature: Signature, + pub account_keys: Vec, + pub message_instructions: Vec, + pub meta_inner_instructions: Vec, } -pub type DownloadMetadataNotifier = Box< - dyn Fn( - DownloadMetadataInfo, - ) -> BoxFuture<'static, Result<(), Box>> - + Sync - + Send, ->; - pub struct ProgramTransformer { storage: DatabaseConnection, download_metadata_notifier: DownloadMetadataNotifier, @@ -90,9 +70,11 @@ impl ProgramTransformer { let bgum = BubblegumParser {}; let token_metadata = TokenMetadataParser {}; let token = TokenAccountParser {}; + let mpl_core = MplCoreParser {}; parsers.insert(bgum.key(), Box::new(bgum)); parsers.insert(token_metadata.key(), Box::new(token_metadata)); parsers.insert(token.key(), Box::new(token)); + parsers.insert(mpl_core.key(), Box::new(mpl_core)); let hs = parsers.iter().fold(HashSet::new(), |mut acc, (k, _)| { acc.insert(*k); acc @@ -109,13 +91,13 @@ impl ProgramTransformer { pub fn break_transaction<'a>( &self, - tx_info: &'a TransactionInfo<'_>, + tx_info: &'a TransactionInfo, ) -> VecDeque<(IxPair<'a>, Option>>)> { order_instructions( &self.key_set, - tx_info.account_keys, - tx_info.message_instructions, - tx_info.meta_inner_instructions, + tx_info.account_keys.as_slice(), + tx_info.message_instructions.as_slice(), + tx_info.meta_inner_instructions.as_slice(), ) } @@ -126,9 +108,9 @@ impl ProgramTransformer { pub async fn handle_transaction( &self, - tx_info: &TransactionInfo<'_>, + tx_info: &TransactionInfo, ) -> ProgramTransformerResult<()> { - info!("Handling Transaction: {:?}", tx_info.signature); + println!("Handling Transaction: {:?}", tx_info.signature); let instructions = self.break_transaction(tx_info); let mut not_impl = 0; let ixlen = instructions.len(); @@ -204,10 +186,10 @@ impl ProgramTransformer { pub async fn handle_account_update( &self, - account_info: &AccountInfo<'_>, + account_info: &AccountInfo, ) -> ProgramTransformerResult<()> { - if let Some(program) = self.match_program(account_info.owner) { - let result = program.handle_account(account_info.data)?; + if let Some(program) = self.match_program(&account_info.owner) { + let result = program.handle_account(&account_info.data)?; match result.result_type() { ProgramParseResult::TokenMetadata(parsing_result) => { handle_token_metadata_account( @@ -227,9 +209,51 @@ impl ProgramTransformer { ) .await } + ProgramParseResult::MplCore(parsing_result) => { + handle_mpl_core_account( + account_info, + parsing_result, + &self.storage, + &self.download_metadata_notifier, + ) + .await + } _ => Err(ProgramTransformerError::NotImplemented), }?; } Ok(()) } } + +pub async fn find_model_with_retry( + conn: &T, + model_name: &str, + select: &Select, + retry_intervals: &[u64], +) -> Result, DbErr> { + let mut retries = 0; + let metric_name = format!("{}_found", model_name); + + for interval in retry_intervals { + let interval_duration = Duration::from_millis(*interval); + sleep(interval_duration).await; + + let model = select.clone().one(conn).await?; + if let Some(m) = model { + record_metric(&metric_name, true, retries); + return Ok(Some(m)); + } + retries += 1; + } + + record_metric(&metric_name, false, retries - 1); + Ok(None) +} + +fn record_metric(metric_name: &str, success: bool, retries: u32) { + let retry_count = &retries.to_string(); + let success = if success { "true" } else { "false" }; + if cadence_macros::is_global_default_set() { + cadence_macros::statsd_count!(metric_name, 1, "success" => success, "retry_count" => retry_count); + } +} diff --git a/program_transformers/src/mpl_core_program/mod.rs b/program_transformers/src/mpl_core_program/mod.rs new file mode 100644 index 0000000..b01252d --- /dev/null +++ b/program_transformers/src/mpl_core_program/mod.rs @@ -0,0 +1,42 @@ +use { + crate::{ + error::{ProgramTransformerError, ProgramTransformerResult}, + mpl_core_program::v1_asset::{burn_v1_asset, save_v1_asset}, + AccountInfo, DownloadMetadataNotifier, + }, + blockbuster::programs::mpl_core_program::{MplCoreAccountData, MplCoreAccountState}, + sea_orm::DatabaseConnection, +}; + +mod v1_asset; + +pub async fn handle_mpl_core_account<'a, 'b, 'c>( + account_info: &AccountInfo, + parsing_result: &'a MplCoreAccountState, + db: &'b DatabaseConnection, + download_metadata_notifier: &DownloadMetadataNotifier, +) -> ProgramTransformerResult<()> { + match &parsing_result.data { + MplCoreAccountData::EmptyAccount => { + burn_v1_asset(db, account_info.pubkey, account_info.slot).await?; + Ok(()) + } + MplCoreAccountData::Asset(_) | MplCoreAccountData::Collection(_) => { + if let Some(info) = save_v1_asset( + db, + account_info.pubkey, + &parsing_result.data, + account_info.slot, + ) + .await? + { + download_metadata_notifier(info) + .await + .map_err(ProgramTransformerError::DownloadMetadataNotify)?; + } + Ok(()) + } + _ => Err(ProgramTransformerError::NotImplemented), + }?; + Ok(()) +} diff --git a/program_transformers/src/mpl_core_program/v1_asset.rs b/program_transformers/src/mpl_core_program/v1_asset.rs new file mode 100644 index 0000000..6714508 --- /dev/null +++ b/program_transformers/src/mpl_core_program/v1_asset.rs @@ -0,0 +1,642 @@ +use { + crate::{ + asset_upserts::{ + upsert_assets_metadata_account_columns, upsert_assets_mint_account_columns, + upsert_assets_token_account_columns, AssetMetadataAccountColumns, + AssetMintAccountColumns, AssetTokenAccountColumns, + }, + error::{ProgramTransformerError, ProgramTransformerResult}, + find_model_with_retry, DownloadMetadataInfo, + }, + blockbuster::{ + mpl_core::types::{Plugin, PluginAuthority, PluginType, UpdateAuthority}, + programs::mpl_core_program::MplCoreAccountData, + }, + digital_asset_types::{ + dao::{ + asset, asset_authority, asset_creators, asset_data, asset_grouping, + sea_orm_active_enums::{ + ChainMutability, Mutability, OwnerType, SpecificationAssetClass, + }, + }, + json::ChainDataV1, + }, + heck::ToSnakeCase, + sea_orm::{ + entity::{ActiveValue, ColumnTrait, EntityTrait}, + query::{JsonValue, QueryFilter, QueryTrait}, + sea_query::query::OnConflict, + sea_query::Expr, + ConnectionTrait, CursorTrait, DbBackend, TransactionTrait, + }, + serde_json::{value::Value, Map}, + solana_sdk::pubkey::Pubkey, + tracing::warn, +}; + +pub async fn burn_v1_asset( + conn: &T, + id: Pubkey, + slot: u64, +) -> ProgramTransformerResult<()> { + let slot_i = slot as i64; + let model = asset::ActiveModel { + id: ActiveValue::Set(id.to_bytes().to_vec()), + slot_updated: ActiveValue::Set(Some(slot_i)), + burnt: ActiveValue::Set(true), + ..Default::default() + }; + let mut query = asset::Entity::insert(model) + .on_conflict( + OnConflict::columns([asset::Column::Id]) + .update_columns([asset::Column::SlotUpdated, asset::Column::Burnt]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.slot_updated > asset.slot_updated", + query.sql + ); + conn.execute(query).await?; + Ok(()) +} + +const RETRY_INTERVALS: &[u64] = &[0, 5, 10]; + +pub async fn save_v1_asset( + conn: &T, + id: Pubkey, + account_data: &MplCoreAccountData, + slot: u64, +) -> ProgramTransformerResult> { + // Notes: + // The address of the Core asset is used for Core Asset ID. There are no token or mint accounts. + // There are no `MasterEdition` or `Edition` accounts associated with Core assets. + let id_array = id.to_bytes(); + let id_vec = id_array.to_vec(); + + // Note: This indexes both Core Assets and Core Collections. + let asset = match account_data { + MplCoreAccountData::Asset(indexable_asset) + | MplCoreAccountData::Collection(indexable_asset) => indexable_asset, + _ => return Err(ProgramTransformerError::NotImplemented), + }; + + //----------------------- + // Asset authority table + //----------------------- + + // If it is an `Address` type, use the value directly. If it is a `Collection`, search for and + // use the collection's authority. + let update_authority = match asset.update_authority { + UpdateAuthority::Address(address) => address.to_bytes().to_vec(), + UpdateAuthority::Collection(address) => find_model_with_retry( + conn, + "mpl_core", + &asset_authority::Entity::find() + .filter(asset_authority::Column::AssetId.eq(address.to_bytes().to_vec())), + RETRY_INTERVALS, + ) + .await? + .map(|model| model.authority) + .unwrap_or_default(), + UpdateAuthority::None => Pubkey::default().to_bytes().to_vec(), + }; + + let slot_i = slot as i64; + + let txn = conn.begin().await?; + + let model = asset_authority::ActiveModel { + asset_id: ActiveValue::Set(id_vec.clone()), + authority: ActiveValue::Set(update_authority.clone()), + seq: ActiveValue::Set(0), + slot_updated: ActiveValue::Set(slot_i), + ..Default::default() + }; + + let mut query = asset_authority::Entity::insert(model) + .on_conflict( + OnConflict::columns([asset_authority::Column::AssetId]) + .update_columns([ + asset_authority::Column::Authority, + asset_authority::Column::Seq, + asset_authority::Column::SlotUpdated, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.slot_updated > asset_authority.slot_updated", + query.sql + ); + txn.execute(query) + .await + .map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?; + + if matches!(account_data, MplCoreAccountData::Collection(_)) { + update_group_asset_authorities(conn, id_vec.clone(), update_authority.clone(), slot_i) + .await?; + } + + //----------------------- + // asset_data table + //----------------------- + + let name = asset.name.clone().into_bytes(); + let uri = asset.uri.trim().replace('\0', ""); + + // Notes: + // There is no symbol for a Core asset. + // Edition nonce hardcoded to `None`. + // There is no primary sale concept for Core Assets, hardcoded to `false`. + // Token standard is hardcoded to `None`. + let mut chain_data = ChainDataV1 { + name: asset.name.clone(), + symbol: "".to_string(), + edition_nonce: None, + primary_sale_happened: false, + token_standard: None, + uses: None, + }; + + chain_data.sanitize(); + let chain_data_json = serde_json::to_value(chain_data) + .map_err(|e| ProgramTransformerError::DeserializationError(e.to_string()))?; + + // Note: + // Mutability set based on core asset data having an update authority. + // Individual plugins could have some or no authority giving them individual mutability status. + let chain_mutability = match asset.update_authority { + UpdateAuthority::None => ChainMutability::Immutable, + _ => ChainMutability::Mutable, + }; + + let asset_data_model = asset_data::ActiveModel { + chain_data_mutability: ActiveValue::Set(chain_mutability), + chain_data: ActiveValue::Set(chain_data_json), + metadata_url: ActiveValue::Set(uri.clone()), + metadata: ActiveValue::Set(JsonValue::String("processing".to_string())), + metadata_mutability: ActiveValue::Set(Mutability::Mutable), + slot_updated: ActiveValue::Set(slot_i), + reindex: ActiveValue::Set(Some(true)), + id: ActiveValue::Set(id_vec.clone()), + raw_name: ActiveValue::Set(Some(name.to_vec())), + raw_symbol: ActiveValue::Set(None), + base_info_seq: ActiveValue::Set(Some(0)), + }; + + let mut query = asset_data::Entity::insert(asset_data_model) + .on_conflict( + OnConflict::columns([asset_data::Column::Id]) + .update_columns([ + asset_data::Column::ChainDataMutability, + asset_data::Column::ChainData, + asset_data::Column::MetadataUrl, + asset_data::Column::MetadataMutability, + asset_data::Column::SlotUpdated, + asset_data::Column::Reindex, + asset_data::Column::RawName, + asset_data::Column::RawSymbol, + asset_data::Column::BaseInfoSeq, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.slot_updated > asset_data.slot_updated", + query.sql + ); + txn.execute(query) + .await + .map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?; + + //----------------------- + // asset table + //----------------------- + + let ownership_type = OwnerType::Single; + let (owner, class) = match account_data { + MplCoreAccountData::Asset(_) => ( + asset.owner.map(|owner| owner.to_bytes().to_vec()), + SpecificationAssetClass::MplCoreAsset, + ), + MplCoreAccountData::Collection(_) => ( + Some(update_authority.clone()), + SpecificationAssetClass::MplCoreCollection, + ), + _ => return Err(ProgramTransformerError::NotImplemented), + }; + + // Get royalty amount and creators from `Royalties` plugin if available. + let default_creators = Vec::new(); + let (royalty_amount, creators) = asset + .plugins + .get(&PluginType::Royalties) + .and_then(|plugin_schema| { + if let Plugin::Royalties(royalties) = &plugin_schema.data { + Some((royalties.basis_points, &royalties.creators)) + } else { + None + } + }) + .unwrap_or((0, &default_creators)); + + // Serialize known plugins into JSON. + let mut plugins_json = serde_json::to_value(&asset.plugins) + .map_err(|e| ProgramTransformerError::DeserializationError(e.to_string()))?; + + // Improve JSON output. + remove_plugins_nesting(&mut plugins_json, "data"); + transform_plugins_authority(&mut plugins_json); + convert_keys_to_snake_case(&mut plugins_json); + + // Serialize any unknown plugins into JSON. + let unknown_plugins_json = if !asset.unknown_plugins.is_empty() { + let mut unknown_plugins_json = serde_json::to_value(&asset.unknown_plugins) + .map_err(|e| ProgramTransformerError::DeserializationError(e.to_string()))?; + + // Improve JSON output. + transform_plugins_authority(&mut unknown_plugins_json); + convert_keys_to_snake_case(&mut unknown_plugins_json); + + Some(unknown_plugins_json) + } else { + None + }; + + // Serialize known external plugins into JSON. + let mut external_plugins_json = serde_json::to_value(&asset.external_plugins) + .map_err(|e| ProgramTransformerError::DeserializationError(e.to_string()))?; + + // Improve JSON output. + remove_plugins_nesting(&mut external_plugins_json, "adapter_config"); + transform_plugins_authority(&mut external_plugins_json); + convert_keys_to_snake_case(&mut external_plugins_json); + + // Serialize any unknown external plugins into JSON. + let unknown_external_plugins_json = if !asset.unknown_external_plugins.is_empty() { + let mut unknown_external_plugins_json = + serde_json::to_value(&asset.unknown_external_plugins) + .map_err(|e| ProgramTransformerError::DeserializationError(e.to_string()))?; + + // Improve JSON output. + transform_plugins_authority(&mut unknown_external_plugins_json); + convert_keys_to_snake_case(&mut unknown_external_plugins_json); + + Some(unknown_external_plugins_json) + } else { + None + }; + + upsert_assets_metadata_account_columns( + AssetMetadataAccountColumns { + mint: id_vec.clone(), + owner_type: ownership_type, + specification_asset_class: Some(class), + royalty_amount: royalty_amount as i32, + asset_data: Some(id_vec.clone()), + slot_updated_metadata_account: slot, + mpl_core_plugins: Some(plugins_json), + mpl_core_unknown_plugins: unknown_plugins_json, + mpl_core_collection_num_minted: asset.num_minted.map(|val| val as i32), + mpl_core_collection_current_size: asset.current_size.map(|val| val as i32), + mpl_core_plugins_json_version: Some(1), + mpl_core_external_plugins: Some(external_plugins_json), + mpl_core_unknown_external_plugins: unknown_external_plugins_json, + }, + &txn, + ) + .await?; + + let supply = 1; + + // Note: these need to be separate for Token Metadata but here could be one upsert. + upsert_assets_mint_account_columns( + AssetMintAccountColumns { + mint: id_vec.clone(), + supply_mint: None, + supply, + slot_updated_mint_account: slot, + }, + &txn, + ) + .await?; + + // Get transfer delegate from `TransferDelegate` plugin if available. + let transfer_delegate = + asset + .plugins + .get(&PluginType::TransferDelegate) + .and_then(|plugin_schema| match &plugin_schema.authority { + PluginAuthority::Owner => owner.clone(), + PluginAuthority::UpdateAuthority => Some(update_authority.clone()), + PluginAuthority::Address { address } => Some(address.to_bytes().to_vec()), + PluginAuthority::None => None, + }); + + // Get frozen status from `FreezeDelegate` plugin if available. + let frozen = asset + .plugins + .get(&PluginType::FreezeDelegate) + .and_then(|plugin_schema| { + if let Plugin::FreezeDelegate(freeze_delegate) = &plugin_schema.data { + Some(freeze_delegate.frozen) + } else { + None + } + }) + .unwrap_or(false); + + // TODO: these upserts needed to be separate for Token Metadata but here could be one upsert. + upsert_assets_token_account_columns( + AssetTokenAccountColumns { + mint: id_vec.clone(), + owner, + frozen, + // Note use transfer delegate for the existing delegate field. + delegate: transfer_delegate.clone(), + slot_updated_token_account: Some(slot_i), + }, + &txn, + ) + .await?; + + //----------------------- + // asset_grouping table + //----------------------- + + if let UpdateAuthority::Collection(address) = asset.update_authority { + let model = asset_grouping::ActiveModel { + asset_id: ActiveValue::Set(id_vec.clone()), + group_key: ActiveValue::Set("collection".to_string()), + group_value: ActiveValue::Set(Some(address.to_string())), + // Note all Core assets in a collection are verified. + verified: ActiveValue::Set(true), + group_info_seq: ActiveValue::Set(Some(0)), + slot_updated: ActiveValue::Set(Some(slot_i)), + ..Default::default() + }; + let mut query = asset_grouping::Entity::insert(model) + .on_conflict( + OnConflict::columns([ + asset_grouping::Column::AssetId, + asset_grouping::Column::GroupKey, + ]) + .update_columns([ + asset_grouping::Column::GroupValue, + asset_grouping::Column::Verified, + asset_grouping::Column::SlotUpdated, + asset_grouping::Column::GroupInfoSeq, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.slot_updated >= asset_grouping.slot_updated", + query.sql + ); + txn.execute(query) + .await + .map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?; + } + + //----------------------- + // creators table + //----------------------- + + let creators = creators + .iter() + .enumerate() + .map(|(i, creator)| asset_creators::ActiveModel { + asset_id: ActiveValue::Set(id_vec.clone()), + position: ActiveValue::Set(i as i16), + creator: ActiveValue::Set(creator.address.to_bytes().to_vec()), + share: ActiveValue::Set(creator.percentage as i32), + // Note all creators are verified for Core Assets. + verified: ActiveValue::Set(true), + slot_updated: ActiveValue::Set(Some(slot_i)), + seq: ActiveValue::Set(Some(0)), + ..Default::default() + }) + .collect::>(); + + if !creators.is_empty() { + let mut query = asset_creators::Entity::insert_many(creators) + .on_conflict( + OnConflict::columns([ + asset_creators::Column::AssetId, + asset_creators::Column::Position, + ]) + .update_columns([ + asset_creators::Column::Creator, + asset_creators::Column::Share, + asset_creators::Column::Verified, + asset_creators::Column::Seq, + asset_creators::Column::SlotUpdated, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.slot_updated >= asset_creators.slot_updated OR asset_creators.slot_updated is NULL", + query.sql + ); + txn.execute(query) + .await + .map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?; + } + + // Commit the database transaction. + txn.commit().await?; + + // Return early if there is no URI. + if uri.is_empty() { + warn!( + "URI is empty for mint {}. Skipping background task.", + bs58::encode(id_vec.clone()).into_string() + ); + return Ok(None); + } + + // Otherwise return with info for background downloading. + Ok(Some(DownloadMetadataInfo::new(id_vec.clone(), uri))) +} + +// Modify the JSON structure to remove the `Plugin` name and just display its data. +// For example, this will transform `FreezeDelegate` JSON from: +// "data":{"freeze_delegate":{"frozen":false}}} +// to: +// "data":{"frozen":false} +fn remove_plugins_nesting(plugins_json: &mut Value, nested_key: &str) { + match plugins_json { + Value::Object(plugins) => { + // Handle the case where plugins_json is an object. + for (_, plugin) in plugins.iter_mut() { + remove_nesting_from_plugin(plugin, nested_key); + } + } + Value::Array(plugins_array) => { + // Handle the case where plugins_json is an array. + for plugin in plugins_array.iter_mut() { + remove_nesting_from_plugin(plugin, nested_key); + } + } + _ => {} + } +} + +fn remove_nesting_from_plugin(plugin: &mut Value, nested_key: &str) { + if let Some(Value::Object(nested_key)) = plugin.get_mut(nested_key) { + // Extract the plugin data and remove it. + if let Some((_, inner_plugin_data)) = nested_key.iter().next() { + let inner_plugin_data_clone = inner_plugin_data.clone(); + // Clear the `nested_key` object. + nested_key.clear(); + // Move the plugin data fields to the top level of `nested_key`. + if let Value::Object(inner_plugin_data) = inner_plugin_data_clone { + for (field_name, field_value) in inner_plugin_data.iter() { + nested_key.insert(field_name.clone(), field_value.clone()); + } + } + } + } +} + +// Modify the JSON for `PluginAuthority` to have consistent output no matter the enum type. +// For example, from: +// "authority":{"Address":{"address":"D7whDWAP5gN9x4Ff6T9MyQEkotyzmNWtfYhCEWjbUDBM"}} +// to: +// "authority":{"address":"4dGxsCAwSCopxjEYY7sFShFUkfKC6vzsNEXJDzFYYFXh","type":"Address"} +// and from: +// "authority":"UpdateAuthority" +// to: +// "authority":{"address":null,"type":"UpdateAuthority"} +fn transform_plugins_authority(plugins_json: &mut Value) { + match plugins_json { + Value::Object(plugins) => { + // Transform plugins in an object + for (_, plugin) in plugins.iter_mut() { + if let Some(plugin_obj) = plugin.as_object_mut() { + transform_authority_in_object(plugin_obj); + } + } + } + Value::Array(plugins_array) => { + // Transform plugins in an array + for plugin in plugins_array.iter_mut() { + if let Some(plugin_obj) = plugin.as_object_mut() { + transform_authority_in_object(plugin_obj); + } + } + } + _ => {} + } +} + +// Helper for `transform_plugins_authority` logic. +fn transform_authority_in_object(plugin: &mut Map) { + match plugin.get_mut("authority") { + Some(Value::Object(authority)) => { + if let Some(authority_type) = authority.keys().next().cloned() { + // Replace the nested JSON objects with desired format. + if let Some(Value::Object(pubkey_obj)) = authority.remove(&authority_type) { + if let Some(address_value) = pubkey_obj.get("address") { + authority.insert("type".to_string(), Value::from(authority_type)); + authority.insert("address".to_string(), address_value.clone()); + } + } + } + } + Some(Value::String(authority_type)) => { + // Handle the case where authority is a string. + let mut authority_obj = Map::new(); + authority_obj.insert("type".to_string(), Value::String(authority_type.clone())); + authority_obj.insert("address".to_string(), Value::Null); + plugin.insert("authority".to_string(), Value::Object(authority_obj)); + } + _ => {} + } +} + +// Convert all keys to snake case. Ignore values that aren't JSON objects themselves. +fn convert_keys_to_snake_case(plugins_json: &mut Value) { + match plugins_json { + Value::Object(obj) => { + let keys = obj.keys().cloned().collect::>(); + for key in keys { + let snake_case_key = key.to_snake_case(); + if let Some(val) = obj.remove(&key) { + obj.insert(snake_case_key, val); + } + } + for (_, val) in obj.iter_mut() { + convert_keys_to_snake_case(val); + } + } + Value::Array(arr) => { + for val in arr { + convert_keys_to_snake_case(val); + } + } + _ => {} + } +} + +/// Updates the `asset_authority` for all assets that are part of a collection in a batch. +/// This function performs a cursor-based paginated read and batch update. +async fn update_group_asset_authorities( + conn: &T, + group_value: Vec, + authority: Vec, + slot: i64, +) -> ProgramTransformerResult<()> { + let mut after = None; + + let group_key = "collection".to_string(); + let group_value = bs58::encode(group_value).into_string(); + + let mut query = asset_grouping::Entity::find() + .filter(asset_grouping::Column::GroupKey.eq(group_key)) + .filter(asset_grouping::Column::GroupValue.eq(group_value)) + .cursor_by(asset_grouping::Column::AssetId); + let mut query = query.first(1_000); + + loop { + if let Some(after) = after.clone() { + query = query.after(after); + } + + let entries = query.all(conn).await?; + + if entries.is_empty() { + break; + } + + let asset_ids = entries + .clone() + .into_iter() + .map(|entry| entry.asset_id) + .collect::>(); + + asset_authority::Entity::update_many() + .col_expr( + asset_authority::Column::Authority, + Expr::value(authority.clone()), + ) + .col_expr(asset_authority::Column::SlotUpdated, Expr::value(slot)) + .filter(asset_authority::Column::AssetId.is_in(asset_ids)) + .filter(asset_authority::Column::Authority.ne(authority.clone())) + .filter(Expr::cust_with_values( + "asset_authority.slot_updated < $1", + vec![slot], + )) + .exec(conn) + .await + .map_err(|db_err| ProgramTransformerError::AssetIndexError(db_err.to_string()))?; + + after = entries.last().map(|entry| entry.asset_id.clone()); + } + + Ok(()) +} diff --git a/program_transformers/src/token/mod.rs b/program_transformers/src/token/mod.rs index 2dd5d99..7cb70c4 100644 --- a/program_transformers/src/token/mod.rs +++ b/program_transformers/src/token/mod.rs @@ -19,10 +19,10 @@ use { spl_token::state::AccountState, }; -pub async fn handle_token_program_account<'a, 'b, 'c>( - account_info: &'a AccountInfo<'a>, - parsing_result: &'b TokenProgramAccount, - db: &'c DatabaseConnection, +pub async fn handle_token_program_account<'a, 'b>( + account_info: &AccountInfo, + parsing_result: &'a TokenProgramAccount, + db: &'b DatabaseConnection, _download_metadata_notifier: &DownloadMetadataNotifier, ) -> ProgramTransformerResult<()> { let account_key = account_info.pubkey.to_bytes().to_vec(); @@ -154,7 +154,7 @@ pub async fn handle_token_program_account<'a, 'b, 'c>( upsert_assets_mint_account_columns( AssetMintAccountColumns { mint: account_key.clone(), - suppply_mint: Some(account_key), + supply_mint: Some(account_key), supply: m.supply, slot_updated_mint_account: account_info.slot, }, diff --git a/program_transformers/src/token_metadata/mod.rs b/program_transformers/src/token_metadata/mod.rs index 9f9d278..cbeb941 100644 --- a/program_transformers/src/token_metadata/mod.rs +++ b/program_transformers/src/token_metadata/mod.rs @@ -14,20 +14,20 @@ use { mod master_edition; mod v1_asset; -pub async fn handle_token_metadata_account<'a, 'b, 'c>( - account_info: &'a AccountInfo<'a>, - parsing_result: &'b TokenMetadataAccountState, - db: &'c DatabaseConnection, +pub async fn handle_token_metadata_account<'a, 'b>( + account_info: &AccountInfo, + parsing_result: &'a TokenMetadataAccountState, + db: &'b DatabaseConnection, download_metadata_notifier: &DownloadMetadataNotifier, ) -> ProgramTransformerResult<()> { match &parsing_result.data { TokenMetadataAccountData::EmptyAccount => { - burn_v1_asset(db, *account_info.pubkey, account_info.slot).await?; + burn_v1_asset(db, account_info.pubkey, account_info.slot).await?; Ok(()) } TokenMetadataAccountData::MasterEditionV1(m) => { let txn = db.begin().await?; - save_v1_master_edition(*account_info.pubkey, account_info.slot, m, &txn).await?; + save_v1_master_edition(account_info.pubkey, account_info.slot, m, &txn).await?; txn.commit().await?; Ok(()) } @@ -41,7 +41,7 @@ pub async fn handle_token_metadata_account<'a, 'b, 'c>( } TokenMetadataAccountData::MasterEditionV2(m) => { let txn = db.begin().await?; - save_v2_master_edition(*account_info.pubkey, account_info.slot, m, &txn).await?; + save_v2_master_edition(account_info.pubkey, account_info.slot, m, &txn).await?; txn.commit().await?; Ok(()) } diff --git a/program_transformers/src/token_metadata/v1_asset.rs b/program_transformers/src/token_metadata/v1_asset.rs index 95b860c..59f4edd 100644 --- a/program_transformers/src/token_metadata/v1_asset.rs +++ b/program_transformers/src/token_metadata/v1_asset.rs @@ -6,7 +6,7 @@ use { AssetMintAccountColumns, AssetTokenAccountColumns, }, error::{ProgramTransformerError, ProgramTransformerResult}, - DownloadMetadataInfo, + find_model_with_retry, DownloadMetadataInfo, }, blockbuster::token_metadata::{ accounts::{MasterEdition, Metadata}, @@ -26,12 +26,11 @@ use { }, sea_orm::{ entity::{ActiveValue, ColumnTrait, EntityTrait}, - query::{JsonValue, Order, QueryFilter, QueryOrder, QueryTrait, Select}, + query::{JsonValue, Order, QueryFilter, QueryOrder, QueryTrait}, sea_query::query::OnConflict, ConnectionTrait, DbBackend, DbErr, TransactionTrait, }, solana_sdk::{pubkey, pubkey::Pubkey}, - tokio::time::{sleep, Duration}, tracing::warn, }; @@ -40,7 +39,7 @@ pub async fn burn_v1_asset( id: Pubkey, slot: u64, ) -> ProgramTransformerResult<()> { - let (id, slot_i) = (id, slot as i64); + let slot_i = slot as i64; let model = asset::ActiveModel { id: ActiveValue::Set(id.to_bytes().to_vec()), slot_updated: ActiveValue::Set(Some(slot_i)), @@ -84,7 +83,7 @@ pub async fn index_and_fetch_mint_data( upsert_assets_mint_account_columns( AssetMintAccountColumns { mint: mint_pubkey_vec.clone(), - suppply_mint: Some(token.mint.clone()), + supply_mint: Some(token.mint.clone()), supply: token.supply as u64, slot_updated_mint_account: token.slot_updated as u64, }, @@ -269,6 +268,13 @@ pub async fn save_v1_asset( royalty_amount: metadata.seller_fee_basis_points as i32, asset_data: Some(mint_pubkey_vec.clone()), slot_updated_metadata_account: slot_i as u64, + mpl_core_plugins: None, + mpl_core_unknown_plugins: None, + mpl_core_collection_num_minted: None, + mpl_core_collection_current_size: None, + mpl_core_plugins_json_version: None, + mpl_core_external_plugins: None, + mpl_core_unknown_external_plugins: None, }, &txn, ) @@ -405,36 +411,3 @@ pub async fn save_v1_asset( Ok(Some(DownloadMetadataInfo::new(mint_pubkey_vec, uri))) } - -async fn find_model_with_retry( - conn: &T, - model_name: &str, - select: &Select, - retry_intervals: &[u64], -) -> Result, DbErr> { - let mut retries = 0; - let metric_name = format!("{}_found", model_name); - - for interval in retry_intervals { - let interval_duration = Duration::from_millis(*interval); - sleep(interval_duration).await; - - let model = select.clone().one(conn).await?; - if let Some(m) = model { - record_metric(&metric_name, true, retries); - return Ok(Some(m)); - } - retries += 1; - } - - record_metric(&metric_name, false, retries - 1); - Ok(None) -} - -fn record_metric(metric_name: &str, success: bool, retries: u32) { - let retry_count = &retries.to_string(); - let success = if success { "true" } else { "false" }; - if cadence_macros::is_global_default_set() { - cadence_macros::statsd_count!(metric_name, 1, "success" => success, "retry_count" => retry_count); - } -} diff --git a/src/backfill/backfill.rs b/src/backfill/backfill.rs deleted file mode 100644 index 6dbdf65..0000000 --- a/src/backfill/backfill.rs +++ /dev/null @@ -1,86 +0,0 @@ -use sea_orm::{ConnectionTrait, DatabaseConnection, Statement}; -use solana_rpc_client_api::response::RpcConfirmedTransactionStatusWithSignature; - -use crate::{ - config::transaction_queue::{push_front, TransactionsQueue}, - rpc::rpc::get_signatures_for_tree, -}; - -pub async fn backfill_tree(tree_address: String, db_connection: DatabaseConnection) { - let mut last_processed_tx: Option = None; - let mut until_signature: Option = None; - let mut genesis_backfill_completed: bool = false; - - let query = "SELECT last_processed_signature,genesis_backfill_completed FROM ld_merkle_trees WHERE address=$1;"; - - let query_res = db_connection - .query_one(Statement::from_sql_and_values( - sea_orm::DatabaseBackend::Postgres, - query, - vec![sea_orm::Value::from(tree_address.as_str())], - )) - .await - .unwrap(); - - match query_res { - Some(row) => { - let sign: Option = row.try_get("", "last_processed_signature").unwrap_or(None); - println!("last_processed_signature: {:?}", sign); - last_processed_tx = sign; - - let gbc: bool = row - .try_get("", "genesis_backfill_completed") - .unwrap_or(false); - - genesis_backfill_completed = gbc; - } - None => { - println!( - "No last_processed_signature found for tree: {}", - tree_address - ); - } - } - - if genesis_backfill_completed { - until_signature = last_processed_tx.clone(); - last_processed_tx.take(); - } - - println!( - "genesis_backfill_completed: {:?}", - genesis_backfill_completed - ); - println!("until_signature: {:?}", until_signature); - println!("last processed: {:?}", last_processed_tx); - - let mut signatures: Vec; - - loop { - signatures = get_signatures_for_tree( - &tree_address, - last_processed_tx.as_ref(), - until_signature.as_ref(), - ) - .await; - - if signatures.len() == 0 { - break; - } - - last_processed_tx = Some(signatures[signatures.len() - 1].signature.clone()); - println!("last_processed_tx: {:?}", last_processed_tx); - - for signature in &signatures { - println!("backfill tx {:?}", signature.signature); - push_front(TransactionsQueue { - transaction_signature: signature.signature.clone(), - tree_address: tree_address.clone().into(), - }) - } - - if signatures.len() < 1000 { - break; - } - } -} diff --git a/src/backfill/mod.rs b/src/backfill/mod.rs deleted file mode 100644 index 493f1c3..0000000 --- a/src/backfill/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod backfill; diff --git a/src/config/mod.rs b/src/config/mod.rs index c04f281..fb11b7c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,3 @@ pub mod database; pub mod env_config; -pub mod transaction_queue; pub mod rpc_config; diff --git a/src/config/rpc_config.rs b/src/config/rpc_config.rs index 3e24b22..ef9ac25 100644 --- a/src/config/rpc_config.rs +++ b/src/config/rpc_config.rs @@ -1,5 +1,3 @@ -use std::env; - use crate::config::env_config::EnvConfig; use solana_client::nonblocking::pubsub_client::PubsubClient; use solana_client::nonblocking::rpc_client::RpcClient; diff --git a/src/config/transaction_queue.rs b/src/config/transaction_queue.rs deleted file mode 100644 index 2bcb65a..0000000 --- a/src/config/transaction_queue.rs +++ /dev/null @@ -1,29 +0,0 @@ -use lazy_static::lazy_static; -use std::sync::Mutex; -use std::{borrow::BorrowMut, collections::VecDeque, sync::OnceLock}; - -#[derive(Clone, Debug)] -pub struct TransactionsQueue { - pub transaction_signature: String, - pub tree_address: Option, -} - -lazy_static! { - static ref TRANSACTIONS_QUEUE: Mutex> = Mutex::new(VecDeque::new()); -} - -pub fn push_front(transaction: TransactionsQueue) { - TRANSACTIONS_QUEUE.lock().unwrap().push_front(transaction); -} - -pub fn pop_front() -> Option { - TRANSACTIONS_QUEUE.lock().unwrap().pop_front() -} - -pub fn pop_back() -> Option { - TRANSACTIONS_QUEUE.lock().unwrap().pop_back() -} - -pub fn push_back(transaction: TransactionsQueue) { - TRANSACTIONS_QUEUE.lock().unwrap().push_back(transaction) -} diff --git a/src/main.rs b/src/main.rs index 55d52e6..c791a1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,51 @@ -use std::iter; use std::pin::Pin; -use std::str::{Bytes, FromStr}; -// use std::thread::sleep; + +use std::str::FromStr; +use std::sync::Arc; +use std::thread; use std::time::Duration; use crate::config::database::setup_database_config; use crate::config::env_config::{setup_env_config, EnvConfig}; use anyhow::Result; -use backfill::backfill::backfill_tree; use config::rpc_config::{get_pubsub_client, setup_rpc_clients}; +use das_bubblegum_backfill::worker::{ + GapWorkerArgs, ProgramTransformerWorkerArgs, SignatureWorkerArgs, +}; +use das_bubblegum_backfill::{ + start_bubblegum_backfill, BubblegumBackfillArgs, BubblegumBackfillContext, +}; +use das_core::{MetadataJsonDownloadWorkerArgs, Rpc, SolanaRpcArgs}; use dotenv::dotenv; -use futures::future::join; + use futures::prelude::*; -use futures::stream::SelectAll; -use futures::{future::join_all, stream::select_all}; -use mpl_bubblegum::accounts::MerkleTree; -use processor::logs::process_logs; -use processor::metadata::fetch_store_metadata; -use processor::queue_processor::process_transactions_queue; -use sea_orm::{ConnectionTrait, Database, DatabaseConnection, SqlxPostgresConnector, Statement}; + +use log::info; +use mpl_token_metadata::types::Data; +use processor::transactions_channel_processor::process_transactions_channel; +use program_transformers::ProgramTransformer; + +use sea_orm::{ConnectionTrait, DatabaseConnection, SqlxPostgresConnector, Statement}; use solana_client::rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}; use solana_client::rpc_response::{Response, RpcLogsResponse}; use solana_sdk::commitment_config::CommitmentConfig; + use solana_sdk::pubkey::Pubkey; -use sqlx::{Acquire, PgPool}; -use tokio::task; -use tokio::time::sleep; +use sqlx::{Pool, Postgres}; + +use tokio::task::{self}; + +use signal_hook::{consts::signal::SIGHUP, iterator::Signals}; -mod backfill; mod config; mod processor; mod rpc; +struct State { + tree_addresses: Vec, + tasks: Vec<(String, task::JoinHandle<()>)>, +} + #[tokio::main] async fn main() -> Result<()> { dotenv().ok(); @@ -42,36 +56,94 @@ async fn main() -> Result<()> { let database_pool = setup_database_config(&env_config).await; - let db_connection = SqlxPostgresConnector::from_sqlx_postgres_pool(database_pool.clone()); + if let Err(e) = configure_database(SqlxPostgresConnector::from_sqlx_postgres_pool( + database_pool.clone(), + )) + .await + { + panic!("Error configuring database: {:?}", e); + } - // Refer https://github.com/WilfredAlmeida/LightDAS/issues/4 to understand why this is needed - let _ = db_connection - .execute(Statement::from_string( - sea_orm::DatabaseBackend::Postgres, - String::from( - "CREATE TABLE IF NOT EXISTS LD_MERKLE_TREES ( - ADDRESS VARCHAR(255), - TAG VARCHAR(255) NULL, - CAPACITY INT NULL, - MAX_DEPTH INT NULL, - CANOPY_DEPTH INT NULL, - MAX_BUFFER_SIZE INT NULL, - SHOULD_INDEX BOOLEAN DEFAULT TRUE, - GENESIS_BACKFILL_COMPLETED BOOLEAN DEFAULT FALSE, - LAST_PROCESSED_SIGNATURE VARCHAR(255) NULL, - CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UPDATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP - );", - ), - )) - .await?; + let tree_addresses = match get_trees(SqlxPostgresConnector::from_sqlx_postgres_pool( + database_pool.clone(), + )) + .await + { + Ok(tree_addresses) => tree_addresses, + Err(e) => { + eprintln!("Error getting trees: {:?}", e); + return Err(e); + } + }; + + if tree_addresses.is_empty() { + eprintln!("No trees found. Exiting..."); + } + + let state = Arc::new(std::sync::Mutex::new(State { + tree_addresses, + tasks: vec![], + })); + + let state_clone = Arc::clone(&state); + + let (signal_tx, mut signal_rx) = tokio::sync::mpsc::channel(1); + + // thread to handle SIGHUP + thread::spawn(move || { + let mut signals = Signals::new(&[SIGHUP]).unwrap(); + for _ in signals.forever() { + let _ = signal_tx.blocking_send(()); + } + }); + + let mut state = state_clone.lock().unwrap(); + reload_tasks(&mut *state, database_pool.clone(), env_config); + + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(1)) => { + // publish metrics + }, + _ = signal_rx.recv() => { + println!("Received SIGHUP, reloading..."); - let pubsub_client = get_pubsub_client(); + let trees = get_trees(SqlxPostgresConnector::from_sqlx_postgres_pool( + database_pool.clone(), + )) + .await + .unwrap(); + + state.tree_addresses = trees; + + reload_tasks( + &mut state, + database_pool.clone(), + setup_env_config(), + ); + } + } + } +} + +async fn handle_stream( + mut stream: Pin> + Send + 'static>>, + sender: tokio::sync::mpsc::UnboundedSender, +) { + loop { + if let Some(logs) = stream.next().await { + if let Err(e) = sender.send(logs.value) { + eprintln!("Error sending logs to transaction processing: {:?}", e); + } + } + } +} - let res = db_connection +async fn get_trees(database_connection: DatabaseConnection) -> Result> { + let res = database_connection .query_all(Statement::from_string( sea_orm::DatabaseBackend::Postgres, - String::from("SELECT * FROM ld_merkle_trees;"), + String::from("SELECT * FROM ld_merkle_trees WHERE should_index IS TRUE;"), )) .await; @@ -79,7 +151,7 @@ async fn main() -> Result<()> { match res { Ok(rows) => { if rows.len() == 0 { - panic!("Trees to index not found in database"); + panic!("Trees to index not found in the database"); } rows.iter().for_each(|row| { @@ -89,7 +161,7 @@ async fn main() -> Result<()> { if let Ok(_) = Pubkey::from_str(s.as_str()) { tree_addresses.push(s); } else { - println!("Invalid tree address {:?}", s) + eprintln!("Invalid tree address {:?}", s) } } None => {} @@ -101,99 +173,117 @@ async fn main() -> Result<()> { } } - // let tree_addresses: Vec = vec![ - // // "GXTXbFwcbNdWbiCWzZc3J2XGofopnhN9T98jnG29D2Yw".to_string(), - // // "Aju7YfPdhjaqJbRdow48PqxcWutDDHWww6eoDC9PVY7m".to_string(), - // // "43XAHmPkq8Yth3swdqrh5aZvWrmuci5ZhPVLptreaUZ1".to_string(), - // // "EQQiiEceUo2uxHQgtRt8W92frLXwMUwdvt7P9Yo26cUM".to_string(), - // // "CkSa2n2eyJvsPLA7ufVos94NAUTYuVhaxrvH2GS69f9j".to_string() - // // "Dbx2uKULg44XeBR28tNWu2dU4bPpGfuYrd7RntgGXvuT".to_string(), - // // "CkSa2n2eyJvsPLA7ufVos94NAUTYuVhaxrvH2GS69f9j".to_string(), - // // "EBFsHQKYCn1obUr2FVNvGTkaUYf2p5jao2MVdbK5UNRH".to_string(), - // // "14b9wzhVSaiUHB4t8tDY9QYNsGStT8ycaoLkBHZLZwax".to_string(), - // // "6kAoPaZV4aB1rMPTPkbgycb9iNbHHibSzjhAvWEroMm".to_string(), - // // "FmUjM4YBLK93WSb7AnbuYZy1h2kCcjZM8kHsi9ZU93TP".to_string(), - // // "6JTnMcq9a6atrqmsz4rgTWp9EG5YPzxoobD7vg1csNt5".to_string(), - // // "HVGMVJ7DyfXLU2H5AJSHvX2HkFrRrHQAoXAHfYUmicYr".to_string(), - // // "D8yRakvsjWSR3ihANhwjP8RmNLg3A46EA1V1EbMLDT8B".to_string(), - // "B1eWW3tTBb5DHrwVrqJximAYLwucGzvjuJWxkFAe4v2X".to_string(), - // ]; - - println!("TREE ADDRESSES {:?}", tree_addresses); - - let mut stream = select_all( - join_all(tree_addresses.iter().map(|address| { - pubsub_client.logs_subscribe( - RpcTransactionLogsFilter::Mentions(vec![address.to_string()]), - RpcTransactionLogsConfig { - commitment: Some(CommitmentConfig::processed()), - }, - ) - })) - .await - .into_iter() - .flat_map(|result| match result { - Ok(subscription) => Some(subscription.0), - Err(e) => { - eprintln!("error creating subscription: {e}"); - None - } + Ok(tree_addresses) +} + +fn reload_tasks(state: &mut State, database_pool: Pool, env_config: EnvConfig) { + state.tasks.retain(|(s, handle)| { + if !state.tree_addresses.contains(s) { + handle.abort(); + false + } else { + true + } + }); + + let context = BubblegumBackfillContext::new( + database_pool.clone(), + Rpc::from_config(&SolanaRpcArgs { + solana_rpc_url: env_config.get_rpc_url().to_string(), }), ); - let handle = task::spawn(handle_stream(stream)); - - task::spawn(handle_metadata_downloads(database_pool.clone())); - - // join_all(tree_addresses.into_iter().map(|tr| { - // let db_connection_1 = SqlxPostgresConnector::from_sqlx_postgres_pool(database_pool.clone()); - // let db_connection_2 = SqlxPostgresConnector::from_sqlx_postgres_pool(database_pool.clone()); - - // let backfill_future = backfill_tree(tr.clone(), db_connection_1); - - // async move { - // let _ = backfill_future.await; - - // let _ = db_connection_2 - // .execute(Statement::from_sql_and_values( - // sea_orm::DatabaseBackend::Postgres, - // "UPDATE ld_merkle_trees SET genesis_backfill_completed=$1 WHERE address=$2;", - // vec![ - // sea_orm::Value::from(true), - // sea_orm::Value::from(tr.as_str()), - // ], - // )) - // .await; - // } - // })) - // .await; - - // tasks spawned to process transactions from queue. depending on your tree and queue sizes, adjust this - futures::future::join_all( - iter::repeat_with(|| process_transactions_queue(database_pool.clone())) - .take(15) - .collect::>(), - ) - .await; - - Ok(()) -} + let tree_addresses = state.tree_addresses.clone(); -async fn handle_stream( - mut stream: SelectAll> + Send>>>, -) { - loop { - if let Some(logs) = stream.next().await { - process_logs(logs.value).await; - } + for address in tree_addresses { + let address_clone = address.clone(); + + let program_transformer = ProgramTransformer::new( + database_pool.clone(), + Box::new(|_info| futures::future::ready(Ok(())).boxed()), + false, + ); + + let context = context.clone(); + + let args = BubblegumBackfillArgs { + only_trees: Some(vec![address.clone().to_string()]), + tree_crawler_count: 4, + tree_worker: das_bubblegum_backfill::worker::TreeWorkerArgs { + metadata_json_download_worker: MetadataJsonDownloadWorkerArgs { + metadata_json_download_worker_count: 100, + metadata_json_download_worker_request_timeout: 200, + }, + signature_worker: SignatureWorkerArgs { + signature_channel_size: 100, + signature_worker_count: 100, + }, + gap_worker: GapWorkerArgs { + gap_channel_size: 100, + gap_worker_count: 100, + }, + program_transformer_worker: ProgramTransformerWorkerArgs { + program_transformer_channel_size: 100, + }, + }, + }; + + let task_handle = task::spawn(async move { + let address = address.clone(); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); + + let stream: Pin> + Send>> = + get_pubsub_client() + .logs_subscribe( + RpcTransactionLogsFilter::Mentions(vec![address.clone().to_string()]), + RpcTransactionLogsConfig { + commitment: Some(CommitmentConfig::processed()), + }, + ) + .await + .unwrap() + .0; + + task::spawn(async move { + handle_stream(stream, tx).await; + }); + + println!("Backfill started for tree: {:}", address); + + if let Err(e) = start_bubblegum_backfill(context.clone(), args).await { + eprintln!("Error backfilling tree {:?}: {:?}", address.clone(), e); + } + + println!("Backfill finished and for tree: {:}", address); + println!("Starting live indexing for tree: {:}", address); + + process_transactions_channel(rx, &program_transformer).await; + }); + + state.tasks.push((address_clone, task_handle)); } } -async fn handle_metadata_downloads(pool: PgPool) { - let connection = SqlxPostgresConnector::from_sqlx_postgres_pool(pool); - loop { - let _ = fetch_store_metadata(&connection).await; - println!("No metadata to update, sleeping for 5 secs"); - sleep(Duration::from_secs(5)).await; - } +async fn configure_database( + database_connection: DatabaseConnection, +) -> Result { + // Refer https://github.com/WilfredAlmeida/LightDAS/issues/4 to understand why this is needed + database_connection + .execute(Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + String::from( + "CREATE TABLE IF NOT EXISTS LD_MERKLE_TREES ( + ADDRESS VARCHAR(255), + TAG VARCHAR(255) NULL, + CAPACITY INT NULL, + MAX_DEPTH INT NULL, + CANOPY_DEPTH INT NULL, + MAX_BUFFER_SIZE INT NULL, + SHOULD_INDEX BOOLEAN DEFAULT TRUE, + CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UPDATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );", + ), + )) + .await } diff --git a/src/processor/handlers/mint_to_collection_v1.rs b/src/processor/handlers/mint_to_collection_v1.rs deleted file mode 100644 index c37d280..0000000 --- a/src/processor/handlers/mint_to_collection_v1.rs +++ /dev/null @@ -1,12 +0,0 @@ -use blockbuster::{instruction::InstructionBundle, programs::bubblegum::BubblegumInstruction}; -use mpl_bubblegum::instructions::{MintToCollectionV1, MintToCollectionV1InstructionArgs}; -use sqlx::{Pool, Postgres}; - -use crate::config::database; - -pub async fn handle_mint_to_collection_v1_instruction( - accounts: MintToCollectionV1, - args: MintToCollectionV1InstructionArgs, - database_pool: Pool, -) { -} diff --git a/src/processor/handlers/mod.rs b/src/processor/handlers/mod.rs deleted file mode 100644 index 238ac99..0000000 --- a/src/processor/handlers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mint_to_collection_v1; \ No newline at end of file diff --git a/src/processor/logs.rs b/src/processor/logs.rs deleted file mode 100644 index 59d2dbc..0000000 --- a/src/processor/logs.rs +++ /dev/null @@ -1,16 +0,0 @@ -use solana_client::rpc_response::RpcLogsResponse; - -use crate::{ - config::transaction_queue::{push_back, TransactionsQueue}, - rpc::rpc::get_transaction_with_retries, -}; - -pub async fn process_logs(logs_response: RpcLogsResponse) { - let transaction_signature = logs_response.signature; - - println!("websocket tx"); - push_back(TransactionsQueue { - transaction_signature: transaction_signature.clone(), - tree_address: None, - }); -} diff --git a/src/processor/metadata.rs b/src/processor/metadata.rs deleted file mode 100644 index 0cf5046..0000000 --- a/src/processor/metadata.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::time::Duration; - -use digital_asset_types::{dao::asset_data, json}; -use reqwest::{Client, ClientBuilder}; -use sea_orm::{ - ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, Set, Unchanged, -}; - -pub async fn fetch_store_metadata(database: &DatabaseConnection) -> Result { - println!("Fetching Metadata"); - - let assets = asset_data::Entity::find() - .columns([asset_data::Column::Id, asset_data::Column::MetadataUrl]) - .filter(asset_data::Column::Reindex.eq(true)) - .all(database) - .await - .unwrap(); - - println!("Assets to update: {:?}", assets.len()); - - for asset in assets { - let asset_id = &asset.id; - - let metadata_url = &asset.metadata_url; - - let client = ClientBuilder::new() - .timeout(Duration::from_secs(4)) - .build()?; - - let response = Client::get(&client, metadata_url).send().await?; - - if response.status() != reqwest::StatusCode::OK { - println!("Download Metadata Error"); - continue; - } - - let val: serde_json::Value = response.json().await?; - - let model = asset_data::ActiveModel { - id: Unchanged(asset_id.clone()), - metadata: Set(val), - reindex: Set(Some(false)), - ..Default::default() - }; - - asset_data::Entity::update(model) - .filter(asset_data::Column::Id.eq(asset.id)) - .exec(database) - .await - .unwrap(); - - println!("Metadata Updated") - } - - Ok(true) -} diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 729bdde..0d8b269 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,6 +1,2 @@ -pub mod logs; -pub mod parser; pub mod transaction; -pub mod handlers; -pub mod queue_processor; -pub mod metadata; \ No newline at end of file +pub mod transactions_channel_processor; \ No newline at end of file diff --git a/src/processor/parser.rs b/src/processor/parser.rs deleted file mode 100644 index c0ee34c..0000000 --- a/src/processor/parser.rs +++ /dev/null @@ -1,189 +0,0 @@ -use std::cell::RefCell; -use std::fmt; -use borsh::BorshDeserialize; -use mpl_bubblegum::{ - get_instruction_type, - instructions::{ - Burn, BurnInstructionArgs, MintToCollectionV1, MintToCollectionV1InstructionArgs, MintV1, - MintV1InstructionArgs, Transfer, TransferInstructionArgs, - }, - InstructionName, ID as MPL_BUBBLEGUM_ID, -}; -use solana_sdk::instruction::AccountMeta; -use solana_sdk::pubkey; - -pub enum BubblegumInstruction { - MintV1 { - accounts: MintV1, - args: MintV1InstructionArgs, - }, - Transfer { - accounts: Transfer, - args: TransferInstructionArgs, - }, - Burn { - accounts: Burn, - args: BurnInstructionArgs, - }, - MintToCollectionV1 { - accounts: MintToCollectionV1, - args: MintToCollectionV1InstructionArgs, - }, -} - -impl BubblegumInstruction { - pub fn parse(account_metas: &[AccountMeta], data: &[u8]) -> BubblegumInstruction { - let accounts_iter = RefCell::new(account_metas.into_iter()); - let next_account_meta = || { - accounts_iter - .borrow_mut() - .next() - .expect("incorrect number of accounts for instruction") - }; - let next_account = || next_account_meta().clone().pubkey.clone(); - let next_account_option = || match next_account() { - MPL_BUBBLEGUM_ID => None, - key => Some(key), - }; - let next_account_with_signer = || { - let &AccountMeta { - pubkey, is_signer, .. - } = next_account_meta(); - (pubkey, is_signer) - }; - - let mut arg_data = &data[8..]; - - match get_instruction_type(data) { - InstructionName::MintV1 => BubblegumInstruction::MintV1 { - accounts: MintV1 { - tree_config: next_account(), - leaf_owner: next_account(), - leaf_delegate: next_account(), - merkle_tree: next_account(), - payer: next_account(), - tree_creator_or_delegate: next_account(), - log_wrapper: next_account(), - compression_program: next_account(), - system_program: next_account(), - }, - args: MintV1InstructionArgs::deserialize(&mut arg_data) - .expect("could not parse args data"), - }, - InstructionName::Transfer => BubblegumInstruction::Transfer { - accounts: Transfer { - tree_config: next_account(), - leaf_owner: next_account_with_signer(), - leaf_delegate: next_account_with_signer(), - new_leaf_owner: next_account(), - merkle_tree: next_account(), - log_wrapper: next_account(), - compression_program: next_account(), - system_program: next_account(), - }, - args: TransferInstructionArgs::deserialize(&mut arg_data) - .expect("could not parse args data"), - }, - InstructionName::Burn => BubblegumInstruction::Burn { - accounts: Burn { - tree_config: next_account(), - leaf_owner: next_account_with_signer(), - leaf_delegate: next_account_with_signer(), - merkle_tree: next_account(), - log_wrapper: next_account(), - compression_program: next_account(), - system_program: next_account(), - }, - args: BurnInstructionArgs::deserialize(&mut arg_data) - .expect("could not parse args data"), - }, - InstructionName::MintToCollectionV1 => BubblegumInstruction::MintToCollectionV1 { - accounts: MintToCollectionV1 { - tree_config: next_account(), - leaf_owner: next_account(), - leaf_delegate: next_account(), - merkle_tree: next_account(), - payer: next_account(), - tree_creator_or_delegate: next_account(), - collection_authority: next_account(), - collection_authority_record_pda: next_account_option(), - collection_mint: next_account(), - collection_metadata: next_account(), - collection_edition: next_account(), - bubblegum_signer: next_account(), - log_wrapper: next_account(), - compression_program: next_account(), - token_metadata_program: next_account(), - system_program: next_account(), - }, - args: MintToCollectionV1InstructionArgs::deserialize(&mut arg_data) - .expect("could not parse args data"), - }, - - _ => panic!("unknown instruction"), - } - } -} - -impl fmt::Debug for BubblegumInstruction { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - use BubblegumInstruction::*; - match self { - MintV1{accounts, args} => fmt.debug_struct("BubblegumInstruction::MintV1") - .field("accounts::tree_config", &accounts.tree_config) - .field("accounts::leaf_owner", &accounts.leaf_owner) - .field("accounts::leaf_delegate", &accounts.leaf_delegate) - .field("accounts::merkle_tree", &accounts.merkle_tree) - .field("accounts::payer", &accounts.payer) - .field("accounts::tree_creator_or_delegate", &accounts.tree_creator_or_delegate) - .field("accounts::log_wrapper", &accounts.log_wrapper) - .field("accounts::compression_program", &accounts.compression_program) - .field("accounts::system_program", &accounts.system_program) - .field("accounts::args", &args) - .finish(), - - Transfer{accounts, args} => fmt.debug_struct("BubblegumInstruction::Transfer") - .field("accounts::tree_config", &accounts.tree_config) - .field("accounts::leaf_owner", &accounts.leaf_owner) - .field("accounts::leaf_delegate", &accounts.leaf_delegate) - .field("accounts::new_leaf_owner", &accounts.new_leaf_owner) - .field("accounts::merkle_tree", &accounts.merkle_tree) - .field("accounts::log_wrapper", &accounts.log_wrapper) - .field("accounts::compression_program", &accounts.compression_program) - .field("accounts::system_program", &accounts.system_program) - .field("args", &args) - .finish(), - - Burn{accounts, args} => fmt.debug_struct("BubblegumInstruction::Burn") - .field("accounts::tree_config", &accounts.tree_config) - .field("accounts::leaf_owner", &accounts.leaf_owner) - .field("accounts::leaf_delegate", &accounts.leaf_delegate) - .field("accounts::merkle_tree", &accounts.merkle_tree) - .field("accounts::log_wrapper", &accounts.log_wrapper) - .field("accounts::compression_program", &accounts.compression_program) - .field("accounts::system_program", &accounts.system_program) - .field("args", &args) - .finish(), - - MintToCollectionV1{accounts, args} => fmt.debug_struct("BubblegumInstruction::MintToCollectionV1") - .field("accounts::tree_config", &accounts.tree_config) - .field("accounts::leaf_owner", &accounts.leaf_owner) - .field("accounts::leaf_delegate", &accounts.leaf_delegate) - .field("accounts::merkle_tree", &accounts.merkle_tree) - .field("accounts::payer", &accounts.payer) - .field("accounts::tree_creator_or_delegate", &accounts.tree_creator_or_delegate) - .field("accounts::collection_authority", &accounts.collection_authority) - .field("accounts::collection_authority_record_pda", &accounts.collection_authority_record_pda) - .field("accounts::collection_mint", &accounts.collection_mint) - .field("accounts::collection_metadata", &accounts.collection_metadata) - .field("accounts::collection_edition", &accounts.collection_edition) - .field("accounts::bubblegum_signer", &accounts.bubblegum_signer) - .field("accounts::log_wrapper", &accounts.log_wrapper) - .field("accounts::compression_program", &accounts.compression_program) - .field("accounts::token_metadata_program", &accounts.token_metadata_program) - .field("accounts::system_program", &accounts.system_program) - .field("args", &args) - .finish(), - } - } -} \ No newline at end of file diff --git a/src/processor/queue_processor.rs b/src/processor/queue_processor.rs deleted file mode 100644 index f3064f2..0000000 --- a/src/processor/queue_processor.rs +++ /dev/null @@ -1,47 +0,0 @@ -use program_transformers::ProgramTransformer; -use sea_orm::{ConnectionTrait, SqlxPostgresConnector, Statement, Value}; -use sqlx::PgPool; - -use crate::{config::transaction_queue::pop_front, rpc::rpc::get_transaction_with_retries}; - -use super::transaction::process_transaction; -use futures::future::{ready, FutureExt}; - -use tokio::time::{sleep, Duration}; - -pub async fn process_transactions_queue(database_pool: PgPool) { - let program_transformer = ProgramTransformer::new( - database_pool.clone(), - Box::new(|_info| ready(Ok(())).boxed()), - false, - ); - - loop { - if let Some(txs) = pop_front() { - let transaction_signature = &txs.transaction_signature; - let tree_address = txs.tree_address; - - if let Ok(transaction) = get_transaction_with_retries(transaction_signature).await { - if process_transaction(&program_transformer, transaction).await.is_ok() { - if let Some(tree_address_string) = tree_address { - let db_connection = SqlxPostgresConnector::from_sqlx_postgres_pool(database_pool.clone()); - let query = "UPDATE ld_merkle_trees SET last_processed_signature=$1 WHERE address=$2;"; - - if let Err(e) = db_connection.execute(Statement::from_sql_and_values( - sea_orm::DatabaseBackend::Postgres, - query, - vec![ - Value::from(transaction_signature.as_str()), - Value::from(tree_address_string.as_str()), - ], - )).await { - println!("Failed to update `ld_merkle_trees` column `last_processed_signature` with error {:?}", e); - } - } - } - } - } else { - sleep(Duration::from_millis(100)).await; - } - } -} diff --git a/src/processor/transaction.rs b/src/processor/transaction.rs index 87579da..5c68100 100644 --- a/src/processor/transaction.rs +++ b/src/processor/transaction.rs @@ -42,9 +42,9 @@ pub async fn process_transaction( let res = program_transformer .handle_transaction(&TransactionInfo { slot: transaction.slot, - signature: &unwrapped_transaction.signatures[0], - account_keys: &account_keys, - message_instructions: &message.instructions(), + signature: unwrapped_transaction.signatures[0], + account_keys: account_keys, + message_instructions: message.instructions().into(), meta_inner_instructions: inner_instructions .unwrap_or_default() .into_iter() @@ -66,16 +66,14 @@ pub async fn process_transaction( }) .collect(), }) - .collect::>() - .as_slice(), + .collect::>(), }) .await; if let Err(e) = res { - println!("TX HANDLING ERROR: {:?}", e); + eprintln!("tx handling error: {:?}", e); return Ok(()); } - println!("HANDLED TX"); Ok(()) } diff --git a/src/processor/transactions_channel_processor.rs b/src/processor/transactions_channel_processor.rs new file mode 100644 index 0000000..67de3a1 --- /dev/null +++ b/src/processor/transactions_channel_processor.rs @@ -0,0 +1,23 @@ +use program_transformers::ProgramTransformer; +use solana_client::rpc_response::RpcLogsResponse; + +use crate::rpc::rpc::get_transaction_with_retries; + +use super::transaction::process_transaction; + +pub async fn process_transactions_channel( + mut receiver: tokio::sync::mpsc::UnboundedReceiver, + program_transformer: &ProgramTransformer, +) { + loop { + if let Some(logs) = receiver.recv().await { + let transaction_signature = logs.signature; + + if let Ok(transaction) = get_transaction_with_retries(&transaction_signature).await { + if let Err(e) = process_transaction(&program_transformer, transaction).await { + eprintln!("Transaction processing error: {:?}", e); + } + } + } + } +} diff --git a/src/rpc/rpc.rs b/src/rpc/rpc.rs index 91920fe..18a6c88 100644 --- a/src/rpc/rpc.rs +++ b/src/rpc/rpc.rs @@ -3,11 +3,9 @@ use std::time::Duration; use crate::config::rpc_config::get_rpc_client; use solana_client::client_error::{ClientError, ClientErrorKind}; -use solana_client::rpc_client::GetConfirmedSignaturesForAddress2Config; use solana_rpc_client_api::config::RpcTransactionConfig; -use solana_rpc_client_api::response::RpcConfirmedTransactionStatusWithSignature; +use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::signature::Signature; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding}; pub async fn get_transaction_with_retries( @@ -36,10 +34,10 @@ pub async fn get_transaction_with_retries( return Ok(transaction); } Err(e) => { - println!("Error: {:?}", e); + eprintln!("Error: {:?}", e); delay += 100; - println!("Retrying in {}ms", delay); + eprintln!("Retrying in {}ms", delay); tokio::time::sleep(Duration::from_millis(delay)).await; } } @@ -50,32 +48,3 @@ pub async fn get_transaction_with_retries( request: None, }) } - -pub async fn get_signatures_for_tree( - tree_address: &str, - last_processed_tx: Option<&String>, - until_signature: Option<&String>, -) -> Vec { - let tree_address_pubkey = Pubkey::from_str(tree_address).expect("Invalid tree address"); - - let last_processed_tx_signature = last_processed_tx - .map(|signature| Signature::from_str(signature).expect("Invalid signature")); - - let until_tx_signature = - until_signature.map(|signature| Signature::from_str(signature).expect("Invalid signature")); - - let rpc_client = get_rpc_client(); - - rpc_client - .get_signatures_for_address_with_config( - &tree_address_pubkey, - GetConfirmedSignaturesForAddress2Config { - commitment: Some(CommitmentConfig::confirmed()), - before: last_processed_tx_signature, - until: until_tx_signature, - ..Default::default() - }, - ) - .await - .expect("Failed to get signatures for tree") -}