diff --git a/.gitignore b/.gitignore index b78f821af..03506ed58 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ flamegraph.* !/minio/bucket-policy.json *.bak +*settings.toml diff --git a/Cargo.lock b/Cargo.lock index 596a95200..d2f0a140b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,8 +356,7 @@ dependencies = [ [[package]] name = "anchor-gen" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3b9def91d1f0c23b99be210afea0990f931a1edae439ea30e0da50baeaea8f" +source = "git+https://github.com/ChewingGlass/anchor-gen.git?branch=master#e6136a50918660c8bab0101568068419cbd23420" dependencies = [ "anchor-generate-cpi-crate", "anchor-generate-cpi-interface", @@ -366,8 +365,7 @@ dependencies = [ [[package]] name = "anchor-generate-cpi-crate" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e8733d55b8492bde0d6f219c25769786758ff9e5e90a16e6a348f91284af50" +source = "git+https://github.com/ChewingGlass/anchor-gen.git?branch=master#e6136a50918660c8bab0101568068419cbd23420" dependencies = [ "anchor-idl", "syn 1.0.109", @@ -376,8 +374,7 @@ dependencies = [ [[package]] name = "anchor-generate-cpi-interface" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7092a50cf959ebf53460162cb07e88d4cc8ea7fa8c45292e80503a3186033daf" +source = "git+https://github.com/ChewingGlass/anchor-gen.git?branch=master#e6136a50918660c8bab0101568068419cbd23420" dependencies = [ "anchor-idl", "darling 0.14.2", @@ -387,8 +384,7 @@ dependencies = [ [[package]] name = "anchor-idl" version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de2665dc06ee0bd3c7d08f47f136a3d5b1e34b7ac1bc632c86a812add04d68b" +source = "git+https://github.com/ChewingGlass/anchor-gen.git?branch=master#e6136a50918660c8bab0101568068419cbd23420" dependencies = [ "anchor-syn 0.24.2", "darling 0.14.2", @@ -1615,7 +1611,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "beacon" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#8e3edc2053a16ec98421d83211399338836f91e4" +source = "git+https://github.com/helium/proto?branch=master#4085e00c6f4d82c3da798ae1bc97324bc9cada2e" dependencies = [ "base64 0.21.7", "byteorder", @@ -2129,7 +2125,7 @@ dependencies = [ [[package]] name = "circuit-breaker" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -2771,7 +2767,7 @@ dependencies = [ [[package]] name = "data-credits" version = "0.2.2" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -3149,7 +3145,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fanout" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -3725,7 +3721,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helium-anchor-gen" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -3772,7 +3768,7 @@ dependencies = [ [[package]] name = "helium-entity-manager" version = "0.2.11" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -3781,7 +3777,7 @@ dependencies = [ [[package]] name = "helium-lib" version = "0.0.0" -source = "git+https://github.com/helium/helium-wallet-rs.git?branch=master#6b8a6bbf343027087a401cd907c9f6c848991aac" +source = "git+https://github.com/helium/helium-wallet-rs.git?branch=master#d527be0dc3db4641c0a658ed462b53e98864eb73" dependencies = [ "anchor-client", "anchor-spl", @@ -3796,7 +3792,6 @@ dependencies = [ "helium-crypto", "helium-proto", "hex", - "hex-literal", "itertools", "jsonrpc_client", "lazy_static", @@ -3811,7 +3806,8 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "spl-account-compression", - "spl-associated-token-account 3.0.2", + "spl-associated-token-account 1.1.3", + "spl-memo 4.0.0", "thiserror", "tonic", "tracing", @@ -3821,7 +3817,7 @@ dependencies = [ [[package]] name = "helium-proto" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#8e3edc2053a16ec98421d83211399338836f91e4" +source = "git+https://github.com/helium/proto?branch=master#4085e00c6f4d82c3da798ae1bc97324bc9cada2e" dependencies = [ "bytes", "prost", @@ -3837,7 +3833,7 @@ dependencies = [ [[package]] name = "helium-sub-daos" version = "0.1.8" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -3891,7 +3887,7 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hexboosting" version = "0.1.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -4360,6 +4356,7 @@ dependencies = [ "file-store", "futures", "futures-util", + "helium-anchor-gen", "helium-crypto", "helium-proto", "hextree", @@ -4373,8 +4370,11 @@ dependencies = [ "prost", "rand 0.8.5", "retainer", + "rust_decimal", "serde", "serde_json", + "solana", + "solana-sdk", "sqlx", "task-manager", "thiserror", @@ -4703,7 +4703,7 @@ dependencies = [ [[package]] name = "lazy-distributor" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -4712,7 +4712,7 @@ dependencies = [ [[package]] name = "lazy-transactions" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -5090,7 +5090,7 @@ dependencies = [ [[package]] name = "mobile-entity-manager" version = "0.1.3" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -5946,7 +5946,7 @@ dependencies = [ [[package]] name = "price-oracle" version = "0.2.1" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -6063,7 +6063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", - "heck 0.4.0", + "heck 0.5.0", "itertools", "log", "multimap", @@ -6595,7 +6595,7 @@ dependencies = [ [[package]] name = "rewards-oracle" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -7316,6 +7316,7 @@ dependencies = [ "anchor-client", "anyhow", "async-trait", + "bincode", "chrono", "clap 4.4.8", "file-store", @@ -7329,6 +7330,7 @@ dependencies = [ "solana-client", "solana-program", "solana-sdk", + "spl-associated-token-account 1.1.3", "spl-token 3.5.0", "thiserror", "tokio", @@ -7354,8 +7356,8 @@ dependencies = [ "solana-sdk", "spl-token 4.0.0", "spl-token-2022 1.0.0", - "spl-token-group-interface 0.1.0", - "spl-token-metadata-interface 0.2.0", + "spl-token-group-interface", + "spl-token-metadata-interface", "thiserror", "zstd", ] @@ -7960,7 +7962,7 @@ dependencies = [ "solana-account-decoder", "solana-sdk", "spl-associated-token-account 2.3.0", - "spl-memo", + "spl-memo 4.0.0", "spl-token 4.0.0", "spl-token-2022 1.0.0", "thiserror", @@ -8106,33 +8108,33 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "2.3.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" +checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" dependencies = [ "assert_matches", - "borsh 0.10.3", - "num-derive 0.4.2", + "borsh 0.9.3", + "num-derive 0.3.3", "num-traits", "solana-program", - "spl-token 4.0.0", - "spl-token-2022 1.0.0", + "spl-token 3.5.0", + "spl-token-2022 0.6.1", "thiserror", ] [[package]] name = "spl-associated-token-account" -version = "3.0.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e688554bac5838217ffd1fab7845c573ff106b6336bf7d290db7c98d5a8efd" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", - "borsh 1.5.1", + "borsh 0.10.3", "num-derive 0.4.2", "num-traits", "solana-program", "spl-token 4.0.0", - "spl-token-2022 3.0.2", + "spl-token-2022 1.0.0", "thiserror", ] @@ -8155,18 +8157,7 @@ checksum = "daa600f2fe56f32e923261719bae640d873edadbc5237681a39b8e37bfd4d263" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator-derive 0.1.2", -] - -[[package]] -name = "spl-discriminator" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d1814406e98b08c5cd02c1126f83fd407ad084adce0b05fda5730677822eac" -dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator-derive 0.2.0", + "spl-discriminator-derive", ] [[package]] @@ -8176,18 +8167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", - "spl-discriminator-syn 0.1.2", - "syn 2.0.58", -] - -[[package]] -name = "spl-discriminator-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" -dependencies = [ - "quote", - "spl-discriminator-syn 0.2.0", + "spl-discriminator-syn", "syn 2.0.58", ] @@ -8205,16 +8185,12 @@ dependencies = [ ] [[package]] -name = "spl-discriminator-syn" -version = "0.2.0" +name = "spl-memo" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f05593b7ca9eac7caca309720f2eafb96355e037e6d373b909a80fe7b69b9" +checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" dependencies = [ - "proc-macro2", - "quote", - "sha2 0.10.8", - "syn 2.0.58", - "thiserror", + "solana-program", ] [[package]] @@ -8245,20 +8221,7 @@ dependencies = [ "bytemuck", "solana-program", "solana-zk-token-sdk", - "spl-program-error 0.3.1", -] - -[[package]] -name = "spl-pod" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ce669f48cf2eca1ec518916d8725596bfb655beb1c74374cf71dc6cb773c9" -dependencies = [ - "borsh 1.5.1", - "bytemuck", - "solana-program", - "solana-zk-token-sdk", - "spl-program-error 0.4.1", + "spl-program-error", ] [[package]] @@ -8270,20 +8233,7 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "solana-program", - "spl-program-error-derive 0.3.2", - "thiserror", -] - -[[package]] -name = "spl-program-error" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49065093ea91f57b9b2bd81493ff705e2ad4e64507a07dbc02b085778e02770e" -dependencies = [ - "num-derive 0.4.2", - "num-traits", - "solana-program", - "spl-program-error-derive 0.4.1", + "spl-program-error-derive", "thiserror", ] @@ -8299,18 +8249,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "spl-program-error-derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d375dd76c517836353e093c2dbb490938ff72821ab568b545fd30ab3256b3e" -dependencies = [ - "proc-macro2", - "quote", - "sha2 0.10.8", - "syn 2.0.58", -] - [[package]] name = "spl-tlv-account-resolution" version = "0.4.0" @@ -8319,10 +8257,10 @@ checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", - "spl-type-length-value 0.3.1", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", ] [[package]] @@ -8333,24 +8271,10 @@ checksum = "56f335787add7fa711819f9e7c573f8145a5358a709446fe2d24bf2a88117c90" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", - "spl-type-length-value 0.3.1", -] - -[[package]] -name = "spl-tlv-account-resolution" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cace91ba08984a41556efe49cbf2edca4db2f577b649da7827d3621161784bf8" -dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator 0.2.2", - "spl-pod 0.2.2", - "spl-program-error 0.4.1", - "spl-type-length-value 0.4.3", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", ] [[package]] @@ -8385,31 +8309,27 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.9.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" dependencies = [ "arrayref", "bytemuck", - "num-derive 0.4.2", + "num-derive 0.3.3", "num-traits", - "num_enum 0.7.2", + "num_enum 0.5.10", "solana-program", "solana-zk-token-sdk", - "spl-memo", - "spl-pod 0.1.1", - "spl-token 4.0.0", - "spl-token-metadata-interface 0.2.0", - "spl-transfer-hook-interface 0.3.0", - "spl-type-length-value 0.3.1", + "spl-memo 3.0.1", + "spl-token 3.5.0", "thiserror", ] [[package]] name = "spl-token-2022" -version = "1.0.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" dependencies = [ "arrayref", "bytemuck", @@ -8417,23 +8337,21 @@ dependencies = [ "num-traits", "num_enum 0.7.2", "solana-program", - "solana-security-txt", "solana-zk-token-sdk", - "spl-memo", - "spl-pod 0.1.1", + "spl-memo 4.0.0", + "spl-pod", "spl-token 4.0.0", - "spl-token-group-interface 0.1.0", - "spl-token-metadata-interface 0.2.0", - "spl-transfer-hook-interface 0.4.1", - "spl-type-length-value 0.3.1", + "spl-token-metadata-interface", + "spl-transfer-hook-interface 0.3.0", + "spl-type-length-value", "thiserror", ] [[package]] name = "spl-token-2022" -version = "3.0.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5412f99ae7ee6e0afde00defaa354e6228e47e30c0e3adf553e2e01e6abb584" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", @@ -8443,13 +8361,13 @@ dependencies = [ "solana-program", "solana-security-txt", "solana-zk-token-sdk", - "spl-memo", - "spl-pod 0.2.2", + "spl-memo 4.0.0", + "spl-pod", "spl-token 4.0.0", - "spl-token-group-interface 0.2.3", - "spl-token-metadata-interface 0.3.3", - "spl-transfer-hook-interface 0.6.3", - "spl-type-length-value 0.4.3", + "spl-token-group-interface", + "spl-token-metadata-interface", + "spl-transfer-hook-interface 0.4.1", + "spl-type-length-value", "thiserror", ] @@ -8461,22 +8379,9 @@ checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", -] - -[[package]] -name = "spl-token-group-interface" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419b5cfa3ee8e0f2386fd7e02a33b3ec8a7db4a9c7064a2ea24849dc4a273b6" -dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator 0.2.2", - "spl-pod 0.2.2", - "spl-program-error 0.4.1", + "spl-discriminator", + "spl-pod", + "spl-program-error", ] [[package]] @@ -8487,24 +8392,10 @@ checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" dependencies = [ "borsh 0.10.3", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", - "spl-type-length-value 0.3.1", -] - -[[package]] -name = "spl-token-metadata-interface" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30179c47e93625680dabb620c6e7931bd12d62af390f447bc7beb4a3a9b5feee" -dependencies = [ - "borsh 1.5.1", - "solana-program", - "spl-discriminator 0.2.2", - "spl-pod 0.2.2", - "spl-program-error 0.4.1", - "spl-type-length-value 0.4.3", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", ] [[package]] @@ -8516,11 +8407,11 @@ dependencies = [ "arrayref", "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", + "spl-discriminator", + "spl-pod", + "spl-program-error", "spl-tlv-account-resolution 0.4.0", - "spl-type-length-value 0.3.1", + "spl-type-length-value", ] [[package]] @@ -8532,27 +8423,11 @@ dependencies = [ "arrayref", "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", + "spl-discriminator", + "spl-pod", + "spl-program-error", "spl-tlv-account-resolution 0.5.2", - "spl-type-length-value 0.3.1", -] - -[[package]] -name = "spl-transfer-hook-interface" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a98359769cd988f7b35c02558daa56d496a7e3bd8626e61f90a7c757eedb9b" -dependencies = [ - "arrayref", - "bytemuck", - "solana-program", - "spl-discriminator 0.2.2", - "spl-pod 0.2.2", - "spl-program-error 0.4.1", - "spl-tlv-account-resolution 0.6.3", - "spl-type-length-value 0.4.3", + "spl-type-length-value", ] [[package]] @@ -8563,22 +8438,9 @@ checksum = "8f9ebd75d29c5f48de5f6a9c114e08531030b75b8ac2c557600ac7da0b73b1e8" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator 0.1.1", - "spl-pod 0.1.1", - "spl-program-error 0.3.1", -] - -[[package]] -name = "spl-type-length-value" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ce13429dbd41d2cee8a73931c05fda0b0c8ca156a8b0c19445642550bb61a" -dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator 0.2.2", - "spl-pod 0.2.2", - "spl-program-error 0.4.1", + "spl-discriminator", + "spl-pod", + "spl-program-error", ] [[package]] @@ -9272,7 +9134,7 @@ dependencies = [ [[package]] name = "treasury-management" version = "0.2.0" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", @@ -9520,7 +9382,7 @@ dependencies = [ [[package]] name = "voter-stake-registry" version = "0.3.3" -source = "git+https://github.com/helium/helium-anchor-gen.git#a7e694d49245ca9830bcfef68cce34f230c91011" +source = "git+https://github.com/helium/helium-anchor-gen.git#3036b33793cfe54b20ab24761677493510d5bd50" dependencies = [ "anchor-gen", "anchor-lang 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index 7388ff0ad..3e977c035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,14 +70,13 @@ helium-lib = { git = "https://github.com/helium/helium-wallet-rs.git", branch = hextree = { git = "https://github.com/jaykickliter/HexTree", branch = "main", features = [ "disktree", ] } -helium-proto = { git = "https://github.com/helium/proto", branch = "master", features = [ - "services", -] } +helium-proto = { git = "https://github.com/helium/proto", branch = "master", features = ["services"] } beacon = { git = "https://github.com/helium/proto", branch = "master" } solana-client = "1.18" solana-sdk = "1.18" solana-program = "1.18" spl-token = "3.5.0" +spl-associated-token-account = "1.1.1" reqwest = { version = "0", default-features = false, features = [ "gzip", "json", @@ -128,10 +127,10 @@ sqlx = { git = "https://github.com/launchbadge/sqlx.git", rev = "42dd78fe931df65 # When attempting to test proto changes without needing to push a branch you can # patch the github url to point to your local proto repo. -# +# # Patching for beacon must point directly to the crate, it will not look in the # repo for sibling crates. -# -# [patch.'https://github.com/helium/proto'] +# +#[patch.'https://github.com/helium/proto'] # helium-proto = { path = "../proto" } -# beacon = { path = "../proto/beacon" } +# beacon = { path = "../proto/beacon" } \ No newline at end of file diff --git a/file_store/src/traits/msg_verify.rs b/file_store/src/traits/msg_verify.rs index 4cbdbcc03..a3e4bdeeb 100644 --- a/file_store/src/traits/msg_verify.rs +++ b/file_store/src/traits/msg_verify.rs @@ -43,15 +43,14 @@ impl_msg_verify!(LoraStreamSessionInitV1, signature); impl_msg_verify!(DataTransferSessionReqV1, signature); impl_msg_verify!(CoverageObjectReqV1, signature); impl_msg_verify!(ServiceProviderBoostedRewardsBannedRadioReqV1, signature); -impl_msg_verify!(iot_config::OrgCreateHeliumReqV1, signature); -impl_msg_verify!(iot_config::OrgCreateRoamerReqV1, signature); -impl_msg_verify!(iot_config::OrgUpdateReqV1, signature); impl_msg_verify!(iot_config::OrgDisableReqV1, signature); impl_msg_verify!(iot_config::OrgEnableReqV1, signature); impl_msg_verify!(iot_config::OrgDisableResV1, signature); impl_msg_verify!(iot_config::OrgEnableResV1, signature); impl_msg_verify!(iot_config::OrgResV1, signature); impl_msg_verify!(iot_config::OrgListResV1, signature); +impl_msg_verify!(iot_config::OrgResV2, signature); +impl_msg_verify!(iot_config::OrgListResV2, signature); impl_msg_verify!(iot_config::RouteStreamReqV1, signature); impl_msg_verify!(iot_config::RouteListReqV1, signature); impl_msg_verify!(iot_config::RouteGetReqV1, signature); diff --git a/iot_config/Cargo.toml b/iot_config/Cargo.toml index cce124e9f..4498837aa 100644 --- a/iot_config/Cargo.toml +++ b/iot_config/Cargo.toml @@ -18,8 +18,9 @@ db-store = { path = "../db_store" } file-store = { path = "../file_store" } futures = { workspace = true } futures-util = { workspace = true } -helium-crypto = { workspace = true, features = ["sqlx-postgres"] } +helium-crypto = { workspace = true, features = ["sqlx-postgres", "solana"] } helium-proto = { workspace = true } +helium-anchor-gen = { workspace = true } hextree = { workspace = true } http = { workspace = true } http-serde = { workspace = true } @@ -44,6 +45,9 @@ triggered = { workspace = true } task-manager = { path = "../task_manager" } humantime-serde = { workspace = true } custom-tracing = { path = "../custom_tracing", features = ["grpc"] } +solana = { path = "../solana" } +solana-sdk = { workspace = true } +rust_decimal = { workspace = true } [dev-dependencies] rand = { workspace = true } diff --git a/iot_config/migrations/20241203190903_solana_net_ids.sql b/iot_config/migrations/20241203190903_solana_net_ids.sql new file mode 100644 index 000000000..3ba1d229b --- /dev/null +++ b/iot_config/migrations/20241203190903_solana_net_ids.sql @@ -0,0 +1,9 @@ +-- Migration here solely for testing purposes +-- An instance of account-postgres-sink +-- Will alter this table depending on an on-chain struct +CREATE TABLE IF NOT EXISTS solana_net_ids ( + address TEXT PRIMARY KEY, + id INTEGER NOT NULL, + authority TEXT NOT NULL, + current_addr_offset NUMERIC NOT NULL +); \ No newline at end of file diff --git a/iot_config/migrations/20241203190910_solana_organizations.sql b/iot_config/migrations/20241203190910_solana_organizations.sql new file mode 100644 index 000000000..b63c0a78d --- /dev/null +++ b/iot_config/migrations/20241203190910_solana_organizations.sql @@ -0,0 +1,39 @@ +-- Migration here solely for testing purposes +-- An instance of account-postgres-sink +-- Will alter this table depending on an on-chain struct +CREATE TABLE IF NOT EXISTS solana_organizations ( + address TEXT PRIMARY KEY, + net_id TEXT NOT NULL, + authority TEXT NOT NULL, + oui BIGINT NOT NULL, + escrow_key TEXT NOT NULL, + approved BOOLEAN NOT NULL +); + +CREATE OR REPLACE FUNCTION delete_routes_on_solana_organizations_delete() RETURNS trigger AS $$ +BEGIN + DELETE FROM routes WHERE routes.oui = OLD.oui; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER delete_routes_on_solana_organizations_delete +AFTER DELETE ON solana_organizations +FOR EACH ROW +EXECUTE FUNCTION delete_routes_on_solana_organizations_delete(); + +CREATE OR REPLACE FUNCTION add_lock_record_on_solana_organizations_insert() RETURNS trigger AS $$ +BEGIN + INSERT INTO organization_locks (organization, locked) + SELECT sol_org.address, COALESCE(org.locked, TRUE) + FROM solana_organizations sol_org + LEFT JOIN organizations org ON sol_org.oui = org.oui + WHERE sol_org.address = NEW.address; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER add_lock_record_on_solana_organizations_insert +AFTER INSERT ON solana_organizations +FOR EACH ROW +EXECUTE FUNCTION add_lock_record_on_solana_organizations_insert(); \ No newline at end of file diff --git a/iot_config/migrations/20241203190923_solana_organization_devaddr_constraints.sql b/iot_config/migrations/20241203190923_solana_organization_devaddr_constraints.sql new file mode 100644 index 000000000..8256e12d9 --- /dev/null +++ b/iot_config/migrations/20241203190923_solana_organization_devaddr_constraints.sql @@ -0,0 +1,10 @@ +-- Migration here solely for testing purposes +-- An instance of account-postgres-sink +-- Will alter this table depending on an on-chain struct +CREATE TABLE IF NOT EXISTS solana_organization_devaddr_constraints ( + address TEXT PRIMARY KEY, + net_id TEXT NOT NULL, + organization TEXT NOT NULL, + start_addr NUMERIC NOT NULL, + end_addr NUMERIC NOT NULL +) \ No newline at end of file diff --git a/iot_config/migrations/20241203190936_solana_organization_delegate_keys.sql b/iot_config/migrations/20241203190936_solana_organization_delegate_keys.sql new file mode 100644 index 000000000..5f73cd957 --- /dev/null +++ b/iot_config/migrations/20241203190936_solana_organization_delegate_keys.sql @@ -0,0 +1,8 @@ +-- Migration here solely for testing purposes +-- An instance of account-postgres-sink +-- Will alter this table depending on an on-chain struct +CREATE TABLE IF NOT EXISTS solana_organization_delegate_keys ( + address TEXT PRIMARY KEY, + organization TEXT NOT NULL, + delegate TEXT NOT NULL +); \ No newline at end of file diff --git a/iot_config/migrations/20241203190945_update_oui_references.sql b/iot_config/migrations/20241203190945_update_oui_references.sql new file mode 100644 index 000000000..5e22a646d --- /dev/null +++ b/iot_config/migrations/20241203190945_update_oui_references.sql @@ -0,0 +1 @@ +ALTER TABLE routes DROP CONSTRAINT IF EXISTS routes_oui_fkey; \ No newline at end of file diff --git a/iot_config/migrations/20241203193310_add_organization_locks.sql b/iot_config/migrations/20241203193310_add_organization_locks.sql new file mode 100644 index 000000000..eeb415255 --- /dev/null +++ b/iot_config/migrations/20241203193310_add_organization_locks.sql @@ -0,0 +1,22 @@ +create table organization_locks ( + organization TEXT PRIMARY KEY NOT NULL, + locked BOOL DEFAULT false, + inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +select trigger_updated_at('organization_locks'); + +DO $$ +BEGIN + IF EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'solana_organizations' + ) THEN + INSERT INTO organization_locks (organization, locked) + SELECT sol_org.address, org.locked + FROM solana_organizations sol_org + LEFT JOIN organizations org ON sol_org.oui = org.oui; + END IF; +END; +$$; diff --git a/iot_config/pkg/settings-template.toml b/iot_config/pkg/settings-template.toml index 7861c7779..e3c7ae69b 100644 --- a/iot_config/pkg/settings-template.toml +++ b/iot_config/pkg/settings-template.toml @@ -1,5 +1,5 @@ # log settings for the application (RUST_LOG format). Default below -# +# # log = "iot-config=debug,poc_store=info" diff --git a/iot_config/src/client/org_client.rs b/iot_config/src/client/org_client.rs index b0e41087e..a3a5a0667 100644 --- a/iot_config/src/client/org_client.rs +++ b/iot_config/src/client/org_client.rs @@ -6,15 +6,15 @@ use async_trait::async_trait; use chrono::Utc; use file_store::traits::TimestampEncode; use helium_proto::services::iot_config::{ - OrgDisableReqV1, OrgEnableReqV1, OrgGetReqV1, OrgListReqV1, OrgResV1, OrgV1, + OrgDisableReqV1, OrgEnableReqV1, OrgGetReqV2, OrgListReqV2, OrgResV2, OrgV2, }; #[async_trait] pub trait Orgs: Send + Sync + 'static { type Error: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static; - async fn get(&mut self, oui: u64) -> Result; - async fn list(&mut self) -> Result, Self::Error>; + async fn get(&mut self, oui: u64) -> Result; + async fn list(&mut self) -> Result, Self::Error>; async fn enable(&mut self, oui: u64) -> Result<(), Self::Error>; async fn disable(&mut self, oui: u64) -> Result<(), Self::Error>; } @@ -44,19 +44,19 @@ impl OrgClient { impl Orgs for OrgClient { type Error = ClientError; - async fn get(&mut self, oui: u64) -> Result { + async fn get(&mut self, oui: u64) -> Result { tracing::debug!(%oui, "retrieving org"); - let req = OrgGetReqV1 { oui }; + let req = OrgGetReqV2 { oui }; let res = call_with_retry!(self.client.get(req.clone()))?.into_inner(); res.verify(&self.config_pubkey)?; Ok(res) } - async fn list(&mut self) -> Result, ClientError> { + async fn list(&mut self) -> Result, ClientError> { tracing::debug!("retrieving org list"); - let res = call_with_retry!(self.client.list(OrgListReqV1 {}))?.into_inner(); + let res = call_with_retry!(self.client.list(OrgListReqV2 {}))?.into_inner(); res.verify(&self.config_pubkey)?; Ok(res.orgs) } diff --git a/iot_config/src/gateway_service.rs b/iot_config/src/gateway_service.rs index 78c5f67d5..e72a05f31 100644 --- a/iot_config/src/gateway_service.rs +++ b/iot_config/src/gateway_service.rs @@ -1,5 +1,6 @@ use crate::{ admin::AuthCache, + convert_to_solana_public_key, gateway_info::{self, GatewayInfo}, org, region_map::RegionMapReader, @@ -76,8 +77,9 @@ impl GatewayService { } fn verify_location_request(&self, request: &GatewayLocationReqV1) -> Result<(), Status> { - let signature_bytes = request.signer.clone(); - let signer_pubkey = verify_public_key(&signature_bytes)?; + let signer_pubkey = verify_public_key(&request.signer)?; + let solana_signer_pubkey = convert_to_solana_public_key(&signer_pubkey) + .map_err(|_| Status::internal("Failed to convert public key"))?; if self .auth_cache @@ -89,7 +91,7 @@ impl GatewayService { self.delegate_cache .borrow() - .contains(&signature_bytes.clone().into()) + .contains(&solana_signer_pubkey.into()) .then(|| { request .verify(&signer_pubkey) diff --git a/iot_config/src/helium_netids.rs b/iot_config/src/helium_netids.rs index 7b753a561..47a8fd996 100644 --- a/iot_config/src/helium_netids.rs +++ b/iot_config/src/helium_netids.rs @@ -1,13 +1,8 @@ -use crate::lora_field::{self, DevAddrConstraint, LoraField, NetIdField}; -use helium_proto::services::iot_config::org_create_helium_req_v1::HeliumNetId as ProtoNetId; -use std::{collections::HashSet, ops::RangeInclusive}; +use crate::lora_field::{LoraField, NetIdField}; const TYPE_0_ID: NetIdField = LoraField(0x00003c); const TYPE_3_ID: NetIdField = LoraField(0x60002d); const TYPE_6_ID: NetIdField = LoraField(0xc00053); -const TYPE_0_RANGE: RangeInclusive = 2_013_265_920..=2_046_820_351; -const TYPE_3_RANGE: RangeInclusive = 3_763_994_624..=3_764_125_695; -const TYPE_6_RANGE: RangeInclusive = 4_227_943_424..=4_227_944_447; #[derive(Clone, Copy)] pub enum HeliumNetId { @@ -16,24 +11,6 @@ pub enum HeliumNetId { Type6_0xc00053, } -impl HeliumNetId { - pub fn id(&self) -> NetIdField { - match *self { - HeliumNetId::Type0_0x00003c => TYPE_0_ID, - HeliumNetId::Type3_0x60002d => TYPE_3_ID, - HeliumNetId::Type6_0xc00053 => TYPE_6_ID, - } - } - - pub fn addr_range(&self) -> RangeInclusive { - match *self { - HeliumNetId::Type0_0x00003c => TYPE_0_RANGE, - HeliumNetId::Type3_0x60002d => TYPE_3_RANGE, - HeliumNetId::Type6_0xc00053 => TYPE_6_RANGE, - } - } -} - impl TryFrom for HeliumNetId { type Error = &'static str; @@ -47,408 +24,3 @@ impl TryFrom for HeliumNetId { Ok(id) } } - -#[async_trait::async_trait] -pub trait AddressStore { - type Error; - - async fn get_used_addrs(&mut self, net_id: HeliumNetId) -> Result, Self::Error>; - async fn claim_addrs( - &mut self, - net_id: HeliumNetId, - new_addrs: &[u32], - ) -> Result<(), Self::Error>; - async fn release_addrs( - &mut self, - net_id: HeliumNetId, - released_addrs: &[u32], - ) -> Result<(), Self::Error>; -} - -#[async_trait::async_trait] -impl AddressStore for sqlx::Transaction<'_, sqlx::Postgres> { - type Error = sqlx::Error; - - async fn get_used_addrs(&mut self, net_id: HeliumNetId) -> Result, Self::Error> { - Ok(sqlx::query_scalar::<_, i32>( - " select devaddr from helium_used_devaddrs where net_id = $1 order by devaddr asc ", - ) - .bind(i32::from(net_id.id())) - .fetch_all(self) - .await? - .into_iter() - .map(|addr| addr as u32) - .collect::>()) - } - - async fn claim_addrs( - &mut self, - net_id: HeliumNetId, - new_addrs: &[u32], - ) -> Result<(), Self::Error> { - let mut query_builder: sqlx::QueryBuilder = - sqlx::QueryBuilder::new(" insert into helium_used_devaddrs (devaddr, net_id) "); - query_builder.push_values(new_addrs, |mut builder, addr| { - builder - .push_bind(*addr as i32) - .push_bind(i32::from(net_id.id())); - }); - Ok(query_builder.build().execute(self).await.map(|_| ())?) - } - - async fn release_addrs( - &mut self, - net_id: HeliumNetId, - released_addrs: &[u32], - ) -> Result<(), Self::Error> { - let net_id = i32::from(net_id.id()); - let released_addrs = released_addrs - .iter() - .map(|addr| (*addr, net_id)) - .collect::>(); - let mut query_builder: sqlx::QueryBuilder = sqlx::QueryBuilder::new( - " delete from helium_used_devaddrs where (devaddr, net_id) in ", - ); - query_builder.push_tuples(released_addrs, |mut builder, (addr, id)| { - builder.push_bind(addr as i32).push_bind(id); - }); - Ok(query_builder.build().execute(self).await.map(|_| ())?) - } -} - -pub fn is_helium_netid(net_id: &NetIdField) -> bool { - [TYPE_0_ID, TYPE_3_ID, TYPE_6_ID].contains(net_id) -} - -pub async fn checkout_devaddr_constraints( - addr_store: &mut S, - count: u64, - net_id: HeliumNetId, -) -> Result, DevAddrConstraintsError> -where - S: AddressStore, -{ - let addr_range = net_id.addr_range(); - let used_addrs = addr_store - .get_used_addrs(net_id) - .await - .map_err(DevAddrConstraintsError::AddressStore)?; - - let range_start = *addr_range.start(); - let range_end = *addr_range.end(); - let last_used = used_addrs.last().copied().unwrap_or(range_start); - let used_range = (range_start..=last_used).collect::>(); - let used_addrs = used_addrs.into_iter().collect::>(); - - let mut available_diff = used_range - .difference(&used_addrs) - .copied() - .collect::>(); - available_diff.sort(); - - let mut claimed_addrs = available_diff - .drain(0..(count as usize).min(available_diff.len())) - .collect::>(); - - let mut next_addr = last_used + 1; - while claimed_addrs.len() < count as usize { - if next_addr <= range_end { - claimed_addrs.push(next_addr); - next_addr += 1 - } else { - return Err(DevAddrConstraintsError::NoAvailableAddrs); - } - } - - addr_store - .claim_addrs(net_id, &claimed_addrs) - .await - .map_err(DevAddrConstraintsError::AddressStore)?; - - let new_constraints = constraints_from_addrs(claimed_addrs)?; - Ok(new_constraints) -} - -pub async fn checkout_specified_devaddr_constraint( - addr_store: &mut S, - net_id: HeliumNetId, - requested_constraint: &DevAddrConstraint, -) -> Result<(), DevAddrConstraintsError> -where - S: AddressStore, -{ - let used_addrs = addr_store - .get_used_addrs(net_id) - .await - .map_err(DevAddrConstraintsError::AddressStore)?; - let request_addrs = (requested_constraint.start_addr.into() - ..=requested_constraint.end_addr.into()) - .collect::>(); - if request_addrs.iter().any(|&addr| used_addrs.contains(&addr)) { - return Err(DevAddrConstraintsError::ConstraintAddrInUse(format!( - "{request_addrs:?}" - ))); - }; - addr_store - .claim_addrs(net_id, &request_addrs) - .await - .map_err(DevAddrConstraintsError::AddressStore) -} - -#[derive(thiserror::Error, Debug)] -pub enum DevAddrConstraintsError { - #[error("AddressStore error: {0}")] - AddressStore(AS), - #[error("No devaddrs available for NetId")] - NoAvailableAddrs, - #[error("Error building constraint")] - InvalidConstraint(#[from] ConstraintsBuildError), - #[error("Requested constraint in use {0}")] - ConstraintAddrInUse(String), -} - -fn constraints_from_addrs( - addrs: Vec, -) -> Result, ConstraintsBuildError> { - let mut constraints = Vec::new(); - let mut start_addr: Option = None; - let mut end_addr: Option = None; - for addr in addrs { - match (start_addr, end_addr) { - (None, None) => start_addr = Some(addr), - (Some(_), None) => end_addr = Some(addr), - (Some(prev_addr), Some(next_addr)) => match addr { - addr if addr == next_addr + 1 => end_addr = Some(addr), - addr if addr > next_addr + 1 => { - constraints.push(DevAddrConstraint::new(prev_addr.into(), next_addr.into())?); - start_addr = Some(addr); - end_addr = None - } - _ => return Err(ConstraintsBuildError::EndAddr), - }, - _ => return Err(ConstraintsBuildError::StartAddr), - } - } - match (start_addr, end_addr) { - (Some(remaining_start), Some(remaining_end)) => constraints.push(DevAddrConstraint::new( - remaining_start.into(), - remaining_end.into(), - )?), - _ => return Err(ConstraintsBuildError::EndAddr), - } - Ok(constraints) -} - -#[derive(thiserror::Error, Debug)] -pub enum ConstraintsBuildError { - #[error("Constraint missing or invalid start addr")] - StartAddr, - #[error("Constraint missing or invalid end addr")] - EndAddr, - #[error("invalid constraint: {0}")] - InvalidConstraint(#[from] lora_field::DevAddrRangeError), -} - -impl From for HeliumNetId { - fn from(pni: ProtoNetId) -> Self { - match pni { - ProtoNetId::Type00x00003c => Self::Type0_0x00003c, - ProtoNetId::Type30x60002d => Self::Type3_0x60002d, - ProtoNetId::Type60xc00053 => Self::Type6_0xc00053, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::collections::HashMap; - - #[async_trait::async_trait] - impl AddressStore for HashMap> { - type Error = &'static str; - - async fn get_used_addrs(&mut self, net_id: HeliumNetId) -> Result, Self::Error> { - let mut result = self.get(&net_id.id()).cloned().unwrap_or_default(); - result.sort(); - Ok(result) - } - - async fn claim_addrs( - &mut self, - net_id: HeliumNetId, - new_addrs: &[u32], - ) -> Result<(), Self::Error> { - self.entry(net_id.id()) - .and_modify(|addrs| new_addrs.iter().for_each(|addr| addrs.push(*addr))) - .or_insert(new_addrs.to_vec()); - Ok(()) - } - - async fn release_addrs( - &mut self, - net_id: HeliumNetId, - released_addrs: &[u32], - ) -> Result<(), Self::Error> { - self.entry(net_id.id()) - .and_modify(|addrs| addrs.retain(|addr| !released_addrs.contains(addr))); - Ok(()) - } - } - - #[tokio::test] - async fn get_free_addrs_from_used_range() { - let mut addr_store = HashMap::new(); - addr_store.insert( - HeliumNetId::Type0_0x00003c.id(), - vec![ - 2013265920, 2013265921, 2013265922, 2013265923, 2013265928, 2013265929, 2013265930, - 2013265931, 2013265936, 2013265937, - ], - ); - let selected_constraints = - checkout_devaddr_constraints(&mut addr_store, 10, HeliumNetId::Type0_0x00003c) - .await - .expect("constraints selected from available addrs"); - let expected_constraints = vec![ - DevAddrConstraint::new(2013265924.into(), 2013265927.into()).expect("new constraint 1"), - DevAddrConstraint::new(2013265932.into(), 2013265935.into()).expect("new constraint 2"), - DevAddrConstraint::new(2013265938.into(), 2013265939.into()).expect("new constraint 3"), - ]; - assert_eq!(selected_constraints, expected_constraints); - addr_store - .entry(HeliumNetId::Type0_0x00003c.id()) - .and_modify(|addrs| addrs.sort()); - let used_addrs = addr_store - .get(&HeliumNetId::Type0_0x00003c.id()) - .cloned() - .unwrap(); - assert_eq!(used_addrs, (2013265920..=2013265939).collect::>()); - } - - #[tokio::test] - async fn get_free_addrs_from_new_range() { - let mut addr_store = HashMap::new(); - let selected_constraints = - checkout_devaddr_constraints(&mut addr_store, 10, HeliumNetId::Type0_0x00003c) - .await - .expect("constraints selected from available addrs"); - let expected_constraints = - vec![DevAddrConstraint::new(2013265920.into(), 2013265929.into()) - .expect("new constraint")]; - assert_eq!(selected_constraints, expected_constraints); - addr_store - .entry(HeliumNetId::Type0_0x00003c.id()) - .and_modify(|addrs| addrs.sort()); - let used_addrs = addr_store - .get(&HeliumNetId::Type0_0x00003c.id()) - .cloned() - .unwrap(); - assert_eq!(used_addrs, (2013265920..=2013265929).collect::>()); - } - - #[tokio::test] - async fn error_when_no_devaddrs_available() { - let mut addr_store = HashMap::new(); - addr_store.insert( - HeliumNetId::Type6_0xc00053.id(), - (4227943424..4227944443).collect::>(), - ); - assert!( - checkout_devaddr_constraints(&mut addr_store, 6, HeliumNetId::Type6_0xc00053) - .await - .is_err() - ); - } - - #[tokio::test] - async fn error_when_odd_number_addrs_requested() { - let mut addr_store = HashMap::new(); - assert!( - checkout_devaddr_constraints(&mut addr_store, 5, HeliumNetId::Type0_0x00003c) - .await - .is_err() - ); - } - - #[tokio::test] - async fn error_when_addrs_uneven() { - let mut addr_store = HashMap::new(); - addr_store.insert( - HeliumNetId::Type3_0x60002d.id(), - vec![ - 3763994627, 3763994628, 3763994629, 3763994630, 3763994631, 3763994632, - ], - ); - assert!( - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type3_0x60002d) - .await - .is_err() - ); - } - - #[tokio::test] - async fn allocate_fewer_than_existing_gap() { - let mut addr_store = HashMap::new(); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type0_0x00003c) - .await - .expect("allocate first round"); - checkout_devaddr_constraints(&mut addr_store, 32, HeliumNetId::Type0_0x00003c) - .await - .expect("allocate second round"); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type0_0x00003c) - .await - .expect("allocate third round"); - // round 2 goes out of business, and their devaddrs are released back to the wild - let remove: Vec = addr_store - .get(&HeliumNetId::Type0_0x00003c.id()) - .cloned() - .unwrap() - .into_iter() - .skip(8) - .take(32) - .collect(); - assert_eq!( - Ok(()), - addr_store - .release_addrs(HeliumNetId::Type0_0x00003c, &remove) - .await - ); - assert_eq!( - 8 + 8, - addr_store - .get(&HeliumNetId::Type0_0x00003c.id()) - .unwrap() - .len() - ); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type0_0x00003c) - .await - .expect("allocate fourth round"); - assert_eq!( - 8 + 8 + 8, - addr_store - .get(&HeliumNetId::Type0_0x00003c.id()) - .unwrap() - .len() - ); - } - - #[tokio::test] - async fn allocate_across_net_id() { - let mut addr_store = HashMap::new(); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type6_0xc00053) - .await - .expect("testing allocation"); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type3_0x60002d) - .await - .expect("special request allocation"); - checkout_devaddr_constraints(&mut addr_store, 8, HeliumNetId::Type0_0x00003c) - .await - .expect("average allocation"); - - assert_eq!( - 8 + 8 + 8, - addr_store.values().fold(0, |acc, elem| acc + elem.len()) - ); - } -} diff --git a/iot_config/src/lib.rs b/iot_config/src/lib.rs index fea359fea..0102a6d4e 100644 --- a/iot_config/src/lib.rs +++ b/iot_config/src/lib.rs @@ -22,6 +22,7 @@ pub use route_service::RouteService; pub use settings::Settings; use helium_crypto::PublicKey; +use solana_sdk::pubkey::Pubkey; use tokio::sync::broadcast; use tokio_stream::wrappers::ReceiverStream; use tonic::{Response, Status}; @@ -60,3 +61,13 @@ pub fn verify_public_key(bytes: &[u8]) -> Result { PublicKey::try_from(bytes) .map_err(|_| Status::invalid_argument(format!("invalid public key: {bytes:?}"))) } + +pub fn convert_to_solana_public_key(pubkey: &PublicKey) -> Result { + Pubkey::try_from(pubkey.clone()) +} + +pub fn convert_to_helium_public_key( + pubkey: &Pubkey, +) -> Result { + PublicKey::try_from(pubkey.clone()) +} diff --git a/iot_config/src/lora_field.rs b/iot_config/src/lora_field.rs index 0a4c8494a..4fdfb8971 100644 --- a/iot_config/src/lora_field.rs +++ b/iot_config/src/lora_field.rs @@ -11,7 +11,7 @@ pub type EuiField = LoraField<16>; pub mod proto { pub use helium_proto::services::iot_config::{ - DevaddrConstraintV1, DevaddrRangeV1, EuiPairV1, OrgV1, SkfV1, + DevaddrConstraintV1, DevaddrRangeV1, EuiPairV1, OrgV2, SkfV1, }; } diff --git a/iot_config/src/org.rs b/iot_config/src/org.rs index d3c6cd603..582833605 100644 --- a/iot_config/src/org.rs +++ b/iot_config/src/org.rs @@ -1,478 +1,167 @@ -use crate::{ - helium_netids::{self, is_helium_netid, AddressStore, HeliumNetId}, - lora_field::{DevAddrConstraint, DevAddrField, NetIdField}, - org_service::UpdateAuthorizer, -}; +use crate::lora_field::{DevAddrConstraint, NetIdField}; use futures::stream::StreamExt; -use helium_crypto::{PublicKey, PublicKeyBinary}; +use rust_decimal::{prelude::ToPrimitive, Decimal}; use serde::Serialize; -use sqlx::{postgres::PgRow, types::Uuid, FromRow, Row}; +use solana_sdk::pubkey::Pubkey; +use sqlx::{error::Error as SqlxError, postgres::PgRow, types::Uuid, FromRow, Row}; use std::collections::HashSet; +use std::str::FromStr; use tokio::sync::watch; pub mod proto { - pub use helium_proto::services::iot_config::{ - org_update_req_v1::update_v1::Update, org_update_req_v1::UpdateV1, ActionV1, OrgResV1, - OrgV1, - }; + pub use helium_proto::services::iot_config::{ActionV1, OrgResV2, OrgV2}; } #[derive(Clone, Debug, Serialize)] pub struct Org { pub oui: u64, - pub owner: PublicKeyBinary, - pub payer: PublicKeyBinary, + pub address: Pubkey, + pub owner: Pubkey, + pub escrow_key: String, + pub approved: bool, pub locked: bool, - pub delegate_keys: Option>, + pub delegate_keys: Option>, pub constraints: Option>, } impl FromRow<'_, PgRow> for Org { fn from_row(row: &PgRow) -> sqlx::Result { - let delegate_keys = row - .get::, &str>("delegate_keys") - .into_iter() - .map(Some) - .collect(); - let constraints = row - .get::, &str>("constraints") - .into_iter() - .map(|(start, end)| { - Some(DevAddrConstraint { - start_addr: start.into(), - end_addr: end.into(), + let address_str: String = row.get("address"); + let address = Pubkey::from_str(&address_str).map_err(|e| SqlxError::Decode(Box::new(e)))?; + let approved = row.get::("approved"); + let oui = row.try_get::("oui")? as u64; + let owner_str: String = row.get("authority"); + let owner = Pubkey::from_str(&owner_str).map_err(|e| SqlxError::Decode(Box::new(e)))?; + let escrow_key = row.get::("escrow_key"); + let locked = row.get::("locked"); + let raw_delegate_keys: Option> = row.try_get("delegate_keys")?; + let delegate_keys: Option> = raw_delegate_keys.map(|keys| { + keys.into_iter() + .filter_map(|key| Pubkey::from_str(&key).ok()) + .collect() + }); + + let raw_constraints: Option> = row.try_get("constraints")?; + let constraints: Option> = if let Some(constraints_data) = + raw_constraints + { + let constraints_result: Result, SqlxError> = constraints_data + .into_iter() + .map(|(start_decimal, end_decimal)| { + let start_u64 = start_decimal.to_u64().ok_or_else(|| { + SqlxError::Decode(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Failed to convert NUMERIC 'start_addr' to u64", + ))) + })?; + + let end_u64 = end_decimal.to_u64().ok_or_else(|| { + SqlxError::Decode(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Failed to convert NUMERIC 'end_addr' to u64", + ))) + })?; + + Ok(DevAddrConstraint { + start_addr: start_u64.into(), + end_addr: end_u64.into(), + }) }) - }) - .collect(); + .collect(); + + Some(constraints_result?) + } else { + None + }; + Ok(Self { - oui: row.get::("oui") as u64, - owner: row.get("owner_pubkey"), - payer: row.get("payer_pubkey"), - locked: row.get("locked"), + oui, + address, + owner, + escrow_key, + approved, + locked, delegate_keys, constraints, }) } } -pub type DelegateCache = HashSet; +pub type DelegateCache = HashSet; pub async fn delegate_keys_cache( db: impl sqlx::PgExecutor<'_>, ) -> Result<(watch::Sender, watch::Receiver), sqlx::Error> { - let key_set = sqlx::query(r#" select delegate_pubkey from organization_delegate_keys "#) - .fetch(db) - .filter_map(|row| async move { row.ok() }) - .map(|row| row.get("delegate_pubkey")) - .collect::>() - .await; - - Ok(watch::channel(key_set)) -} - -pub async fn create_org( - owner: PublicKeyBinary, - payer: PublicKeyBinary, - delegate_keys: Vec, - net_id: NetIdField, - devaddr_ranges: &[DevAddrConstraint], - db: impl sqlx::PgExecutor<'_> + sqlx::Acquire<'_, Database = sqlx::Postgres>, -) -> Result { - let mut txn = db.begin().await?; - - let oui = sqlx::query( - r#" - insert into organizations (owner_pubkey, payer_pubkey) - values ($1, $2) - returning oui - "#, + let key_set: HashSet = sqlx::query_scalar( + "SELECT delegate FROM solana_organization_delegate_keys WHERE delegate IS NOT NULL", ) - .bind(&owner) - .bind(&payer) - .fetch_one(&mut txn) - .await - .map_err(|_| { - OrgStoreError::SaveOrg(format!("owner: {owner}, payer: {payer}, net_id: {net_id}")) - })? - .get::("oui"); - - if !delegate_keys.is_empty() { - let delegate_keys = delegate_keys - .into_iter() - .map(|key| (key, oui)) - .collect::>(); - let mut query_builder: sqlx::QueryBuilder = sqlx::QueryBuilder::new( - " insert into organization_delegate_keys (delegate_pubkey, oui) ", - ); - query_builder.push_values(delegate_keys, |mut builder, (key, oui)| { - builder.push_bind(key).push_bind(oui); - }); - query_builder - .build() - .execute(&mut txn) - .await - .map_err(|_| { - OrgStoreError::SaveDelegates(format!( - "owner: {owner}, payer: {payer}, net_id: {net_id}" - )) - }) - .map(|_| ())? - }; - - if is_helium_netid(&net_id) { - insert_helium_constraints(oui as u64, net_id, devaddr_ranges, &mut txn).await - } else { - let constraint = devaddr_ranges - .first() - .ok_or(OrgStoreError::SaveConstraints( - "no devaddr constraints supplied".to_string(), - ))?; - if check_roamer_constraint_count(net_id, &mut txn).await? == 0 { - insert_roamer_constraint(oui as u64, net_id, constraint, &mut txn).await - } else { - return Err(OrgStoreError::SaveConstraints(format!( - "constraint already in use {constraint:?}" - ))); - } - } - .map_err(|err| OrgStoreError::SaveConstraints(format!("{devaddr_ranges:?}: {err:?}")))?; - - let org = get(oui as u64, &mut txn) - .await? - .ok_or_else(|| OrgStoreError::SaveOrg(format!("{oui}")))?; - - txn.commit().await?; - - Ok(org) -} - -pub async fn update_org( - oui: u64, - authorizer: UpdateAuthorizer, - updates: Vec, - db: impl sqlx::PgExecutor<'_> + sqlx::Acquire<'_, Database = sqlx::Postgres>, - delegate_cache: &watch::Sender, -) -> Result { - let mut txn = db.begin().await?; - - let current_org = get(oui, &mut txn) - .await? - .ok_or_else(|| OrgStoreError::NotFound(format!("{oui}")))?; - let net_id = get_org_netid(oui, &mut txn).await?; - let is_helium_org = is_helium_netid(&net_id); - - for update in updates.iter() { - match update.update { - Some(proto::Update::Owner(ref pubkeybin)) if authorizer == UpdateAuthorizer::Admin => { - let pubkeybin: PublicKeyBinary = pubkeybin.clone().into(); - update_owner(oui, &pubkeybin, &mut txn).await?; - tracing::info!(oui, pubkey = %pubkeybin, "owner pubkey updated"); - } - Some(proto::Update::Payer(ref pubkeybin)) if authorizer == UpdateAuthorizer::Admin => { - let pubkeybin: PublicKeyBinary = pubkeybin.clone().into(); - update_payer(oui, &pubkeybin, &mut txn).await?; - tracing::info!(oui, pubkey = %pubkeybin, "payer pubkey updated"); - } - Some(proto::Update::Devaddrs(addr_count)) - if authorizer == UpdateAuthorizer::Admin && is_helium_org => - { - add_devaddr_slab(oui, net_id, addr_count, &mut txn).await?; - tracing::info!(oui, addrs = addr_count, "new devaddr slab assigned"); - } - Some(proto::Update::Constraint(ref constraint_update)) - if authorizer == UpdateAuthorizer::Admin && is_helium_org => - { - match (constraint_update.action(), &constraint_update.constraint) { - (proto::ActionV1::Add, Some(ref constraint)) => { - let constraint: DevAddrConstraint = constraint.into(); - add_constraint_update(oui, net_id, constraint.clone(), &mut txn).await?; - tracing::info!(oui, %net_id, ?constraint, "devaddr constraint added"); - } - (proto::ActionV1::Remove, Some(ref constraint)) => { - let constraint: DevAddrConstraint = constraint.into(); - remove_constraint_update(oui, net_id, current_org.constraints.as_ref(), constraint.clone(), &mut txn).await?; - tracing::info!(oui, %net_id, ?constraint, "devaddr constraint removed"); - } - _ => return Err(OrgStoreError::InvalidUpdate(format!("invalid action or missing devaddr constraint update: {constraint_update:?}"))) - } - } - Some(proto::Update::DelegateKey(ref delegate_key_update)) => { - match delegate_key_update.action() { - proto::ActionV1::Add => { - let delegate = delegate_key_update.delegate_key.clone().into(); - add_delegate_key(oui, &delegate, &mut txn).await?; - tracing::info!(oui, %delegate, "delegate key authorized"); - } - proto::ActionV1::Remove => { - let delegate = delegate_key_update.delegate_key.clone().into(); - remove_delegate_key(oui, &delegate, &mut txn).await?; - tracing::info!(oui, %delegate, "delegate key de-authorized"); - } - } - } - _ => { - return Err(OrgStoreError::InvalidUpdate(format!( - "update: {update:?}, authorizer: {authorizer:?}" - ))) - } - }; - } - - let updated_org = get(oui, &mut txn) - .await? - .ok_or_else(|| OrgStoreError::SaveOrg(format!("{oui}")))?; - - txn.commit().await?; - - for update in updates.iter() { - if let Some(proto::Update::DelegateKey(ref delegate_key_update)) = update.update { - match delegate_key_update.action() { - proto::ActionV1::Add => { - delegate_cache.send_if_modified(|cache| { - cache.insert(delegate_key_update.delegate_key.clone().into()) - }); - } - proto::ActionV1::Remove => { - delegate_cache.send_if_modified(|cache| { - cache.remove(&delegate_key_update.delegate_key.clone().into()) - }); - } - } - } - } + .fetch_all(db) + .await? + .into_iter() + .filter_map(|delegate: String| Pubkey::from_str(&delegate).ok()) + .collect(); - Ok(updated_org) + Ok(watch::channel(key_set)) } pub async fn get_org_netid( oui: u64, db: impl sqlx::PgExecutor<'_>, ) -> Result { - let netid = sqlx::query_scalar::<_, i32>( - " select net_id from organization_devaddr_constraints where oui = $1 limit 1 ", - ) - .bind(oui as i64) - .fetch_one(db) - .await?; - Ok(netid.into()) -} - -async fn update_owner( - oui: u64, - owner_pubkey: &PublicKeyBinary, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), sqlx::Error> { - sqlx::query(" update organizations set owner_pubkey = $1 where oui = $2 ") - .bind(owner_pubkey) - .bind(oui as i64) - .execute(db) - .await - .map(|_| ()) -} - -async fn update_payer( - oui: u64, - payer_pubkey: &PublicKeyBinary, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), sqlx::Error> { - sqlx::query(" update organizations set payer_pubkey = $1 where oui = $2 ") - .bind(payer_pubkey) - .bind(oui as i64) - .execute(db) - .await - .map(|_| ()) -} - -async fn add_delegate_key( - oui: u64, - delegate_pubkey: &PublicKeyBinary, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), sqlx::Error> { - sqlx::query(" insert into organization_delegate_keys (delegate_pubkey, oui) values ($1, $2) ") - .bind(delegate_pubkey) - .bind(oui as i64) - .execute(db) - .await - .map(|_| ()) -} - -async fn remove_delegate_key( - oui: u64, - delegate_pubkey: &PublicKeyBinary, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), sqlx::Error> { - sqlx::query(" delete from organization_delegate_keys where delegate_pubkey = $1 and oui = $2 ") - .bind(delegate_pubkey) - .bind(oui as i64) - .execute(db) - .await - .map(|_| ()) -} - -async fn add_constraint_update( - oui: u64, - net_id: NetIdField, - added_constraint: DevAddrConstraint, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), OrgStoreError> { - let helium_net_id: HeliumNetId = net_id - .try_into() - .map_err(|err: &'static str| OrgStoreError::InvalidUpdate(err.to_string()))?; - helium_netids::checkout_specified_devaddr_constraint(db, helium_net_id, &added_constraint) - .await - .map_err(|err| OrgStoreError::InvalidUpdate(format!("{err:?}")))?; - insert_helium_constraints(oui, net_id, &[added_constraint], db).await?; - Ok(()) -} - -async fn remove_constraint_update( - oui: u64, - net_id: NetIdField, - org_constraints: Option<&Vec>, - removed_constraint: DevAddrConstraint, - db: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), OrgStoreError> { - let helium_net_id: HeliumNetId = net_id - .try_into() - .map_err(|err: &'static str| OrgStoreError::InvalidUpdate(err.to_string()))?; - if let Some(org_constraints) = org_constraints { - if org_constraints.contains(&removed_constraint) && org_constraints.len() > 1 { - let remove_range = (u32::from(removed_constraint.start_addr) - ..=u32::from(removed_constraint.end_addr)) - .collect::>(); - db.release_addrs(helium_net_id, &remove_range).await?; - remove_helium_constraints(oui, &[removed_constraint], db).await?; - Ok(()) - } else if org_constraints.len() == 1 { - return Err(OrgStoreError::InvalidUpdate( - "org must have at least one constraint range".to_string(), - )); - } else { - return Err(OrgStoreError::InvalidUpdate( - "cannot remove constraint leased by other org".to_string(), - )); - } - } else { - Err(OrgStoreError::InvalidUpdate( - "no org constraints defined".to_string(), - )) - } -} - -async fn add_devaddr_slab( - oui: u64, - net_id: NetIdField, - addr_count: u64, - txn: &mut sqlx::Transaction<'_, sqlx::Postgres>, -) -> Result<(), OrgStoreError> { - let helium_net_id: HeliumNetId = net_id - .try_into() - .map_err(|err: &'static str| OrgStoreError::InvalidUpdate(err.to_string()))?; - let constraints = helium_netids::checkout_devaddr_constraints(txn, addr_count, helium_net_id) - .await - .map_err(|err| OrgStoreError::SaveConstraints(format!("{err:?}")))?; - insert_helium_constraints(oui, net_id, &constraints, txn).await?; - Ok(()) -} - -async fn insert_helium_constraints( - oui: u64, - net_id: NetIdField, - devaddr_ranges: &[DevAddrConstraint], - db: impl sqlx::PgExecutor<'_>, -) -> Result<(), sqlx::Error> { - let mut query_builder: sqlx::QueryBuilder = sqlx::QueryBuilder::new( + let netid = sqlx::query_scalar::<_, i64>( r#" - insert into organization_devaddr_constraints (oui, net_id, start_addr, end_addr) + SELECT sol_ni.id::bigint + FROM solana_organizations sol_org + JOIN solana_net_ids sol_ni ON sol_ni.address = sol_org.net_id + WHERE sol_org.oui = $1 + LIMIT 1 "#, - ); - query_builder.push_values(devaddr_ranges, |mut builder, range| { - builder - .push_bind(oui as i64) - .push_bind(i32::from(net_id)) - .push_bind(i32::from(range.start_addr)) - .push_bind(i32::from(range.end_addr)); - }); - - query_builder.build().execute(db).await.map(|_| ()) -} - -async fn remove_helium_constraints( - oui: u64, - devaddr_ranges: &[DevAddrConstraint], - db: impl sqlx::PgExecutor<'_>, -) -> Result<(), sqlx::Error> { - let constraints = devaddr_ranges - .iter() - .map(|constraint| (oui, constraint.start_addr, constraint.end_addr)) - .collect::>(); - let mut query_builder: sqlx::QueryBuilder = sqlx::QueryBuilder::new( - "delete from organization_devaddr_constraints where (oui, start_addr, end_addr) in ", - ); - query_builder.push_tuples(constraints, |mut builder, (oui, start_addr, end_addr)| { - builder - .push_bind(oui as i64) - .push_bind(i32::from(start_addr)) - .push_bind(i32::from(end_addr)); - }); - - query_builder.build().execute(db).await.map(|_| ()) -} - -async fn check_roamer_constraint_count( - net_id: NetIdField, - db: impl sqlx::PgExecutor<'_>, -) -> Result { - sqlx::query_scalar( - " select count(net_id) from organization_devaddr_constraints where net_id = $1 ", ) - .bind(i32::from(net_id)) + .bind(Decimal::from(oui)) .fetch_one(db) - .await -} + .await?; -async fn insert_roamer_constraint( - oui: u64, - net_id: NetIdField, - devaddr_range: &DevAddrConstraint, - db: impl sqlx::PgExecutor<'_>, -) -> Result<(), sqlx::Error> { - sqlx::query( - r#" - insert into organization_devaddr_constraints (oui, net_id, start_addr, end_addr) - values ($1, $2, $3, $4) - "#, - ) - .bind(oui as i64) - .bind(i32::from(net_id)) - .bind(i32::from(devaddr_range.start_addr)) - .bind(i32::from(devaddr_range.end_addr)) - .execute(db) - .await - .map(|_| ()) + Ok(netid.into()) } const GET_ORG_SQL: &str = r#" - select org.oui, org.owner_pubkey, org.payer_pubkey, org.locked, - array(select (start_addr, end_addr) from organization_devaddr_constraints org_const where org_const.oui = org.oui) as constraints, - array(select delegate_pubkey from organization_delegate_keys org_delegates where org_delegates.oui = org.oui) as delegate_keys - from organizations org - "#; +SELECT + sol_org.oui::bigint, + sol_org.address, + sol_org.authority, + sol_org.escrow_key, + sol_org.approved, + COALESCE(ol.locked, true) AS locked, + ARRAY( + SELECT (start_addr, end_addr) + FROM solana_organization_devaddr_constraints + WHERE organization = sol_org.address + ) AS constraints, + ARRAY( + SELECT delegate + FROM solana_organization_delegate_keys + WHERE organization = sol_org.address + ) AS delegate_keys +FROM solana_organizations sol_org +LEFT JOIN organization_locks ol ON sol_org.address = ol.organization +"#; pub async fn list(db: impl sqlx::PgExecutor<'_>) -> Result, sqlx::Error> { - Ok(sqlx::query_as::<_, Org>(GET_ORG_SQL) + let orgs = sqlx::query_as::<_, Org>(GET_ORG_SQL) .fetch(db) .filter_map(|row| async move { row.ok() }) .collect::>() - .await) + .await; + + Ok(orgs) } pub async fn get(oui: u64, db: impl sqlx::PgExecutor<'_>) -> Result, sqlx::Error> { let mut query: sqlx::QueryBuilder = sqlx::QueryBuilder::new(GET_ORG_SQL); - query.push(" where org.oui = $1 "); + query.push(" where sol_org.oui = $1 "); query .build_query_as::() - .bind(oui as i64) + .bind(Decimal::from(oui)) .fetch_optional(db) .await } @@ -482,23 +171,38 @@ pub async fn get_constraints_by_route( db: impl sqlx::PgExecutor<'_>, ) -> Result, OrgStoreError> { let uuid = Uuid::try_parse(route_id)?; - let constraints = sqlx::query( r#" - select consts.start_addr, consts.end_addr from organization_devaddr_constraints consts - join routes on routes.oui = consts.oui - where routes.id = $1 + SELECT sol_odc.start_addr, sol_odc.end_addr + FROM solana_organization_devaddr_constraints sol_odc + JOIN solana_organizations sol_orgs ON sol_odc.organization = sol_orgs.address + JOIN routes ON routes.oui = sol_orgs.oui + WHERE routes.id = $1 "#, ) .bind(uuid) .fetch_all(db) .await? .into_iter() - .map(|row| DevAddrConstraint { - start_addr: row.get::("start_addr").into(), - end_addr: row.get::("end_addr").into(), + .map(|row| -> Result { + let start_decimal: Decimal = row.get("start_addr"); + let start_u64 = start_decimal.to_u64().ok_or_else(|| { + OrgStoreError::DecodeNumeric( + "Failed to convert NUMERIC 'start_addr' to u64".to_string(), + ) + })?; + + let end_decimal: Decimal = row.get("end_addr"); + let end_u64 = end_decimal.to_u64().ok_or_else(|| { + OrgStoreError::DecodeNumeric("Failed to convert NUMERIC 'end_addr' to u64".to_string()) + })?; + + Ok(DevAddrConstraint { + start_addr: start_u64.into(), + end_addr: end_u64.into(), + }) }) - .collect(); + .collect::, OrgStoreError>>()?; Ok(constraints) } @@ -511,12 +215,10 @@ pub async fn get_route_ids_by_route( let route_ids = sqlx::query( r#" - select routes.id from routes - where oui = ( - select organizations.oui from organizations - join routes on organizations.oui = routes.oui - where routes.id = $1 - ) + SELECT r2.id + FROM routes r1 + JOIN routes r2 ON r1.oui = r2.oui + WHERE r1.id = $1 "#, ) .bind(uuid) @@ -532,10 +234,13 @@ pub async fn get_route_ids_by_route( pub async fn is_locked(oui: u64, db: impl sqlx::PgExecutor<'_>) -> Result { sqlx::query_scalar::<_, bool>( r#" - select locked from organizations where oui = $1 + SELECT COALESCE(org_lock.locked, true) + FROM solana_organizations sol_org + LEFT JOIN organization_locks org_lock ON sol_org.address = org_lock.organization + WHERE sol_org.oui = $1 "#, ) - .bind(oui as i64) + .bind(Decimal::from(oui)) .fetch_one(db) .await } @@ -543,12 +248,16 @@ pub async fn is_locked(oui: u64, db: impl sqlx::PgExecutor<'_>) -> Result) -> Result<(), sqlx::Error> { sqlx::query( r#" - update organizations - set locked = not locked - where oui = $1 + INSERT INTO organization_locks (organization, locked) + SELECT address, NOT COALESCE(org_lock.locked, false) + FROM solana_organizations sol_org + LEFT JOIN organization_locks org_lock ON sol_org.address = org_lock.organization + WHERE sol_org.oui = $1 + ON CONFLICT (organization) DO UPDATE + SET locked = NOT organization_locks.locked "#, ) - .bind(oui as i64) + .bind(Decimal::from(oui)) .execute(db) .await?; @@ -573,31 +282,22 @@ pub enum OrgStoreError { RouteIdParse(#[from] sqlx::types::uuid::Error), #[error("Invalid update: {0}")] InvalidUpdate(String), + #[error("unable to decode numeric field: {0}")] + DecodeNumeric(String), } pub async fn get_org_pubkeys( oui: u64, db: impl sqlx::PgExecutor<'_>, -) -> Result, OrgStoreError> { +) -> Result, OrgStoreError> { let org = get(oui, db) .await? .ok_or_else(|| OrgStoreError::NotFound(format!("{oui}")))?; - let mut pubkeys: Vec = vec![ - PublicKey::try_from(org.owner)?, - PublicKey::try_from(org.payer)?, - ]; + let mut pubkeys: Vec = vec![org.owner]; - if let Some(ref mut delegate_pubkeys) = org - .delegate_keys - .map(|keys| { - keys.into_iter() - .map(PublicKey::try_from) - .collect::, helium_crypto::Error>>() - }) - .transpose()? - { - pubkeys.append(delegate_pubkeys); + if let Some(delegate_keys) = org.delegate_keys { + pubkeys.extend(delegate_keys); } Ok(pubkeys) @@ -606,52 +306,56 @@ pub async fn get_org_pubkeys( pub async fn get_org_pubkeys_by_route( route_id: &str, db: impl sqlx::PgExecutor<'_>, -) -> Result, OrgStoreError> { +) -> Result, OrgStoreError> { let uuid = Uuid::try_parse(route_id)?; - let org = sqlx::query_as::<_, Org>( r#" - select org.oui, org.owner_pubkey, org.payer_pubkey, org.locked, - array(select (start_addr, end_addr) from organization_devaddr_constraints org_const where org_const.oui = org.oui) as constraints, - array(select delegate_pubkey from organization_delegate_keys org_delegates where org_delegates.oui = org.oui) as delegate_keys - from organizations org - join routes on org.oui = routes.oui - where routes.id = $1 + SELECT + sol_org.oui::bigint, + sol_org.address, + sol_org.authority, + sol_org.escrow_key, + sol_org.approved, + COALESCE(ol.locked, true) AS locked, + ARRAY( + SELECT (start_addr, end_addr) + FROM solana_organization_devaddr_constraints + WHERE organization = sol_org.address + ) AS constraints, + ARRAY( + SELECT delegate + FROM solana_organization_delegate_keys + WHERE organization = sol_org.address + ) AS delegate_keys + FROM solana_organizations sol_org + LEFT JOIN organization_locks ol ON sol_org.address = ol.organization + JOIN routes r on sol_org.oui = r.oui + WHERE r.id = $1 "#, ) .bind(uuid) .fetch_one(db) .await?; - let mut pubkeys: Vec = vec![ - PublicKey::try_from(org.owner)?, - PublicKey::try_from(org.payer)?, - ]; - - if let Some(ref mut delegate_keys) = org - .delegate_keys - .map(|keys| { - keys.into_iter() - .map(PublicKey::try_from) - .collect::, helium_crypto::Error>>() - }) - .transpose()? - { - pubkeys.append(delegate_keys); + let mut pubkeys: Vec = vec![org.owner]; + if let Some(delegate_keys) = org.delegate_keys { + pubkeys.extend(delegate_keys); } Ok(pubkeys) } -impl From for proto::OrgV1 { +impl From for proto::OrgV2 { fn from(org: Org) -> Self { Self { oui: org.oui, - owner: org.owner.into(), - payer: org.payer.into(), + address: org.address.to_bytes().to_vec(), + owner: org.owner.to_bytes().to_vec(), + escrow_key: org.escrow_key, + approved: org.approved, locked: org.locked, delegate_keys: org.delegate_keys.map_or_else(Vec::new, |keys| { - keys.iter().map(|key| key.as_ref().into()).collect() + keys.iter().map(|key| key.to_bytes().to_vec()).collect() }), } } diff --git a/iot_config/src/org_service.rs b/iot_config/src/org_service.rs index a7379811f..e05568f5a 100644 --- a/iot_config/src/org_service.rs +++ b/iot_config/src/org_service.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{ admin::{AuthCache, KeyType}, - broadcast_update, helium_netids, lora_field, org, + broadcast_update, org, route::list_routes, telemetry, verify_public_key, GrpcResult, }; @@ -12,9 +12,8 @@ use file_store::traits::{MsgVerify, TimestampEncode}; use helium_crypto::{Keypair, PublicKey, Sign}; use helium_proto::{ services::iot_config::{ - self, route_stream_res_v1, ActionV1, DevaddrConstraintV1, OrgCreateHeliumReqV1, - OrgCreateRoamerReqV1, OrgDisableReqV1, OrgDisableResV1, OrgEnableReqV1, OrgEnableResV1, - OrgGetReqV1, OrgListReqV1, OrgListResV1, OrgResV1, OrgUpdateReqV1, OrgV1, RouteStreamResV1, + self, route_stream_res_v1, ActionV1, OrgDisableReqV1, OrgDisableResV1, OrgEnableReqV1, + OrgEnableResV1, OrgGetReqV2, OrgListReqV2, OrgListResV2, OrgResV2, OrgV2, RouteStreamResV1, }, Message, }; @@ -53,20 +52,6 @@ impl OrgService { }) } - fn verify_admin_request_signature( - &self, - signer: &PublicKey, - request: &R, - ) -> Result<(), Status> - where - R: MsgVerify, - { - self.auth_cache - .verify_signature_with_type(KeyType::Administrator, signer, request) - .map_err(|_| Status::permission_denied("invalid admin signature"))?; - Ok(()) - } - fn verify_request_signature(&self, signer: &PublicKey, request: &R) -> Result<(), Status> where R: MsgVerify, @@ -77,37 +62,6 @@ impl OrgService { Ok(()) } - async fn verify_update_request_signature( - &self, - signer: &PublicKey, - request: &OrgUpdateReqV1, - ) -> Result { - if self - .auth_cache - .verify_signature_with_type(KeyType::Administrator, signer, request) - .is_ok() - { - tracing::debug!(signer = signer.to_string(), "request authorized by admin"); - return Ok(UpdateAuthorizer::Admin); - } - - let org_owner = org::get(request.oui, &self.pool) - .await - .transpose() - .ok_or_else(|| Status::not_found(format!("oui: {}", request.oui)))? - .map(|org| org.owner) - .map_err(|_| Status::internal("auth verification error"))?; - if org_owner == signer.clone().into() && request.verify(signer).is_ok() { - tracing::debug!( - signer = signer.to_string(), - "request authorized by delegate" - ); - return Ok(UpdateAuthorizer::Org); - } - - Err(Status::permission_denied("unauthorized request signature")) - } - fn sign_response(&self, response: &[u8]) -> Result, Status> { self.signing_key .sign(response) @@ -149,17 +103,17 @@ impl OrgService { #[tonic::async_trait] impl iot_config::Org for OrgService { - async fn list(&self, _request: Request) -> GrpcResult { + async fn list(&self, _request: Request) -> GrpcResult { telemetry::count_request("org", "list"); - let proto_orgs: Vec = org::list(&self.pool) + let proto_orgs: Vec = org::list(&self.pool) .await .map_err(|_| Status::internal("org list failed"))? .into_iter() .map(|org| org.into()) .collect(); - let mut resp = OrgListResV1 { + let mut resp = OrgListResV2 { orgs: proto_orgs, timestamp: Utc::now().encode_timestamp(), signer: self.signing_key.public_key().into(), @@ -170,7 +124,7 @@ impl iot_config::Org for OrgService { Ok(Response::new(resp)) } - async fn get(&self, request: Request) -> GrpcResult { + async fn get(&self, request: Request) -> GrpcResult { let request = request.into_inner(); telemetry::count_request("org", "get"); custom_tracing::record("oui", request.oui); @@ -182,11 +136,12 @@ impl iot_config::Org for OrgService { Status::internal("org get failed") })? .ok_or_else(|| Status::not_found(format!("oui: {}", request.oui)))?; + let net_id = org::get_org_netid(org.oui, &self.pool) .await .map_err(|err| { tracing::error!(oui = org.oui, reason = ?err, "get org net id failed"); - Status::not_found("invalid org; no valid devaddr constraints") + Status::not_found("invalid org; no net id found") })?; let devaddr_constraints = org @@ -199,7 +154,7 @@ impl iot_config::Org for OrgService { .collect() }); - let mut resp = OrgResV1 { + let mut resp = OrgResV2 { org: Some(org.into()), net_id: net_id.into(), devaddr_constraints, @@ -207,250 +162,9 @@ impl iot_config::Org for OrgService { signer: self.signing_key.public_key().into(), signature: vec![], }; - resp.signature = self.sign_response(&resp.encode_to_vec())?; - - Ok(Response::new(resp)) - } - - async fn create_helium(&self, request: Request) -> GrpcResult { - let request = request.into_inner(); - telemetry::count_request("org", "create-helium"); - custom_tracing::record_b58("pub_key", &request.owner); - custom_tracing::record_b58("signer", &request.signer); - - let signer = verify_public_key(&request.signer)?; - self.verify_admin_request_signature(&signer, &request)?; - - let mut verify_keys: Vec<&[u8]> = vec![request.owner.as_ref(), request.payer.as_ref()]; - let mut verify_delegates: Vec<&[u8]> = request - .delegate_keys - .iter() - .map(|key| key.as_slice()) - .collect(); - verify_keys.append(&mut verify_delegates); - _ = verify_keys - .iter() - .map(|key| { - verify_public_key(key).map_err(|err| { - tracing::error!(reason = ?err, "failed pubkey validation"); - Status::invalid_argument(format!("failed pubkey validation: {err:?}")) - }) - }) - .collect::, Status>>()?; - - tracing::info!(?request, "create helium org"); - let net_id = request.net_id(); - let requested_addrs = if request.devaddrs >= 8 && request.devaddrs % 2 == 0 { - request.devaddrs - } else { - return Err(Status::invalid_argument(format!( - "{} devaddrs requested; minimum 8, even number required", - request.devaddrs - ))); - }; - - let mut txn = self - .pool - .begin() - .await - .map_err(|_| Status::internal("error saving org record"))?; - let devaddr_constraints = helium_netids::checkout_devaddr_constraints(&mut txn, requested_addrs, net_id.into()) - .await - .map_err(|err| { - tracing::error!(?net_id, count = %requested_addrs, reason = ?err, "failed to retrieve available helium devaddrs"); - Status::failed_precondition("helium addresses unavailable") - })?; - tracing::info!(constraints = ?devaddr_constraints, "devaddr constraints issued"); - let helium_netid_field = helium_netids::HeliumNetId::from(net_id).id(); - - let org = org::create_org( - request.owner.into(), - request.payer.into(), - request - .delegate_keys - .into_iter() - .map(|key| key.into()) - .collect(), - helium_netid_field, - &devaddr_constraints, - &mut txn, - ) - .await - .map_err(|err| { - tracing::error!(reason = ?err, "org save failed"); - Status::internal(format!("org save failed: {err:?}")) - })?; - - txn.commit() - .await - .map_err(|_| Status::internal("error saving org record"))?; - - org.delegate_keys.as_ref().map(|keys| { - self.delegate_updater.send_if_modified(|cache| { - keys.iter().fold(false, |acc, key| { - if cache.insert(key.clone()) { - tracing::info!(%key, "delegate key authorized"); - true - } else { - acc - } - }) - }) - }); - - let devaddr_constraints = org - .constraints - .clone() - .unwrap_or_default() - .into_iter() - .map(DevaddrConstraintV1::from) - .collect(); - let mut resp = OrgResV1 { - org: Some(org.into()), - net_id: helium_netid_field.into(), - devaddr_constraints, - timestamp: Utc::now().encode_timestamp(), - signer: self.signing_key.public_key().into(), - signature: vec![], - }; resp.signature = self.sign_response(&resp.encode_to_vec())?; - - Ok(Response::new(resp)) - } - - async fn create_roamer(&self, request: Request) -> GrpcResult { - let request = request.into_inner(); - telemetry::count_request("org", "create-roamer"); - custom_tracing::record_b58("pub_key", &request.owner); - custom_tracing::record_b58("signer", &request.signer); - - let signer = verify_public_key(&request.signer)?; - self.verify_admin_request_signature(&signer, &request)?; - - let mut verify_keys: Vec<&[u8]> = vec![request.owner.as_ref(), request.payer.as_ref()]; - let mut verify_delegates: Vec<&[u8]> = request - .delegate_keys - .iter() - .map(|key| key.as_slice()) - .collect(); - verify_keys.append(&mut verify_delegates); - _ = verify_keys - .iter() - .map(|key| { - verify_public_key(key).map_err(|err| { - Status::invalid_argument(format!("failed pubkey validation: {err:?}")) - }) - }) - .collect::, Status>>()?; - - tracing::info!(?request, "create roamer org"); - - let net_id = lora_field::net_id(request.net_id); - let devaddr_range = net_id - .full_range() - .map_err(|_| Status::invalid_argument("invalid net_id"))?; - tracing::info!(constraints = ?devaddr_range, "roaming devaddr range"); - - let org = org::create_org( - request.owner.into(), - request.payer.into(), - request - .delegate_keys - .into_iter() - .map(|key| key.into()) - .collect(), - net_id, - &[devaddr_range], - &self.pool, - ) - .await - .map_err(|err| { - tracing::error!(reason = ?err, "failed to create org"); - Status::internal(format!("org save failed: {err:?}")) - })?; - - org.delegate_keys.as_ref().map(|keys| { - self.delegate_updater.send_if_modified(|cache| { - keys.iter().fold(false, |acc, key| { - if cache.insert(key.clone()) { - tracing::info!(?key, "delegate key authorized"); - true - } else { - acc - } - }) - }) - }); - - let devaddr_constraints = org - .constraints - .clone() - .unwrap_or_default() - .into_iter() - .map(DevaddrConstraintV1::from) - .collect(); - let mut resp = OrgResV1 { - org: Some(org.into()), - net_id: net_id.into(), - devaddr_constraints, - timestamp: Utc::now().encode_timestamp(), - signer: self.signing_key.public_key().into(), - signature: vec![], - }; - resp.signature = self.sign_response(&resp.encode_to_vec())?; - - Ok(Response::new(resp)) - } - - async fn update(&self, request: Request) -> GrpcResult { - let request = request.into_inner(); - telemetry::count_request("org", "update"); - custom_tracing::record("oui", request.oui); - custom_tracing::record_b58("signer", &request.signer); - - let signer = verify_public_key(&request.signer)?; - let authorizer = self - .verify_update_request_signature(&signer, &request) - .await?; - - let org = org::update_org( - request.oui, - authorizer, - request.updates, - &self.pool, - &self.delegate_updater, - ) - .await - .map_err(|err| { - tracing::error!(reason = ?err, "org update failed"); - Status::internal(format!("org update failed: {err:?}")) - })?; - - let net_id = org::get_org_netid(org.oui, &self.pool) - .await - .map_err(|err| { - tracing::error!(oui = org.oui, reason = ?err, "get org net id failed"); - Status::not_found("invalid org; no valid devaddr constraints") - })?; - - let devaddr_constraints = org - .constraints - .clone() - .unwrap_or_default() - .into_iter() - .map(DevaddrConstraintV1::from) - .collect(); - let mut resp = OrgResV1 { - org: Some(org.into()), - net_id: net_id.into(), - devaddr_constraints, - timestamp: Utc::now().encode_timestamp(), - signer: self.signing_key.public_key().into(), - signature: vec![], - }; - resp.signature = self.sign_response(&resp.encode_to_vec())?; - + println!("Response: {:?}", resp); Ok(Response::new(resp)) } diff --git a/iot_config/src/route.rs b/iot_config/src/route.rs index f050f1324..05e0d6740 100644 --- a/iot_config/src/route.rs +++ b/iot_config/src/route.rs @@ -132,13 +132,13 @@ pub async fn create_route( .await?; let route_id = row.get::("id").to_string(); - let new_route = get_route(&route_id, &mut transaction).await?; transaction.commit().await?; let timestamp = Utc::now().encode_timestamp(); let signer = signing_key.public_key().into(); + let mut update = proto::RouteStreamResV1 { action: proto::ActionV1::Add.into(), data: Some(proto::route_stream_res_v1::Data::Route( @@ -148,6 +148,7 @@ pub async fn create_route( signer, signature: vec![], }; + _ = futures::future::ready(signing_key.sign(&update.encode_to_vec())) .map_err(|err| { tracing::error!(error = ?err, "error signing route create"); @@ -462,26 +463,42 @@ pub async fn update_devaddr_ranges( pub async fn list_routes(oui: u64, db: impl sqlx::PgExecutor<'_>) -> anyhow::Result> { Ok(sqlx::query_as::<_, StorageRoute>( r#" - select r.id, r.oui, r.net_id, r.max_copies, r.server_host, r.server_port, r.server_protocol_opts, r.active, r.ignore_empty_skf, o.locked - from routes r - join organizations o on r.oui = o.oui - where o.oui = $1 and r.deleted = false - group by r.id, o.locked + SELECT + r.id, + r.oui, + r.net_id, + r.max_copies, + r.server_host, + r.server_port, + r.server_protocol_opts, + r.active, + r.ignore_empty_skf, + COALESCE(ol.locked, true) AS locked + FROM routes r + JOIN solana_organizations o ON r.oui = o.oui + LEFT JOIN organization_locks ol ON ol.organization = o.address + WHERE o.oui = $1 AND r.deleted = false "#, ) .bind(oui as i64) .fetch(db) .map_err(RouteStorageError::from) - .and_then(|route| async move { Ok(Route { + .and_then(|route| async move { + Ok(Route { id: route.id.to_string(), net_id: route.net_id.into(), oui: route.oui as u64, - server: RouteServer::new(route.server_host, route.server_port as u32, serde_json::from_value(route.server_protocol_opts)?), + server: RouteServer::new( + route.server_host, + route.server_port as u32, + serde_json::from_value(route.server_protocol_opts)?, + ), max_copies: route.max_copies as u32, active: route.active, locked: route.locked, ignore_empty_skf: route.ignore_empty_skf, - })}) + }) + }) .filter_map(|route| async move { route.ok() }) .collect::>() .await) @@ -527,27 +544,47 @@ pub fn route_stream<'a>( ) -> impl Stream + 'a { sqlx::query( r#" - select r.id, r.oui, r.net_id, r.max_copies, r.server_host, r.server_port, r.server_protocol_opts, r.active, r.ignore_empty_skf, o.locked, r.deleted - from routes r - join organizations o on r.oui = o.oui - where r.updated_at >= $1 - group by r.id, o.locked + SELECT + r.id, + r.oui, + r.net_id, + r.max_copies, + r.server_host, + r.server_port, + r.server_protocol_opts, + r.active, + r.ignore_empty_skf, + COALESCE(ol.locked, true) AS locked, + r.deleted + FROM routes r + JOIN solana_organizations o ON r.oui = o.oui + LEFT JOIN organization_locks ol ON ol.organization = o.address + WHERE r.updated_at >= $1 "#, ) .bind(since) .fetch(db) .and_then(|row| async move { StorageRoute::from_row(&row).map(|sr| (sr, row.get("deleted"))) }) .map_err(RouteStorageError::from) - .and_then(|(route, deleted)| async move { Ok((Route { - id: route.id.to_string(), - net_id: route.net_id.into(), - oui: route.oui as u64, - server: RouteServer::new(route.server_host, route.server_port as u32, serde_json::from_value(route.server_protocol_opts)?), - max_copies: route.max_copies as u32, - active: route.active, - locked: route.locked, - ignore_empty_skf: route.ignore_empty_skf, - }, deleted))}) + .and_then(|(route, deleted)| async move { + Ok(( + Route { + id: route.id.to_string(), + net_id: route.net_id.into(), + oui: route.oui as u64, + server: RouteServer::new( + route.server_host, + route.server_port as u32, + serde_json::from_value(route.server_protocol_opts)?, + ), + max_copies: route.max_copies as u32, + active: route.active, + locked: route.locked, + ignore_empty_skf: route.ignore_empty_skf, + }, + deleted, + )) + }) .filter_map(|result| async move { result.ok() }) .boxed() } @@ -615,11 +652,21 @@ pub async fn get_route(id: &str, db: impl sqlx::PgExecutor<'_>) -> anyhow::Resul let uuid = Uuid::try_parse(id)?; let route = sqlx::query_as::<_, StorageRoute>( r#" - select r.id, r.oui, r.net_id, r.max_copies, r.server_host, r.server_port, r.server_protocol_opts, r.active, r.ignore_empty_skf, o.locked - from routes r - join organizations o on r.oui = o.oui - where r.id = $1 and r.deleted = false - group by r.id, o.locked + SELECT + r.id, + r.oui, + r.net_id, + r.max_copies, + r.server_host, + r.server_port, + r.server_protocol_opts, + r.active, + r.ignore_empty_skf, + COALESCE(ol.locked, true) AS locked + FROM routes r + JOIN solana_organizations o ON r.oui = o.oui + LEFT JOIN organization_locks ol ON ol.organization = o.address + WHERE r.id = $1 AND r.deleted = false "#, ) .bind(uuid) diff --git a/iot_config/src/route_service.rs b/iot_config/src/route_service.rs index 349933271..46a6627d6 100644 --- a/iot_config/src/route_service.rs +++ b/iot_config/src/route_service.rs @@ -1,5 +1,6 @@ use crate::{ admin::{AuthCache, KeyType}, + convert_to_helium_public_key, convert_to_solana_public_key, lora_field::{DevAddrConstraint, DevAddrRange, EuiPair, Skf}, org::{self, OrgStoreError}, route::{self, Route, RouteStorageError}, @@ -28,7 +29,6 @@ use sqlx::{Pool, Postgres}; use std::{pin::Pin, sync::Arc}; use tokio::sync::{broadcast, mpsc}; use tonic::{Request, Response, Status}; - const UPDATE_BATCH_LIMIT: usize = 5_000; const SKF_UPDATE_LIMIT: usize = 100; @@ -90,7 +90,10 @@ impl RouteService { _ => Status::internal("auth verification error"), })?; - if org_keys.as_slice().contains(signer) && request.verify(signer).is_ok() { + let sol_signer = convert_to_solana_public_key(signer) + .map_err(|err| Status::invalid_argument(format!("invalid public key: {err:?}")))?; + + if org_keys.as_slice().contains(&sol_signer) && request.verify(signer).is_ok() { tracing::debug!( signer = signer.to_string(), "request authorized by delegate" @@ -521,9 +524,12 @@ impl iot_config::Route for RouteService { incoming_stream .map_ok(|update| match validator.validate_update(&update) { Ok(()) => Ok(update), - Err(reason) => Err(Status::invalid_argument(format!( - "invalid update request: {reason:?}" - ))), + Err(reason) => { + tracing::error!("Validation failed: {:?}", reason); + Err(Status::invalid_argument(format!( + "invalid update request: {reason:?}" + ))) + } }) .try_chunks(UPDATE_BATCH_LIMIT) .map_err(|err| Status::internal(format!("eui pair updates failed to batch: {err:?}"))) @@ -561,6 +567,7 @@ impl iot_config::Route for RouteService { .into_iter() .map(|(_, remove)| remove.into()) .collect(); + route::update_euis( &adds_update, &removes_update, @@ -949,7 +956,7 @@ enum DevAddrEuiValidationError { impl DevAddrEuiValidator { async fn new( route_id: &str, - mut admin_keys: Vec, + admin_keys: Vec, db: impl sqlx::PgExecutor<'_> + Copy, check_constraints: bool, ) -> Result { @@ -959,13 +966,18 @@ impl DevAddrEuiValidator { None }; - let mut org_keys = org::get_org_pubkeys_by_route(route_id, db).await?; - org_keys.append(&mut admin_keys); + let org_keys = org::get_org_pubkeys_by_route(route_id, db).await?; + let mut signing_keys = org_keys + .into_iter() + .filter_map(|key| convert_to_helium_public_key(&key).ok()) + .collect::>(); + + signing_keys.extend(admin_keys); Ok(Self { route_ids: org::get_route_ids_by_route(route_id, db).await?, constraints, - signing_keys: org_keys, + signing_keys, }) } @@ -1074,7 +1086,7 @@ where R: MsgVerify + ValidateRouteComponent<'a> + std::fmt::Debug, { for (idx, pubkey) in signing_keys.iter().enumerate() { - if request.verify(pubkey).is_ok() { + if request.verify(&pubkey).is_ok() { signing_keys.swap(idx, 0); return Ok(request); } diff --git a/iot_config/tests/fixtures.rs b/iot_config/tests/fixtures.rs new file mode 100644 index 000000000..cb873b911 --- /dev/null +++ b/iot_config/tests/fixtures.rs @@ -0,0 +1,154 @@ +use backon::{ExponentialBuilder, Retryable}; +use helium_proto::services::iot_config::{self as proto, config_org_client::OrgClient}; +use solana_sdk::pubkey::Pubkey; +use sqlx::{Pool, Postgres}; +use std::net::SocketAddr; + +pub async fn create_solana_org( + pool: &Pool, + authority: &String, + escrow_key: &String, + net_id: &String, + oui: Option, +) -> anyhow::Result<(String, u64)> { + let address = Pubkey::new_unique().to_string(); + let oui = oui.unwrap_or(1); + + sqlx::query( + r#" + INSERT INTO solana_organizations ( + address, + net_id, + authority, + oui, + escrow_key, + approved + ) + VALUES ($1, $2, $3, $4, $5, $6) + "#, + ) + .bind(address.clone()) + .bind(net_id) + .bind(authority) + .bind(oui) + .bind(escrow_key) + .bind(true) + .execute(pool) + .await?; + + Ok((address, oui as u64)) +} + +pub async fn create_solana_org_devaddr_constraint( + pool: &Pool, + net_id: &String, + organization: &String, + current_addr_offset: Option, + num_blocks: i64, +) -> anyhow::Result { + let address = Pubkey::new_unique().to_string(); + let end_addr = current_addr_offset.unwrap_or(0) + num_blocks * 8; + + sqlx::query( + r#" + INSERT INTO solana_organization_devaddr_constraints ( + address, + net_id, + organization, + start_addr, + end_addr + ) + VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(address.clone()) + .bind(net_id) + .bind(organization) + .bind(current_addr_offset.unwrap_or(0)) + .bind(end_addr) + .execute(pool) + .await?; + + Ok(address) +} + +pub async fn create_solana_org_delegate_key( + pool: &Pool, + organization: &String, + delegate: &String, +) -> anyhow::Result { + let address = Pubkey::new_unique().to_string(); + + sqlx::query( + r#" + INSERT INTO solana_organization_delegate_keys ( + address, + organization, + delegate + ) + VALUES ($1, $2, $3) + "#, + ) + .bind(address.clone()) + .bind(organization) + .bind(delegate) + .execute(pool) + .await?; + + Ok(address) +} + +pub async fn create_solana_net_id( + pool: &Pool, + authority: &String, + id: Option, + current_addr_offset: Option, +) -> anyhow::Result { + let address = Pubkey::new_unique().to_string(); + + sqlx::query( + r#" + INSERT INTO solana_net_ids ( + address, + id, + authority, + current_addr_offset + ) + VALUES ($1, $2, $3, $4) + "#, + ) + .bind(address.clone()) + .bind(id.unwrap_or(6)) + .bind(authority) + .bind(current_addr_offset.unwrap_or(0)) + .execute(pool) + .await?; + + Ok(address) +} + +pub async fn create_org(socket_addr: SocketAddr, pool: &Pool) -> proto::OrgResV2 { + let mut client = (|| OrgClient::connect(format!("http://{socket_addr}"))) + .retry(&ExponentialBuilder::default()) + .await + .expect("org client"); + + let payer = Pubkey::new_unique().to_string(); + let net_id_res = create_solana_net_id(pool, &payer, None, None).await; + let net_id = net_id_res.unwrap(); + + let org_res = create_solana_org(pool, &payer, &payer, &net_id, None).await; + let (org_id, oui) = org_res.unwrap(); + + let devaddr_res = create_solana_org_devaddr_constraint(pool, &net_id, &org_id, None, 8).await; + let _devaddr = devaddr_res.unwrap(); + + let response = match client.get(proto::OrgGetReqV2 { oui }).await { + Ok(resp) => resp, + Err(e) => { + panic!("Failed to get the org: {:?}", e); + } + }; + + response.into_inner() +} diff --git a/iot_config/tests/route_service.rs b/iot_config/tests/route_service.rs index 3c10e51e4..3fca2c445 100644 --- a/iot_config/tests/route_service.rs +++ b/iot_config/tests/route_service.rs @@ -8,8 +8,8 @@ use chrono::Utc; use futures::{Future, StreamExt, TryFutureExt}; use helium_crypto::{KeyTag, Keypair, PublicKey, Sign}; use helium_proto::services::iot_config::{ - self as proto, config_org_client::OrgClient, config_route_client::RouteClient, RouteGetReqV1, - RouteListReqV1, RouteStreamReqV1, + self as proto, config_route_client::RouteClient, RouteGetReqV1, RouteListReqV1, + RouteStreamReqV1, }; use iot_config::{ admin::{AuthCache, KeyType}, @@ -25,6 +25,8 @@ use tonic::{ Streaming, }; +mod fixtures; + #[sqlx::test] async fn packet_router_can_access_route_list(pool: Pool) { let signing_keypair = Arc::new(generate_keypair()); @@ -43,7 +45,7 @@ async fn packet_router_can_access_route_list(pool: Pool) { let _handle = start_server(socket_addr, signing_keypair, auth_cache, pool.clone()).await; let mut client = connect_client(socket_addr).await; - let org = create_org(socket_addr, &admin_keypair).await; + let org = fixtures::create_org(socket_addr, &pool).await; let route = create_route(&mut client, &org.org.unwrap(), &admin_keypair).await; // List Routes for OUI @@ -85,7 +87,7 @@ async fn stream_sends_all_data_when_since_is_0(pool: Pool) { let _handle = start_server(socket_addr, signing_keypair, auth_cache, pool.clone()).await; let mut client = connect_client(socket_addr).await; - let org = create_org(socket_addr, &admin_keypair).await; + let org = fixtures::create_org(socket_addr, &pool).await; let route = create_route(&mut client, &org.org.unwrap(), &admin_keypair).await; create_euis( @@ -187,17 +189,16 @@ async fn stream_only_sends_data_modified_since(pool: Pool) { let _handle = start_server(socket_addr, signing_keypair, auth_cache, pool.clone()).await; let mut client = connect_client(socket_addr).await; - let org_res_v1 = create_org(socket_addr, &admin_keypair).await; - - let proto::OrgResV1 { org: Some(org), .. } = org_res_v1 else { - panic!("invalid OrgResV1") + let org_res_v2 = fixtures::create_org(socket_addr, &pool).await; + let proto::OrgResV2 { org: Some(org), .. } = org_res_v2 else { + panic!("invalid OrgResV2") }; let route1 = create_route(&mut client, &org, &admin_keypair).await; create_euis(&mut client, &route1, vec![(200, 201)], &admin_keypair).await; - let constraint = org_res_v1.devaddr_constraints.first().unwrap(); + let constraint = org_res_v2.devaddr_constraints.first().unwrap(); create_devaddr_ranges( &mut client, &route1, @@ -293,10 +294,10 @@ async fn stream_updates_with_deactivate_reactivate(pool: Pool) { let _handle = start_server(socket_addr, signing_keypair, auth_cache, pool.clone()).await; let mut client = connect_client(socket_addr).await; - let org_res_v1 = create_org(socket_addr, &admin_keypair).await; + let org_res_v2 = fixtures::create_org(socket_addr, &pool).await; - let proto::OrgResV1 { org: Some(org), .. } = org_res_v1 else { - panic!("invalid OrgResV1") + let proto::OrgResV2 { org: Some(org), .. } = org_res_v2 else { + panic!("invalid OrgResV2") }; let route = create_route(&mut client, &org, &admin_keypair).await; @@ -532,43 +533,9 @@ fn get_socket_addr() -> anyhow::Result { Ok(listener.local_addr()?) } -async fn create_org(socket_addr: SocketAddr, admin_keypair: &Keypair) -> proto::OrgResV1 { - let mut client = (|| OrgClient::connect(format!("http://{socket_addr}"))) - .retry(&ExponentialBuilder::default()) - .await - .expect("org client"); - - let mut request = proto::OrgCreateHeliumReqV1 { - owner: generate_keypair().public_key().to_vec(), - payer: generate_keypair().public_key().to_vec(), - devaddrs: 8, - timestamp: Utc::now().timestamp() as u64, - signature: vec![], - delegate_keys: vec![], - signer: admin_keypair.public_key().into(), - net_id: 6, - }; - - request.signature = admin_keypair - .sign(&request.encode_to_vec()) - .expect("sign create org"); - - let response = client.create_helium(request).await; - - let proto::OrgResV1 { org: Some(org), .. } = response.unwrap().into_inner() else { - panic!("org response is incorrect") - }; - - let Ok(response) = client.get(proto::OrgGetReqV1 { oui: org.oui }).await else { - panic!("could not get the org") - }; - - response.into_inner() -} - async fn create_route( client: &mut RouteClient, - org: &proto::OrgV1, + org: &proto::OrgV2, signing_keypair: &Keypair, ) -> proto::RouteV1 { let mut request = proto::RouteCreateReqV1 { @@ -640,6 +607,7 @@ async fn create_euis( }) .collect::>(); + println!("Logging Route {:?}", route); let Ok(_) = client.update_euis(futures::stream::iter(requests)).await else { panic!("unable to create eui pairs") }; diff --git a/iot_packet_verifier/migrations/0007_rename_payer_to_escrow_key.sql b/iot_packet_verifier/migrations/0007_rename_payer_to_escrow_key.sql new file mode 100644 index 000000000..a12c5567e --- /dev/null +++ b/iot_packet_verifier/migrations/0007_rename_payer_to_escrow_key.sql @@ -0,0 +1,2 @@ +ALTER TABLE pending_burns RENAME COLUMN payer TO escrow_key; +ALTER TABLE pending_txns RENAME COLUMN payer TO escrow_key; \ No newline at end of file diff --git a/iot_packet_verifier/src/balances.rs b/iot_packet_verifier/src/balances.rs index 57f559e71..d16fa02c0 100644 --- a/iot_packet_verifier/src/balances.rs +++ b/iot_packet_verifier/src/balances.rs @@ -2,7 +2,6 @@ use crate::{ pending::{Burn, PendingTables}, verifier::Debiter, }; -use helium_crypto::PublicKeyBinary; use solana::burn::SolanaNetwork; use std::{ collections::{hash_map::Entry, HashMap}, @@ -17,7 +16,7 @@ pub struct BalanceCache { solana: S, } -pub type BalanceStore = Arc>>; +pub type BalanceStore = Arc>>; impl BalanceCache where @@ -29,14 +28,14 @@ where let mut balances = HashMap::new(); for Burn { - payer, + escrow_key, amount: burn_amount, } in pending_tables.fetch_all_pending_burns().await? { // Look up the current balance of the payer - let balance = solana.payer_balance(&payer).await?; + let balance = solana.payer_balance(&escrow_key).await?; balances.insert( - payer, + escrow_key, PayerAccount { burned: burn_amount, balance, @@ -68,30 +67,31 @@ where /// option if there was enough and none otherwise. async fn debit_if_sufficient( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, trigger_balance_check_threshold: u64, ) -> Result, S::Error> { let mut payer_accounts = self.payer_accounts.lock().await; // Fetch the balance if we haven't seen the payer before - if let Entry::Vacant(payer_account) = payer_accounts.entry(payer.clone()) { - let payer_account = - payer_account.insert(PayerAccount::new(self.solana.payer_balance(payer).await?)); + if let Entry::Vacant(payer_account) = payer_accounts.entry(escrow_key.clone()) { + let payer_account = payer_account.insert(PayerAccount::new( + self.solana.payer_balance(escrow_key).await?, + )); return Ok((payer_account.balance >= amount).then(|| { payer_account.burned += amount; payer_account.balance - amount })); } - let payer_account = payer_accounts.get_mut(payer).unwrap(); + let payer_account = payer_accounts.get_mut(escrow_key).unwrap(); match payer_account .balance .checked_sub(amount + payer_account.burned) { Some(remaining_balance) => { if remaining_balance < trigger_balance_check_threshold { - payer_account.balance = self.solana.payer_balance(payer).await?; + payer_account.balance = self.solana.payer_balance(escrow_key).await?; } payer_account.burned += amount; Ok(Some(payer_account.balance - payer_account.burned)) diff --git a/iot_packet_verifier/src/burner.rs b/iot_packet_verifier/src/burner.rs index 750860511..fb073b95c 100644 --- a/iot_packet_verifier/src/burner.rs +++ b/iot_packet_verifier/src/burner.rs @@ -97,20 +97,20 @@ where pub async fn burn(&mut self) -> Result<(), BurnError> { // Fetch the next payer and amount that should be burn. If no such burn // exists, perform no action. - let Some(Burn { payer, amount }) = self.pending_tables.fetch_next_burn().await? else { + let Some(Burn { escrow_key, amount }) = self.pending_tables.fetch_next_burn().await? else { return Ok(()); }; - tracing::info!(%amount, %payer, "Burning DC"); + tracing::info!(%amount, %escrow_key, "Burning DC"); // Create a burn transaction and execute it: let txn = self .solana - .make_burn_transaction(&payer, amount) + .make_burn_transaction(&escrow_key, amount) .await .map_err(BurnError::SolanaError)?; self.pending_tables - .add_pending_transaction(&payer, amount, txn.get_signature()) + .add_pending_transaction(&escrow_key, amount, txn.get_signature()) .await?; self.solana .submit_transaction(&txn) @@ -125,18 +125,18 @@ where .remove_pending_transaction(txn.get_signature()) .await?; pending_tables_txn - .subtract_burned_amount(&payer, amount) + .subtract_burned_amount(&escrow_key, amount) .await?; pending_tables_txn.commit().await?; let mut balance_lock = self.balances.lock().await; - let payer_account = balance_lock.get_mut(&payer).unwrap(); + let payer_account = balance_lock.get_mut(&escrow_key).unwrap(); // Reduce the pending burn amount and the payer's balance by the amount // we've burned. payer_account.burned = payer_account.burned.saturating_sub(amount); payer_account.balance = payer_account.balance.saturating_sub(amount); - metrics::counter!("burned", "payer" => payer.to_string()).increment(amount); + metrics::counter!("burned", "payer" => escrow_key.to_string()).increment(amount); Ok(()) } diff --git a/iot_packet_verifier/src/pending.rs b/iot_packet_verifier/src/pending.rs index 0db62ce63..8360f70ea 100644 --- a/iot_packet_verifier/src/pending.rs +++ b/iot_packet_verifier/src/pending.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; use chrono::{DateTime, Duration, Utc}; -use helium_crypto::PublicKeyBinary; use solana::burn::SolanaNetwork; use solana_sdk::signature::Signature; use sqlx::{postgres::PgRow, FromRow, PgPool, Postgres, Row, Transaction}; @@ -17,7 +16,7 @@ const BURN_THRESHOLD: i64 = 10_000; pub trait AddPendingBurn { async fn add_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error>; } @@ -36,7 +35,7 @@ pub trait PendingTables { async fn add_pending_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, signature: &Signature, ) -> Result<(), sqlx::Error>; @@ -83,10 +82,10 @@ where .await .map_err(ConfirmPendingError::SolanaError)? { - txn.subtract_burned_amount(&pending.payer, pending.amount) + txn.subtract_burned_amount(&pending.escrow_key, pending.amount) .await?; let mut balance_lock = balances.lock().await; - let payer_account = balance_lock.get_mut(&pending.payer).unwrap(); + let payer_account = balance_lock.get_mut(&pending.escrow_key).unwrap(); payer_account.burned = payer_account.burned.saturating_sub(pending.amount); payer_account.balance = payer_account.balance.saturating_sub(pending.amount); } @@ -106,7 +105,7 @@ pub trait PendingTablesTransaction<'a> { async fn subtract_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error>; @@ -144,18 +143,18 @@ impl PendingTables for PgPool { async fn add_pending_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, signature: &Signature, ) -> Result<(), sqlx::Error> { sqlx::query( r#" - INSERT INTO pending_txns (signature, payer, amount, time_of_submission) + INSERT INTO pending_txns (signature, escrow_key, amount, time_of_submission) VALUES ($1, $2, $3, $4) "#, ) .bind(signature.to_string()) - .bind(payer) + .bind(escrow_key) .bind(amount as i64) .bind(Utc::now()) .execute(self) @@ -168,18 +167,18 @@ impl PendingTables for PgPool { impl AddPendingBurn for &'_ mut Transaction<'_, Postgres> { async fn add_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error> { sqlx::query( r#" - INSERT INTO pending_burns (payer, amount, last_burn) + INSERT INTO pending_burns (escrow_key, amount, last_burn) VALUES ($1, $2, $3) ON CONFLICT (payer) DO UPDATE SET amount = pending_burns.amount + $2 "#, ) - .bind(payer) + .bind(escrow_key) .bind(amount as i64) .bind(Utc::now()) .execute(&mut **self) @@ -203,7 +202,7 @@ impl<'a> PendingTablesTransaction<'a> for Transaction<'a, Postgres> { async fn subtract_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error> { sqlx::query( @@ -212,12 +211,12 @@ impl<'a> PendingTablesTransaction<'a> for Transaction<'a, Postgres> { amount = amount - $1, last_burn = $2 WHERE - payer = $3 + escrow_key = $3 "#, ) .bind(amount as i64) .bind(Utc::now()) - .bind(payer) + .bind(escrow_key) .execute(&mut *self) .await?; Ok(()) @@ -230,14 +229,14 @@ impl<'a> PendingTablesTransaction<'a> for Transaction<'a, Postgres> { #[derive(Debug)] pub struct Burn { - pub payer: PublicKeyBinary, + pub escrow_key: String, pub amount: u64, } impl FromRow<'_, PgRow> for Burn { fn from_row(row: &PgRow) -> sqlx::Result { Ok(Self { - payer: row.try_get("payer")?, + escrow_key: row.try_get("escrow_key")?, amount: row.try_get::("amount")? as u64, }) } @@ -245,7 +244,7 @@ impl FromRow<'_, PgRow> for Burn { pub struct PendingTxn { pub signature: Signature, - pub payer: PublicKeyBinary, + pub escrow_key: String, pub amount: u64, pub time_of_submission: DateTime, } @@ -253,7 +252,7 @@ pub struct PendingTxn { impl FromRow<'_, PgRow> for PendingTxn { fn from_row(row: &PgRow) -> sqlx::Result { Ok(Self { - payer: row.try_get("payer")?, + escrow_key: row.try_get("escrow_key")?, amount: row.try_get::("amount")? as u64, time_of_submission: row.try_get("time_of_submission")?, signature: row @@ -268,21 +267,21 @@ impl FromRow<'_, PgRow> for PendingTxn { } #[async_trait] -impl AddPendingBurn for Arc>> { +impl AddPendingBurn for Arc>> { async fn add_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error> { let mut map = self.lock().await; - *map.entry(payer.clone()).or_default() += amount; + *map.entry(escrow_key.clone()).or_default() += amount; Ok(()) } } #[derive(Clone)] pub struct MockPendingTxn { - payer: PublicKeyBinary, + escrow_key: String, amount: u64, time_of_submission: DateTime, } @@ -290,7 +289,7 @@ pub struct MockPendingTxn { #[derive(Default, Clone)] pub struct MockPendingTables { pub pending_txns: Arc>>, - pub pending_burns: Arc>>, + pub pending_burns: Arc>>, } #[async_trait] @@ -304,8 +303,8 @@ impl PendingTables for MockPendingTables { .await .iter() .max_by_key(|(_, amount)| **amount) - .map(|(payer, &amount)| Burn { - payer: payer.clone(), + .map(|(escrow_key, &amount)| Burn { + escrow_key: escrow_key.clone(), amount, })) } @@ -317,7 +316,7 @@ impl PendingTables for MockPendingTables { .await .clone() .into_iter() - .map(|(payer, amount)| Burn { payer, amount }) + .map(|(escrow_key, amount)| Burn { escrow_key, amount }) .collect()) } @@ -330,7 +329,7 @@ impl PendingTables for MockPendingTables { .into_iter() .map(|(signature, mock)| PendingTxn { signature, - payer: mock.payer, + escrow_key: mock.escrow_key, amount: mock.amount, time_of_submission: mock.time_of_submission, }) @@ -339,14 +338,14 @@ impl PendingTables for MockPendingTables { async fn add_pending_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, signature: &Signature, ) -> Result<(), sqlx::Error> { self.pending_txns.lock().await.insert( *signature, MockPendingTxn { - payer: payer.clone(), + escrow_key: escrow_key.clone(), amount, time_of_submission: Utc::now(), }, @@ -371,11 +370,11 @@ impl<'a> PendingTablesTransaction<'a> for &'a MockPendingTables { async fn subtract_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error> { let mut map = self.pending_burns.lock().await; - let balance = map.get_mut(payer).unwrap(); + let balance = map.get_mut(escrow_key).unwrap(); *balance -= amount; Ok(()) } @@ -401,14 +400,14 @@ mod test { type Transaction = Signature; #[allow(clippy::diverging_sub_expression)] - async fn payer_balance(&self, _payer: &PublicKeyBinary) -> Result { + async fn payer_balance(&self, _escrow_key: &String) -> Result { unreachable!() } #[allow(clippy::diverging_sub_expression)] async fn make_burn_transaction( &self, - _payer: &PublicKeyBinary, + _escrow_key: &String, _amount: u64, ) -> Result { unreachable!() @@ -431,16 +430,14 @@ mod test { async fn test_confirm_pending_txns() { let confirmed = Signature::new_unique(); let unconfirmed = Signature::new_unique(); - let payer: PublicKeyBinary = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6" - .parse() - .unwrap(); + let escrow_key = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6".to_string(); let mut pending_txns = HashMap::new(); const CONFIRMED_BURN_AMOUNT: u64 = 7; const UNCONFIRMED_BURN_AMOUNT: u64 = 11; pending_txns.insert( confirmed, MockPendingTxn { - payer: payer.clone(), + escrow_key: escrow_key.clone(), amount: CONFIRMED_BURN_AMOUNT, time_of_submission: Utc::now() - Duration::minutes(1), }, @@ -448,14 +445,14 @@ mod test { pending_txns.insert( unconfirmed, MockPendingTxn { - payer: payer.clone(), + escrow_key: escrow_key.clone(), amount: UNCONFIRMED_BURN_AMOUNT, time_of_submission: Utc::now() - Duration::minutes(1), }, ); let mut balances = HashMap::new(); balances.insert( - payer.clone(), + escrow_key.clone(), PayerAccount { balance: CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, burned: CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, @@ -463,7 +460,7 @@ mod test { ); let mut pending_burns = HashMap::new(); pending_burns.insert( - payer.clone(), + escrow_key.clone(), CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, ); let pending_txns = Arc::new(Mutex::new(pending_txns)); @@ -486,7 +483,7 @@ mod test { .pending_burns .lock() .await - .get(&payer) + .get(&escrow_key) .unwrap(), UNCONFIRMED_BURN_AMOUNT, ); diff --git a/iot_packet_verifier/src/verifier.rs b/iot_packet_verifier/src/verifier.rs index 9e96c5040..3d0c36335 100644 --- a/iot_packet_verifier/src/verifier.rs +++ b/iot_packet_verifier/src/verifier.rs @@ -6,7 +6,6 @@ use file_store::{ traits::{MsgBytes, MsgTimestamp}, }; use futures::{Stream, StreamExt}; -use helium_crypto::PublicKeyBinary; use helium_proto::services::{ packet_verifier::{InvalidPacket, InvalidPacketReason, ValidPacket}, router::packet_router_packet_report_v1::PacketType, @@ -64,7 +63,7 @@ where VP: PacketWriter, IP: PacketWriter, { - let mut org_cache = HashMap::::new(); + let mut org_cache = HashMap::::new(); tokio::pin!(reports); @@ -79,7 +78,7 @@ where payload_size_to_dc(report.payload_size as u64) }; - let payer = self + let escrow_key = self .config_server .fetch_org(report.oui, &mut org_cache) .await @@ -87,12 +86,12 @@ where if let Some(remaining_balance) = self .debiter - .debit_if_sufficient(&payer, debit_amount, minimum_allowed_balance) + .debit_if_sufficient(&escrow_key, debit_amount, minimum_allowed_balance) .await .map_err(VerificationError::DebitError)? { pending_burns - .add_burned_amount(&payer, debit_amount) + .add_burned_amount(&escrow_key, debit_amount) .await .map_err(VerificationError::BurnError)?; @@ -149,34 +148,33 @@ pub trait Debiter { /// return the remaining amount. async fn debit_if_sufficient( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, trigger_balance_check_threshold: u64, ) -> Result, Self::Error>; } #[async_trait] -impl Debiter for Arc>> { +impl Debiter for Arc>> { type Error = Infallible; async fn debit_if_sufficient( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, _trigger_balance_check_threshold: u64, ) -> Result, Infallible> { let map = self.lock().await; - let balance = map.get(payer).unwrap(); + let balance = map.get(escrow_key).unwrap(); // Don't debit the amount if we're mocking. That is a job for the burner. Ok((*balance >= amount).then(|| balance.saturating_sub(amount))) } } // TODO: Move these to a separate module - pub struct Org { pub oui: u64, - pub payer: PublicKeyBinary, + pub escrow_key: String, pub locked: bool, } @@ -187,8 +185,8 @@ pub trait ConfigServer: Sized + Send + Sync + 'static { async fn fetch_org( &self, oui: u64, - cache: &mut HashMap, - ) -> Result; + cache: &mut HashMap, + ) -> Result; async fn disable_org(&self, oui: u64) -> Result<(), Self::Error>; @@ -212,7 +210,11 @@ pub trait ConfigServer: Sized + Send + Sync + 'static { loop { tracing::info!("Checking if any orgs need to be re-enabled"); - for Org { locked, payer, oui } in self + for Org { + locked, + escrow_key, + oui, + } in self .list_orgs() .await .map_err(MonitorError::ConfigClientError)? @@ -220,11 +222,11 @@ pub trait ConfigServer: Sized + Send + Sync + 'static { { if locked { let balance = solana - .payer_balance(&payer) + .payer_balance(&escrow_key) .await .map_err(MonitorError::SolanaError)?; if balance >= minimum_allowed_balance { - balances.set_balance(&payer, balance).await; + balances.set_balance(&escrow_key, balance).await; self.enable_org(oui) .await .map_err(MonitorError::ConfigClientError)?; @@ -248,21 +250,25 @@ pub trait ConfigServer: Sized + Send + Sync + 'static { #[async_trait] pub trait BalanceStore: Send + Sync + 'static { - async fn set_balance(&self, payer: &PublicKeyBinary, balance: u64); + async fn set_balance(&self, escrow_key: &String, balance: u64); } #[async_trait] impl BalanceStore for crate::balances::BalanceStore { - async fn set_balance(&self, payer: &PublicKeyBinary, balance: u64) { - self.lock().await.entry(payer.clone()).or_default().balance = balance; + async fn set_balance(&self, escrow_key: &String, balance: u64) { + self.lock() + .await + .entry(escrow_key.clone()) + .or_default() + .balance = balance; } } #[async_trait] // differs from the BalanceStore in the value stored in the contained HashMap; a u64 here instead of a Balance {} struct -impl BalanceStore for Arc>> { - async fn set_balance(&self, payer: &PublicKeyBinary, balance: u64) { - *self.lock().await.entry(payer.clone()).or_default() = balance; +impl BalanceStore for Arc>> { + async fn set_balance(&self, escrow_key: &String, balance: u64) { + *self.lock().await.entry(escrow_key.clone()).or_default() = balance; } } @@ -308,20 +314,20 @@ where async fn fetch_org( &self, oui: u64, - oui_cache: &mut HashMap, - ) -> Result { + oui_cache: &mut HashMap, + ) -> Result { if let Entry::Vacant(e) = oui_cache.entry(oui) { - let pubkey = PublicKeyBinary::from( - self.lock() - .await - .orgs - .get(oui) - .await? - .org - .ok_or(ConfigServerError::NotFound(oui))? - .payer, - ); - e.insert(pubkey); + let escrow_key = self + .lock() + .await + .orgs + .get(oui) + .await? + .org + .ok_or(ConfigServerError::NotFound(oui))? + .escrow_key; + + e.insert(escrow_key); } Ok(oui_cache.get(&oui).unwrap().clone()) } @@ -354,7 +360,7 @@ where .into_iter() .map(|org| Org { oui: org.oui, - payer: PublicKeyBinary::from(org.payer), + escrow_key: org.escrow_key, locked: org.locked, }) .collect()) diff --git a/iot_packet_verifier/tests/integration_tests.rs b/iot_packet_verifier/tests/integration_tests.rs index f4251dda0..06c1fad38 100644 --- a/iot_packet_verifier/tests/integration_tests.rs +++ b/iot_packet_verifier/tests/integration_tests.rs @@ -30,7 +30,7 @@ use std::{ use tokio::sync::Mutex; struct MockConfig { - payer: PublicKeyBinary, + escrow_key: String, enabled: bool, } @@ -40,11 +40,11 @@ struct MockConfigServer { } impl MockConfigServer { - async fn insert(&self, oui: u64, payer: PublicKeyBinary) { + async fn insert(&self, oui: u64, escrow_key: String) { self.payers.lock().await.insert( oui, MockConfig { - payer, + escrow_key, enabled: true, }, ); @@ -55,12 +55,15 @@ impl MockConfigServer { impl ConfigServer for MockConfigServer { type Error = (); - async fn fetch_org( - &self, - oui: u64, - _cache: &mut HashMap, - ) -> Result { - Ok(self.payers.lock().await.get(&oui).unwrap().payer.clone()) + async fn fetch_org(&self, oui: u64, _cache: &mut HashMap) -> Result { + Ok(self + .payers + .lock() + .await + .get(&oui) + .unwrap() + .escrow_key + .clone()) } async fn disable_org(&self, oui: u64) -> Result<(), ()> { @@ -81,7 +84,7 @@ impl ConfigServer for MockConfigServer { .iter() .map(|(oui, config)| Org { oui: *oui, - payer: config.payer.clone(), + escrow_key: config.escrow_key.clone(), locked: !config.enabled, }) .collect()) @@ -164,17 +167,17 @@ fn invalid_packet(payload_size: u32, payload_hash: Vec) -> InvalidPacket { } #[derive(Clone)] -struct InstantlyBurnedBalance(Arc>>); +struct InstantlyBurnedBalance(Arc>>); #[async_trait] impl AddPendingBurn for InstantlyBurnedBalance { async fn add_burned_amount( &mut self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result<(), sqlx::Error> { let mut map = self.0.lock().await; - let balance = map.get_mut(payer).unwrap(); + let balance = map.get_mut(escrow_key).unwrap(); *balance -= amount; Ok(()) } @@ -182,16 +185,17 @@ impl AddPendingBurn for InstantlyBurnedBalance { #[tokio::test] async fn test_config_unlocking() { + let escrow_key = format!("OUI_{}", 0); // Set up orgs: let orgs = MockConfigServer::default(); - orgs.insert(0_u64, PublicKeyBinary::from(vec![0])).await; + orgs.insert(0_u64, escrow_key.clone()).await; // Set up balances: let mut solana_network = HashMap::new(); - solana_network.insert(PublicKeyBinary::from(vec![0]), 3); + solana_network.insert(escrow_key.clone(), 3); let solana_network = Arc::new(Mutex::new(solana_network)); // Set up cache: let mut cache = HashMap::new(); - cache.insert(PublicKeyBinary::from(vec![0]), 3); + cache.insert(escrow_key.clone(), 3); let cache = Arc::new(Mutex::new(cache)); let balances = InstantlyBurnedBalance(cache.clone()); // Set up verifier: @@ -219,11 +223,7 @@ async fn test_config_unlocking() { assert!(!orgs.payers.lock().await.get(&0).unwrap().enabled); // Update the solana network: - *solana_network - .lock() - .await - .get_mut(&PublicKeyBinary::from(vec![0])) - .unwrap() = 50; + *solana_network.lock().await.get_mut(&escrow_key).unwrap() = 50; let (trigger, listener) = triggered::trigger(); @@ -248,14 +248,7 @@ async fn test_config_unlocking() { // We should be re-enabled assert!(orgs.payers.lock().await.get(&0).unwrap().enabled); - assert_eq!( - *cache - .lock() - .await - .get(&PublicKeyBinary::from(vec![0])) - .unwrap(), - 50 - ); + assert_eq!(*cache.lock().await.get(&escrow_key).unwrap(), 50); trigger.trigger(); @@ -276,14 +269,7 @@ async fn test_config_unlocking() { // Still enabled: assert!(orgs.payers.lock().await.get(&0).unwrap().enabled); - assert_eq!( - *cache - .lock() - .await - .get(&PublicKeyBinary::from(vec![0])) - .unwrap(), - 46 - ); + assert_eq!(*cache.lock().await.get(&escrow_key).unwrap(), 46); } #[tokio::test] @@ -295,15 +281,15 @@ async fn test_verifier_free_packets() { packet_report(0, 2, 1, vec![6], true), ]; - let org_pubkey = PublicKeyBinary::from(vec![0]); + let org_escrow_key = format!("OUI_{}", 0); // Set up orgs: let orgs = MockConfigServer::default(); - orgs.insert(0_u64, org_pubkey.clone()).await; + orgs.insert(0_u64, org_escrow_key.clone()).await; // Set up balances: let mut balances = HashMap::new(); - balances.insert(org_pubkey.clone(), 5); + balances.insert(org_escrow_key.clone(), 5); let balances = InstantlyBurnedBalance(Arc::new(Mutex::new(balances))); // Set up output: @@ -345,7 +331,7 @@ async fn test_verifier_free_packets() { assert_eq!( verifier .debiter - .payer_balance(&org_pubkey) + .payer_balance(&org_escrow_key) .await .expect("unchanged balance"), 5 @@ -371,14 +357,14 @@ async fn test_verifier() { ]; // Set up orgs: let orgs = MockConfigServer::default(); - orgs.insert(0_u64, PublicKeyBinary::from(vec![0])).await; - orgs.insert(1_u64, PublicKeyBinary::from(vec![1])).await; - orgs.insert(2_u64, PublicKeyBinary::from(vec![2])).await; + orgs.insert(0_u64, format!("OUI_{}", 0)).await; + orgs.insert(1_u64, format!("OUI_{}", 1)).await; + orgs.insert(2_u64, format!("OUI_{}", 2)).await; // Set up balances: let mut balances = HashMap::new(); - balances.insert(PublicKeyBinary::from(vec![0]), 3); - balances.insert(PublicKeyBinary::from(vec![1]), 5); - balances.insert(PublicKeyBinary::from(vec![2]), 2); + balances.insert(format!("OUI_{}", 0), 3); + balances.insert(format!("OUI_{}", 1), 5); + balances.insert(format!("OUI_{}", 2), 2); let balances = InstantlyBurnedBalance(Arc::new(Mutex::new(balances))); // Set up output: let mut valid_packets = Vec::new(); @@ -428,11 +414,10 @@ async fn test_verifier() { #[tokio::test] async fn test_end_to_end() { - let payer = PublicKeyBinary::from(vec![0]); + let escrow_key = format!("OUI_{}", 0); // Pending tables: - let pending_burns: Arc>> = - Arc::new(Mutex::new(HashMap::new())); + let pending_burns: Arc>> = Arc::new(Mutex::new(HashMap::new())); let pending_tables = MockPendingTables { pending_txns: Default::default(), pending_burns: pending_burns.clone(), @@ -440,7 +425,7 @@ async fn test_end_to_end() { // Solana network: let mut solana_network = HashMap::new(); - solana_network.insert(payer.clone(), 3_u64); // Start with 3 data credits + solana_network.insert(escrow_key.clone(), 3_u64); // Start with 3 data credits let solana_network = Arc::new(Mutex::new(solana_network)); // Balance cache: @@ -458,7 +443,7 @@ async fn test_end_to_end() { // Orgs: let orgs = MockConfigServer::default(); - orgs.insert(0_u64, payer.clone()).await; + orgs.insert(0_u64, escrow_key.clone()).await; // Packet output: let mut valid_packets = Vec::new(); @@ -518,13 +503,13 @@ async fn test_end_to_end() { let balance = { let balances = verifier.debiter.balances(); let balances = balances.lock().await; - *balances.get(&payer).unwrap() + *balances.get(&escrow_key).unwrap() }; assert_eq!(balance.balance, 3); assert_eq!(balance.burned, 3); // Check that 3 DC are pending to be burned: - let pending_burn = *pending_burns.lock().await.get(&payer).unwrap(); + let pending_burn = *pending_burns.lock().await.get(&escrow_key).unwrap(); assert_eq!(pending_burn, 3); // Initiate the burn: @@ -534,17 +519,17 @@ async fn test_end_to_end() { let balance = { let balances = verifier.debiter.balances(); let balances = balances.lock().await; - *balances.get(&payer).unwrap() + *balances.get(&escrow_key).unwrap() }; assert_eq!(balance.balance, 0); assert_eq!(balance.burned, 0); // Pending burns should be empty as well: - let pending_burn = *pending_burns.lock().await.get(&payer).unwrap(); + let pending_burn = *pending_burns.lock().await.get(&escrow_key).unwrap(); assert_eq!(pending_burn, 0); // Additionally, the balance on the solana network should be zero: - let solana_balance = *solana_network.lock().await.get(&payer).unwrap(); + let solana_balance = *solana_network.lock().await.get(&escrow_key).unwrap(); assert_eq!(solana_balance, 0); // Attempting to validate one packet should fail now: @@ -579,11 +564,11 @@ async fn test_end_to_end() { #[derive(Clone)] struct MockSolanaNetwork { confirmed: Arc>>, - ledger: Arc>>, + ledger: Arc>>, } impl MockSolanaNetwork { - fn new(ledger: HashMap) -> Self { + fn new(ledger: HashMap) -> Self { Self { confirmed: Arc::new(Default::default()), ledger: Arc::new(Mutex::new(ledger)), @@ -596,16 +581,16 @@ impl SolanaNetwork for MockSolanaNetwork { type Error = std::convert::Infallible; type Transaction = MockTransaction; - async fn payer_balance(&self, payer: &PublicKeyBinary) -> Result { - self.ledger.payer_balance(payer).await + async fn payer_balance(&self, escrow_key: &String) -> Result { + self.ledger.payer_balance(escrow_key).await } async fn make_burn_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result { - self.ledger.make_burn_transaction(payer, amount).await + self.ledger.make_burn_transaction(escrow_key, amount).await } async fn submit_transaction(&self, txn: &MockTransaction) -> Result<(), Self::Error> { @@ -623,17 +608,15 @@ impl SolanaNetwork for MockSolanaNetwork { async fn test_pending_txns(pool: PgPool) -> anyhow::Result<()> { const CONFIRMED_BURN_AMOUNT: u64 = 7; const UNCONFIRMED_BURN_AMOUNT: u64 = 11; - let payer: PublicKeyBinary = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6" - .parse() - .unwrap(); + let escrow_key = format!("112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6"); let mut ledger = HashMap::new(); ledger.insert( - payer.clone(), + escrow_key.clone(), CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, ); let mut cache = HashMap::new(); cache.insert( - payer.clone(), + escrow_key.clone(), PayerAccount { balance: CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, burned: CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT, @@ -645,7 +628,7 @@ async fn test_pending_txns(pool: PgPool) -> anyhow::Result<()> { { let mut transaction = pool.begin().await.unwrap(); (&mut transaction) - .add_burned_amount(&payer, CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT) + .add_burned_amount(&escrow_key, CONFIRMED_BURN_AMOUNT + UNCONFIRMED_BURN_AMOUNT) .await .unwrap(); transaction.commit().await.unwrap(); @@ -654,10 +637,10 @@ async fn test_pending_txns(pool: PgPool) -> anyhow::Result<()> { // First transaction is confirmed { let txn = mock_network - .make_burn_transaction(&payer, CONFIRMED_BURN_AMOUNT) + .make_burn_transaction(&escrow_key, CONFIRMED_BURN_AMOUNT) .await .unwrap(); - pool.add_pending_transaction(&payer, CONFIRMED_BURN_AMOUNT, txn.get_signature()) + pool.add_pending_transaction(&escrow_key, CONFIRMED_BURN_AMOUNT, txn.get_signature()) .await .unwrap(); mock_network.submit_transaction(&txn).await.unwrap(); @@ -666,10 +649,10 @@ async fn test_pending_txns(pool: PgPool) -> anyhow::Result<()> { // Second is unconfirmed { let txn = mock_network - .make_burn_transaction(&payer, UNCONFIRMED_BURN_AMOUNT) + .make_burn_transaction(&escrow_key, UNCONFIRMED_BURN_AMOUNT) .await .unwrap(); - pool.add_pending_transaction(&payer, UNCONFIRMED_BURN_AMOUNT, txn.get_signature()) + pool.add_pending_transaction(&escrow_key, UNCONFIRMED_BURN_AMOUNT, txn.get_signature()) .await .unwrap(); } diff --git a/mobile_packet_verifier/src/burner.rs b/mobile_packet_verifier/src/burner.rs index 7ab9b362b..266d33fa0 100644 --- a/mobile_packet_verifier/src/burner.rs +++ b/mobile_packet_verifier/src/burner.rs @@ -42,7 +42,7 @@ where let total_dcs = payer_pending_burn.total_dcs; let sessions = payer_pending_burn.sessions; - let payer_balance = self.solana.payer_balance(&payer).await?; + let payer_balance = self.solana.payer_balance(&payer.to_string()).await?; if payer_balance < total_dcs { tracing::warn!( @@ -55,7 +55,10 @@ where } tracing::info!(%total_dcs, %payer, "Burning DC"); - let txn = self.solana.make_burn_transaction(&payer, total_dcs).await?; + let txn = self + .solana + .make_burn_transaction(&payer.to_string(), total_dcs) + .await?; match self.solana.submit_transaction(&txn).await { Ok(()) => { handle_transaction_success( diff --git a/solana/Cargo.toml b/solana/Cargo.toml index b63b87618..9477e4be6 100644 --- a/solana/Cargo.toml +++ b/solana/Cargo.toml @@ -24,6 +24,9 @@ solana-client = {workspace = true} solana-program = {workspace = true} solana-sdk = {workspace = true} spl-token = {workspace = true} +spl-associated-token-account = {workspace = true} thiserror = {workspace = true} tokio = {workspace = true} tracing = {workspace = true} +bincode = {workspace = true} + diff --git a/solana/src/burn.rs b/solana/src/burn.rs index eb84f8127..6660b3a06 100644 --- a/solana/src/burn.rs +++ b/solana/src/burn.rs @@ -7,7 +7,6 @@ use helium_anchor_gen::{ data_credits::{self, accounts, instruction}, helium_sub_daos::{self, DaoV0, SubDaoV0}, }; -use helium_crypto::PublicKeyBinary; use itertools::Itertools; use serde::Deserialize; use sha2::{Digest, Sha256}; @@ -23,8 +22,8 @@ use solana_sdk::{ signer::Signer, transaction::Transaction, }; +use std::collections::HashMap; use std::convert::Infallible; -use std::{collections::HashMap, str::FromStr}; use std::{ sync::Arc, time::{Duration, SystemTime}, @@ -36,11 +35,13 @@ pub trait SolanaNetwork: Clone + Send + Sync + 'static { type Error: std::error::Error + Send + Sync + 'static; type Transaction: GetSignature + Send + Sync + 'static; - async fn payer_balance(&self, payer: &PublicKeyBinary) -> Result; + #[allow(clippy::ptr_arg)] + async fn payer_balance(&self, escrow_key: &String) -> Result; + #[allow(clippy::ptr_arg)] async fn make_burn_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result; @@ -57,7 +58,7 @@ pub struct Settings { dc_mint: String, dnt_mint: String, #[serde(default)] - payers_to_monitor: Vec, + escrow_keys_to_monitor: Vec, #[serde(default = "min_priority_fee")] min_priority_fee: u64, } @@ -67,12 +68,8 @@ fn min_priority_fee() -> u64 { } impl Settings { - pub fn payers_to_monitor(&self) -> Result, SolanaRpcError> { - self.payers_to_monitor - .iter() - .map(|payer| PublicKeyBinary::from_str(payer)) - .collect::>() - .map_err(SolanaRpcError::from) + pub fn escrow_keys_to_monitor(&self) -> Result, SolanaRpcError> { + Ok(self.escrow_keys_to_monitor.clone()) } } @@ -82,7 +79,7 @@ pub struct SolanaRpc { program_cache: BurnProgramCache, cluster: String, keypair: [u8; 64], - payers_to_monitor: Vec, + escrow_keys_to_monitor: Vec, priority_fee: PriorityFee, min_priority_fee: u64, } @@ -105,7 +102,7 @@ impl SolanaRpc { provider: Arc::new(provider), program_cache, keypair: keypair.to_bytes(), - payers_to_monitor: settings.payers_to_monitor()?, + escrow_keys_to_monitor: settings.escrow_keys_to_monitor()?, priority_fee: PriorityFee::default(), min_priority_fee: settings.min_priority_fee, })) @@ -117,8 +114,8 @@ impl SolanaNetwork for SolanaRpc { type Error = SolanaRpcError; type Transaction = Transaction; - async fn payer_balance(&self, payer: &PublicKeyBinary) -> Result { - let ddc_key = delegated_data_credits(&self.program_cache.sub_dao, payer); + async fn payer_balance(&self, escrow_key: &String) -> Result { + let ddc_key = delegated_data_credits(&self.program_cache.sub_dao, escrow_key); let (escrow_account, _) = Pubkey::find_program_address( &["escrow_dc_account".as_bytes(), &ddc_key.to_bytes()], &data_credits::ID, @@ -129,7 +126,7 @@ impl SolanaNetwork for SolanaRpc { .await? { Response { value: None, .. } => { - tracing::info!(%payer, "Account not found, therefore no balance"); + tracing::info!(%escrow_key, "Account not found, therefore no balance"); return Ok(0); } Response { @@ -139,10 +136,10 @@ impl SolanaNetwork for SolanaRpc { }; let account_layout = spl_token::state::Account::unpack(account_data.as_slice())?; - if self.payers_to_monitor.contains(payer) { + if self.escrow_keys_to_monitor.contains(escrow_key) { metrics::gauge!( "balance", - "payer" => payer.to_string() + "escrow_key" => escrow_key.to_string() ) .set(account_layout.amount as f64); } @@ -152,7 +149,7 @@ impl SolanaNetwork for SolanaRpc { async fn make_burn_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result { // Fetch the sub dao epoch info: @@ -171,7 +168,7 @@ impl SolanaNetwork for SolanaRpc { ); // Fetch escrow account - let ddc_key = delegated_data_credits(&self.program_cache.sub_dao, payer); + let ddc_key = delegated_data_credits(&self.program_cache.sub_dao, escrow_key); let (escrow_account, _) = Pubkey::find_program_address( &["escrow_dc_account".as_bytes(), &ddc_key.to_bytes()], &data_credits::ID, @@ -183,7 +180,7 @@ impl SolanaNetwork for SolanaRpc { sub_dao: self.program_cache.sub_dao, account_payer: self.program_cache.account_payer, data_credits: self.program_cache.data_credits, - delegated_data_credits: delegated_data_credits(&self.program_cache.sub_dao, payer), + delegated_data_credits: delegated_data_credits(&self.program_cache.sub_dao, escrow_key), token_program: spl_token::id(), helium_sub_daos_program: helium_sub_daos::id(), system_program: solana_program::system_program::id(), @@ -434,9 +431,9 @@ impl SolanaNetwork for Option> { type Error = SolanaRpcError; type Transaction = PossibleTransaction; - async fn payer_balance(&self, payer: &PublicKeyBinary) -> Result { + async fn payer_balance(&self, escrow_key: &String) -> Result { if let Some(ref rpc) = self { - rpc.payer_balance(payer).await + rpc.payer_balance(escrow_key).await } else { Ok(FIXED_BALANCE) } @@ -444,12 +441,12 @@ impl SolanaNetwork for Option> { async fn make_burn_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result { if let Some(ref rpc) = self { Ok(PossibleTransaction::Transaction( - rpc.make_burn_transaction(payer, amount).await?, + rpc.make_burn_transaction(escrow_key, amount).await?, )) } else { Ok(PossibleTransaction::NoTransaction(Signature::new_unique())) @@ -478,7 +475,7 @@ impl SolanaNetwork for Option> { pub struct MockTransaction { pub signature: Signature, - pub payer: PublicKeyBinary, + pub escrow_key: String, pub amount: u64, } @@ -489,28 +486,28 @@ impl GetSignature for MockTransaction { } #[async_trait] -impl SolanaNetwork for Arc>> { +impl SolanaNetwork for Arc>> { type Error = Infallible; type Transaction = MockTransaction; - async fn payer_balance(&self, payer: &PublicKeyBinary) -> Result { - Ok(*self.lock().await.get(payer).unwrap()) + async fn payer_balance(&self, escrow_key: &String) -> Result { + Ok(*self.lock().await.get(escrow_key).unwrap()) } async fn make_burn_transaction( &self, - payer: &PublicKeyBinary, + escrow_key: &String, amount: u64, ) -> Result { Ok(MockTransaction { signature: Signature::new_unique(), - payer: payer.clone(), + escrow_key: escrow_key.clone(), amount, }) } async fn submit_transaction(&self, txn: &MockTransaction) -> Result<(), Self::Error> { - *self.lock().await.get_mut(&txn.payer).unwrap() -= txn.amount; + *self.lock().await.get_mut(&txn.escrow_key).unwrap() -= txn.amount; Ok(()) } @@ -520,9 +517,9 @@ impl SolanaNetwork for Arc>> { } /// Returns the PDA for the Delegated Data Credits of the given `payer`. -pub fn delegated_data_credits(sub_dao: &Pubkey, payer: &PublicKeyBinary) -> Pubkey { +pub fn delegated_data_credits(sub_dao: &Pubkey, escrow_key: &String) -> Pubkey { let mut hasher = Sha256::new(); - hasher.update(payer.to_string()); + hasher.update(escrow_key); let sha_digest = hasher.finalize(); let (ddc_key, _) = Pubkey::find_program_address( &[