diff --git a/Cargo.lock b/Cargo.lock index aff7d76738..2bc3dc97e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,15 +118,16 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8ebf106e84a1c37f86244df7da0c7587e697b71a0d565cce079449b85ac6f8" +checksum = "02b0561294ccedc6181e5528b850b4579e3fbde696507baa00109bfd9054c5bb" dependencies = [ "alloy-consensus", "alloy-contract", "alloy-core", "alloy-eips", "alloy-genesis", + "alloy-json-rpc", "alloy-network", "alloy-node-bindings", "alloy-provider", @@ -141,9 +142,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" +checksum = "a0161082e0edd9013d23083465cc04b20e44b7a15646d36ba7b0cdb7cd6fe18f" dependencies = [ "alloy-primitives", "num_enum", @@ -152,25 +153,40 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed961a48297c732a5d97ee321aa8bb5009ecadbcb077d8bec90cb54e651629" +checksum = "a101d4d016f47f13890a74290fdd17b05dd175191d9337bc600791fb96e4dea8" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", + "alloy-trie", "auto_impl", "c-kzg", "derive_more", "serde", ] +[[package]] +name = "alloy-consensus-any" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa60357dda9a3d0f738f18844bd6d0f4a5924cc5cf00bfad2ff1369897966123" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-contract" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460ab80ce4bda1c80bcf96fe7460520476f2c7b734581c6567fac2708e2a60ef" +checksum = "2869e4fb31331d3b8c58c7db567d1e4e4e94ef64640beda3b6dd9b7045690941" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -183,14 +199,14 @@ dependencies = [ "alloy-transport", "futures", "futures-util", - "thiserror 1.0.69", + "thiserror 2.0.6", ] [[package]] name = "alloy-core" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d14d531c99995de71558e8e2206c27d709559ee8e5a0452b965ea82405a013" +checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -201,9 +217,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -229,9 +245,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.3.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ffc577390ce50234e02d841214b3dc0bea6aaaae8e04bbf3cb82e9a45da9eb" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -241,9 +257,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69e06cf9c37be824b9d26d6d101114fdde6af0c87de2828b414c05c4b3daa71" +checksum = "8b6755b093afef5925f25079dd5a7c8d096398b804ba60cb5275397b06b31689" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -259,20 +275,21 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde15e14944a88bd6a57d325e9a49b75558746fe16aaccc79713ae50a6a9574c" +checksum = "aeec8e6eab6e52b7c9f918748c9b811e87dbef7312a2e3a2ca1729a92966a6af" dependencies = [ "alloy-primitives", "alloy-serde", + "alloy-trie", "serde", ] [[package]] name = "alloy-json-abi" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -282,29 +299,31 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5979e0d5a7bf9c7eb79749121e8256e59021af611322aee56e77e20776b4b3" +checksum = "4fa077efe0b834bcd89ff4ba547f48fb081e4fdc3673dd7da1b295a2cf2bb7b7" dependencies = [ "alloy-primitives", "alloy-sol-types", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.6", "tracing", ] [[package]] name = "alloy-network" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "204237129086ce5dc17a58025e93739b01b45313841f98fa339eb1d780511e57" +checksum = "209a1882a08e21aca4aac6e2a674dc6fcf614058ef8cb02947d63782b1899552" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", + "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -312,14 +331,16 @@ dependencies = [ "async-trait", "auto_impl", "futures-utils-wasm", - "thiserror 1.0.69", + "serde", + "serde_json", + "thiserror 2.0.6", ] [[package]] name = "alloy-network-primitives" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514f70ee2a953db21631cd817b13a1571474ec77ddc03d47616d5e8203489fde" +checksum = "c20219d1ad261da7a6331c16367214ee7ded41d001fabbbd656fbf71898b2773" dependencies = [ "alloy-consensus", "alloy-eips", @@ -330,9 +351,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27444ea67d360508753022807cdd0b49a95c878924c9c5f8f32668b7d7768245" +checksum = "bffcf33dd319f21cd6f066d81cbdef0326d4bdaaf7cfe91110bc090707858e9f" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -340,16 +361,16 @@ dependencies = [ "rand 0.8.5", "serde_json", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.6", "tracing", "url", ] [[package]] name = "alloy-primitives" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", @@ -375,9 +396,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4814d141ede360bb6cd1b4b064f1aab9de391e7c4d0d4d50ac89ea4bc1e25fbd" +checksum = "9eefa6f4c798ad01f9b4202d02cea75f5ec11fa180502f4701e2b47965a8c0bb" dependencies = [ "alloy-chains", "alloy-consensus", @@ -407,18 +428,18 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tracing", "url", - "wasmtimer", + "wasmtimer 0.4.1", ] [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -427,9 +448,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "5a833d97bf8a5f0f878daf2c8451fff7de7f9de38baa5a45d936ec718d81255a" dependencies = [ "proc-macro2", "quote", @@ -438,9 +459,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2bd1e7403463a5f2c61e955bcc9d3072b63aa177442b0f9aa6a6d22a941e3" +checksum = "ed30bf1041e84cabc5900f52978ca345dd9969f2194a945e6fdec25b0620705c" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -456,17 +477,16 @@ dependencies = [ "tower 0.5.1", "tracing", "url", - "wasmtimer", + "wasmtimer 0.4.1", ] [[package]] name = "alloy-rpc-types" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea9bf1abdd506f985a53533f5ac01296bcd6102c5e139bbc5d40bc468d2c916" +checksum = "5ab686b0fa475d2a4f5916c5f07797734a691ec58e44f0f55d4746ea39cbcefb" dependencies = [ "alloy-primitives", - "alloy-rpc-types-anvil", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -474,22 +494,35 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2382fc63fb0cf3e02818d547b80cb66cc49a31f8803d0c328402b2008bc13650" +checksum = "d33bc190844626c08e21897736dbd7956ab323c09e6f141b118d1c8b7aff689e" dependencies = [ "alloy-primitives", + "alloy-rpc-types-eth", "alloy-serde", "serde", ] +[[package]] +name = "alloy-rpc-types-any" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200661999b6e235d9840be5d60a6e8ae2f0af9eb2a256dd378786744660e36ec" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + [[package]] name = "alloy-rpc-types-eth" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b034779a4850b4b03f5be5ea674a1cf7d746b2da762b34d1860ab45e48ca27" +checksum = "a0600b8b5e2dc0cab12cbf91b5a885c35871789fb7b3a57b434bd4fced5b7a8b" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", "alloy-primitives", @@ -504,9 +537,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028e72eaa9703e4882344983cfe7636ce06d8cce104a78ea62fd19b46659efc4" +checksum = "9afa753a97002a33b2ccb707d9f15f31c81b8c1b786c95b73cc62bb1d1fd0c3f" dependencies = [ "alloy-primitives", "serde", @@ -515,23 +548,23 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592c185d7100258c041afac51877660c7bf6213447999787197db4842f0e938e" +checksum = "9b2cbff01a673936c2efd7e00d4c0e9a4dbbd6d600e2ce298078d33efbb19cd7" dependencies = [ "alloy-primitives", "async-trait", "auto_impl", "elliptic-curve 0.13.8", "k256", - "thiserror 1.0.69", + "thiserror 2.0.6", ] [[package]] name = "alloy-signer-local" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6614f02fc1d5b079b2a4a5320018317b506fd0a6d67c1fd5542a71201724986c" +checksum = "bd6d988cb6cd7d2f428a74476515b1a6e901e08c796767f9f93311ab74005c8b" dependencies = [ "alloy-consensus", "alloy-network", @@ -540,14 +573,14 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror 1.0.69", + "thiserror 2.0.6", ] [[package]] name = "alloy-sol-macro" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -559,9 +592,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -578,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" dependencies = [ "alloy-json-abi", "const-hex", @@ -595,9 +628,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" dependencies = [ "serde", "winnow", @@ -605,9 +638,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -618,9 +651,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be77579633ebbc1266ae6fd7694f75c408beb1aeb6865d0b18f22893c265a061" +checksum = "d69d36982b9e46075ae6b792b0f84208c6c2c15ad49f6c500304616ef67b70e0" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -628,20 +661,20 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tower 0.5.1", "tracing", "url", "wasm-bindgen-futures", - "wasmtimer", + "wasmtimer 0.4.1", ] [[package]] name = "alloy-transport-http" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd1a5d0827939847983b46f2f79510361f901dc82f8e3c38ac7397af142c6e" +checksum = "2e02ffd5d93ffc51d72786e607c97de3b60736ca3e636ead0ec1f7dce68ea3fd" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -652,6 +685,22 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-trie" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5fd8fea044cc9a8c8a50bb6f28e31f0385d820f116c5b98f6f4e55d6e5590b" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -780,7 +829,7 @@ dependencies = [ "tiny-keccak", "tokio", "tracing", - "wasmtimer", + "wasmtimer 0.2.1", "xor_name", ] @@ -866,7 +915,7 @@ dependencies = [ "void", "walkdir", "wasm-bindgen-futures", - "wasmtimer", + "wasmtimer 0.2.1", "xor_name", ] @@ -1118,9 +1167,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" @@ -1287,6 +1336,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "ascii" @@ -2065,9 +2117,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -2112,9 +2164,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2171,9 +2223,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -2191,9 +2243,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -2218,9 +2270,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -3304,9 +3356,9 @@ checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -3321,9 +3373,9 @@ dependencies = [ [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -3793,7 +3845,7 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d48b897b4bbc881aea994b4a5bbb340a04979d7be9089791304e04a9fbc66b53" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -3802,7 +3854,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6ffbeb3a5c0b8b84c3fe4133a6f8c82fa962f4caefe8d0762eced025d3eb4f7" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -3850,7 +3902,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -4073,7 +4125,7 @@ dependencies = [ "gix-trace", "home", "once_cell", - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -4084,7 +4136,7 @@ checksum = "64a1e282216ec2ab2816cd57e6ed88f8009e634aec47562883c05ac8a7009a63" dependencies = [ "bstr", "gix-utils", - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -4321,7 +4373,7 @@ dependencies = [ "indexmap 2.7.0", "slab", "tokio", - "tokio-util 0.7.12", + "tokio-util 0.7.13", "tracing", ] @@ -4584,9 +4636,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -4611,7 +4663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -4622,7 +4674,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -4688,7 +4740,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "httparse", "itoa", @@ -4719,13 +4771,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", + "http 1.2.0", "hyper 1.5.1", "hyper-util", "rustls 0.23.19", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", "webpki-roots 0.26.7", ] @@ -4751,7 +4803,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "hyper 1.5.1", "pin-project-lite", @@ -5209,9 +5261,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -5268,9 +5320,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" @@ -6065,9 +6117,9 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", "unsigned-varint", @@ -6241,7 +6293,7 @@ dependencies = [ "tempfile", "throbber-widgets-tui", "tokio", - "tokio-util 0.7.12", + "tokio-util 0.7.13", "tracing", "tracing-error", "tracing-subscriber", @@ -6383,6 +6435,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "nybbles" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -6721,14 +6786,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 1.0.109", ] [[package]] @@ -6813,20 +6878,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -6834,9 +6899,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -6847,9 +6912,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -6967,9 +7032,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -7417,7 +7482,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.19", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -7436,7 +7501,7 @@ dependencies = [ "rustls 0.23.19", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -7444,9 +7509,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" dependencies = [ "cfg_aliases", "libc", @@ -7858,7 +7923,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.5.1", @@ -7880,7 +7945,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", "url", "wasm-bindgen", @@ -8121,15 +8186,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8727,6 +8792,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "sn_bls_ckd" @@ -8926,9 +8994,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" dependencies = [ "paste", "proc-macro2", @@ -9108,11 +9176,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.6", ] [[package]] @@ -9128,9 +9196,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -9179,9 +9247,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -9202,9 +9270,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -9269,9 +9337,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -9329,25 +9397,24 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls 0.23.19", - "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.12", + "tokio-util 0.7.13", ] [[package]] @@ -9378,9 +9445,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -9509,7 +9576,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.12", + "tokio-util 0.7.13", "tower-layer", "tower-service", "tracing", @@ -9734,7 +9801,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand 0.8.5", @@ -10047,7 +10114,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-tungstenite", - "tokio-util 0.7.12", + "tokio-util 0.7.13", "tower-service", "tracing", ] @@ -10066,9 +10133,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -10077,13 +10144,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -10092,9 +10158,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -10105,9 +10171,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10115,9 +10181,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -10128,19 +10194,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ "js-sys", "minicov", - "once_cell", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -10149,9 +10214,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", @@ -10173,11 +10238,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wasmtimer" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -10616,9 +10695,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" [[package]] name = "xmltree" diff --git a/ant-evm/src/data_payments.rs b/ant-evm/src/data_payments.rs index 89751e4d23..ddc1840998 100644 --- a/ant-evm/src/data_payments.rs +++ b/ant-evm/src/data_payments.rs @@ -6,10 +6,10 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::{AttoTokens, EvmError}; -use evmlib::common::TxHash; +use crate::EvmError; use evmlib::{ common::{Address as RewardsAddress, QuoteHash}, + quoting_metrics::QuotingMetrics, utils::dummy_address, }; use libp2p::{identity::PublicKey, PeerId}; @@ -26,59 +26,89 @@ pub const QUOTE_EXPIRATION_SECS: u64 = 3600; /// The margin allowed for live_time const LIVE_TIME_MARGIN: u64 = 10; +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct EncodedPeerId(Vec); + +impl EncodedPeerId { + pub fn to_peer_id(&self) -> Result { + PeerId::from_bytes(&self.0) + } +} + +impl From for EncodedPeerId { + fn from(peer_id: PeerId) -> Self { + let bytes = peer_id.to_bytes(); + EncodedPeerId(bytes) + } +} + /// The proof of payment for a data payment #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] pub struct ProofOfPayment { - /// The Quote we're paying for - pub quote: PaymentQuote, - /// The transaction hash - pub tx_hash: TxHash, + pub peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>, } impl ProofOfPayment { - pub fn to_peer_id_payee(&self) -> Option { - let pub_key = PublicKey::try_decode_protobuf(&self.quote.pub_key).ok()?; - Some(PeerId::from_public_key(&pub_key)) + /// returns a short digest of the proof of payment to use for verification + pub fn digest(&self) -> Vec<(QuoteHash, QuotingMetrics, RewardsAddress)> { + self.peer_quotes + .clone() + .into_iter() + .map(|(_, quote)| (quote.hash(), quote.quoting_metrics, quote.rewards_address)) + .collect() } -} -/// Quoting metrics that got used to generate a quote, or to track peer's status. -#[derive( - Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, custom_debug::Debug, -)] -pub struct QuotingMetrics { - /// the records stored - pub close_records_stored: usize, - /// the max_records configured - pub max_records: usize, - /// number of times that got paid - pub received_payment_count: usize, - /// the duration that node keeps connected to the network, measured in hours - pub live_time: u64, - /// network density from this node's perspective, which is the responsible_range as well - /// This could be calculated via sampling, or equation calculation. - pub network_density: Option<[u8; 32]>, - /// estimated network size - pub network_size: Option, -} + /// returns the list of payees + pub fn payees(&self) -> Vec { + self.peer_quotes + .iter() + .filter_map(|(peer_id, _)| peer_id.to_peer_id().ok()) + .collect() + } -impl QuotingMetrics { - /// construct an empty QuotingMetrics - pub fn new() -> Self { - Self { - close_records_stored: 0, - max_records: 0, - received_payment_count: 0, - live_time: 0, - network_density: None, - network_size: None, - } + /// has the quote expired + pub fn has_expired(&self) -> bool { + self.peer_quotes + .iter() + .any(|(_, quote)| quote.has_expired()) } -} -impl Default for QuotingMetrics { - fn default() -> Self { - Self::new() + /// Returns all quotes by given peer id + pub fn quotes_by_peer(&self, peer_id: &PeerId) -> Vec<&PaymentQuote> { + self.peer_quotes + .iter() + .filter_map(|(id, quote)| { + if let Ok(id) = id.to_peer_id() { + if id == *peer_id { + return Some(quote); + } + } + None + }) + .collect() + } + + /// verifies the proof of payment is valid for the given peer id + pub fn verify_for(&self, peer_id: PeerId) -> bool { + // make sure I am in the list of payees + if !self.payees().contains(&peer_id) { + return false; + } + + // verify all signatures + for (encoded_peer_id, quote) in self.peer_quotes.iter() { + let peer_id = match encoded_peer_id.to_peer_id() { + Ok(peer_id) => peer_id, + Err(e) => { + warn!("Invalid encoded peer id: {e}"); + return false; + } + }; + if !quote.check_is_signed_by_claimed_peer(peer_id) { + return false; + } + } + true } } @@ -89,17 +119,10 @@ impl Default for QuotingMetrics { pub struct PaymentQuote { /// the content paid for pub content: XorName, - /// how much the node demands for storing the content - /// TODO: to be removed once swtich to `client querying smart_contract` - pub cost: AttoTokens, /// the local node time when the quote was created pub timestamp: SystemTime, /// quoting metrics being used to generate this quote pub quoting_metrics: QuotingMetrics, - /// list of bad_nodes that client shall not pick as a payee - /// in `serialised` format to avoid cyclic dependent on ant_protocol - #[debug(skip)] - pub bad_nodes: Vec, /// the node's wallet address pub rewards_address: RewardsAddress, /// the node's libp2p identity public key in bytes (PeerId) @@ -115,10 +138,8 @@ impl PaymentQuote { pub fn zero() -> Self { Self { content: Default::default(), - cost: AttoTokens::zero(), timestamp: SystemTime::now(), quoting_metrics: Default::default(), - bad_nodes: vec![], rewards_address: dummy_address(), pub_key: vec![], signature: vec![], @@ -135,14 +156,11 @@ impl PaymentQuote { /// returns the bytes to be signed from the given parameters pub fn bytes_for_signing( xorname: XorName, - cost: AttoTokens, timestamp: SystemTime, quoting_metrics: &QuotingMetrics, - serialised_bad_nodes: &[u8], rewards_address: &RewardsAddress, ) -> Vec { let mut bytes = xorname.to_vec(); - bytes.extend_from_slice(&cost.to_bytes()); bytes.extend_from_slice( ×tamp .duration_since(SystemTime::UNIX_EPOCH) @@ -152,7 +170,6 @@ impl PaymentQuote { ); let serialised_quoting_metrics = rmp_serde::to_vec(quoting_metrics).unwrap_or_default(); bytes.extend_from_slice(&serialised_quoting_metrics); - bytes.extend_from_slice(serialised_bad_nodes); bytes.extend_from_slice(rewards_address.as_slice()); bytes } @@ -161,10 +178,8 @@ impl PaymentQuote { pub fn bytes_for_sig(&self) -> Vec { Self::bytes_for_signing( self.content, - self.cost, self.timestamp, &self.quoting_metrics, - &self.bad_nodes, &self.rewards_address, ) } @@ -205,7 +220,7 @@ impl PaymentQuote { true } - /// Returns true) if the quote has not yet expired + /// Returns true if the quote has expired pub fn has_expired(&self) -> bool { let now = SystemTime::now(); @@ -217,13 +232,11 @@ impl PaymentQuote { } /// test utility to create a dummy quote - pub fn test_dummy(xorname: XorName, cost: AttoTokens) -> Self { + pub fn test_dummy(xorname: XorName) -> Self { Self { content: xorname, - cost, timestamp: SystemTime::now(), quoting_metrics: Default::default(), - bad_nodes: vec![], pub_key: vec![], signature: vec![], rewards_address: dummy_address(), @@ -305,6 +318,14 @@ mod tests { use libp2p::identity::Keypair; use std::{thread::sleep, time::Duration}; + #[test] + fn test_encode_decode_peer_id() { + let id = PeerId::random(); + let encoded = EncodedPeerId::from(id); + let decoded = encoded.to_peer_id().expect("decode to work"); + assert_eq!(id, decoded); + } + #[test] fn test_is_newer_than() { let old_quote = PaymentQuote::zero(); diff --git a/ant-evm/src/lib.rs b/ant-evm/src/lib.rs index 45185101fb..10f557e395 100644 --- a/ant-evm/src/lib.rs +++ b/ant-evm/src/lib.rs @@ -13,6 +13,7 @@ pub use evmlib::common::Address as RewardsAddress; pub use evmlib::common::Address as EvmAddress; pub use evmlib::common::QuotePayment; pub use evmlib::common::{QuoteHash, TxHash}; +pub use evmlib::contract::payment_vault; pub use evmlib::cryptography; #[cfg(feature = "external-signer")] pub use evmlib::external_signer; @@ -28,7 +29,8 @@ mod amount; mod data_payments; mod error; -pub use data_payments::{PaymentQuote, ProofOfPayment, QuotingMetrics, QUOTE_EXPIRATION_SECS}; +pub use data_payments::{EncodedPeerId, PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS}; +pub use evmlib::quoting_metrics::QuotingMetrics; /// Types used in the public API pub use amount::{Amount, AttoTokens}; diff --git a/ant-networking/src/cmd.rs b/ant-networking/src/cmd.rs index cba58c1f3b..c76eb1ab0d 100644 --- a/ant-networking/src/cmd.rs +++ b/ant-networking/src/cmd.rs @@ -13,7 +13,7 @@ use crate::{ log_markers::Marker, multiaddr_pop_p2p, GetRecordCfg, GetRecordError, MsgResponder, NetworkEvent, CLOSE_GROUP_SIZE, }; -use ant_evm::{AttoTokens, PaymentQuote, QuotingMetrics}; +use ant_evm::{PaymentQuote, QuotingMetrics}; use ant_protocol::{ messages::{Cmd, Request, Response}, storage::{RecordHeader, RecordKind, RecordType}, @@ -98,10 +98,11 @@ pub enum LocalSwarmCmd { key: RecordKey, sender: oneshot::Sender>, }, - /// GetLocalStoreCost for this node, also with the bad_node list close to the target - GetLocalStoreCost { + /// GetLocalQuotingMetrics for this node + /// Returns the quoting metrics and whether the record at `key` is already stored locally + GetLocalQuotingMetrics { key: RecordKey, - sender: oneshot::Sender<(AttoTokens, QuotingMetrics, Vec)>, + sender: oneshot::Sender<(QuotingMetrics, bool)>, }, /// Notify the node received a payment. PaymentReceived, @@ -241,8 +242,8 @@ impl Debug for LocalSwarmCmd { "LocalSwarmCmd::GetCloseGroupLocalPeers {{ key: {key:?} }}" ) } - LocalSwarmCmd::GetLocalStoreCost { .. } => { - write!(f, "LocalSwarmCmd::GetLocalStoreCost") + LocalSwarmCmd::GetLocalQuotingMetrics { .. } => { + write!(f, "LocalSwarmCmd::GetLocalQuotingMetrics") } LocalSwarmCmd::PaymentReceived => { write!(f, "LocalSwarmCmd::PaymentReceived") @@ -573,8 +574,8 @@ impl SwarmDriver { cmd_string = "TriggerIntervalReplication"; self.try_interval_replication()?; } - LocalSwarmCmd::GetLocalStoreCost { key, sender } => { - cmd_string = "GetLocalStoreCost"; + LocalSwarmCmd::GetLocalQuotingMetrics { key, sender } => { + cmd_string = "GetLocalQuotingMetrics"; let ( _index, _total_peers, @@ -584,15 +585,14 @@ impl SwarmDriver { ) = self.kbuckets_status(); let estimated_network_size = Self::estimate_network_size(peers_in_non_full_buckets, num_of_full_buckets); - let (cost, quoting_metrics) = self + let (quoting_metrics, is_already_stored) = self .swarm .behaviour_mut() .kademlia .store_mut() - .store_cost(&key, Some(estimated_network_size as u64)); + .quoting_metrics(&key, Some(estimated_network_size as u64)); - self.record_metrics(Marker::StoreCost { - cost: cost.as_atto(), + self.record_metrics(Marker::QuotingMetrics { quoting_metrics: "ing_metrics, }); @@ -630,7 +630,7 @@ impl SwarmDriver { .retain(|peer_addr| key_address.distance(peer_addr) < boundary_distance); } - let _res = sender.send((cost, quoting_metrics, bad_nodes)); + let _res = sender.send((quoting_metrics, is_already_stored)); } LocalSwarmCmd::PaymentReceived => { cmd_string = "PaymentReceived"; diff --git a/ant-networking/src/error.rs b/ant-networking/src/error.rs index 9835e8f1d2..c683ff4432 100644 --- a/ant-networking/src/error.rs +++ b/ant-networking/src/error.rs @@ -178,7 +178,7 @@ pub enum NetworkError { OutgoingResponseDropped(Response), #[error("Error setting up behaviour: {0}")] - BahviourErr(String), + BehaviourErr(String), #[error("Register already exists at this address")] RegisterAlreadyExists, diff --git a/ant-networking/src/event/request_response.rs b/ant-networking/src/event/request_response.rs index d7a210821b..ce6755e8dc 100644 --- a/ant-networking/src/event/request_response.rs +++ b/ant-networking/src/event/request_response.rs @@ -48,30 +48,6 @@ impl SwarmDriver { self.add_keys_to_replication_fetcher(holder, keys); } - Request::Cmd(ant_protocol::messages::Cmd::QuoteVerification { - quotes, - .. - }) => { - let response = Response::Cmd( - ant_protocol::messages::CmdResponse::QuoteVerification(Ok(())), - ); - self.queue_network_swarm_cmd(NetworkSwarmCmd::SendResponse { - resp: response, - channel: MsgResponder::FromPeer(channel), - }); - - // The keypair is required to verify the quotes, - // hence throw it up to Network layer for further actions. - let quotes = quotes - .iter() - .filter_map(|(peer_address, quote)| { - peer_address - .as_peer_id() - .map(|peer_id| (peer_id, quote.clone())) - }) - .collect(); - self.send_event(NetworkEvent::QuoteVerification { quotes }) - } Request::Cmd(ant_protocol::messages::Cmd::PeerConsideredAsBad { detected_by, bad_peer, diff --git a/ant-networking/src/lib.rs b/ant-networking/src/lib.rs index c7dc9928f8..1ef8f78f9b 100644 --- a/ant-networking/src/lib.rs +++ b/ant-networking/src/lib.rs @@ -40,7 +40,7 @@ pub use self::{ }, error::{GetRecordError, NetworkError}, event::{MsgResponder, NetworkEvent}, - record_store::{calculate_cost_for_records, NodeRecordStore}, + record_store::NodeRecordStore, transactions::get_transactions_from_record, }; #[cfg(feature = "open-metrics")] @@ -48,10 +48,10 @@ pub use metrics::service::MetricsRegistries; pub use target_arch::{interval, sleep, spawn, Instant, Interval}; use self::{cmd::NetworkSwarmCmd, error::Result}; -use ant_evm::{AttoTokens, PaymentQuote, QuotingMetrics, RewardsAddress}; +use ant_evm::{PaymentQuote, QuotingMetrics}; use ant_protocol::{ error::Error as ProtocolError, - messages::{ChunkProof, Cmd, Nonce, Query, QueryResponse, Request, Response}, + messages::{ChunkProof, Nonce, Query, QueryResponse, Request, Response}, storage::{RecordType, RetryStrategy, Scratchpad}, NetworkAddress, PrettyPrintKBucketKey, PrettyPrintRecordKey, CLOSE_GROUP_SIZE, }; @@ -83,9 +83,6 @@ use { std::collections::HashSet, }; -/// The type of quote for a selected payee. -pub type PayeeQuote = (PeerId, RewardsAddress, PaymentQuote); - /// Majority of a given group (i.e. > 1/2). #[inline] pub const fn close_group_majority() -> usize { @@ -378,11 +375,11 @@ impl Network { /// /// Ignore the quote from any peers from `ignore_peers`. /// This is useful if we want to repay a different PeerId on failure. - pub async fn get_store_costs_from_network( + pub async fn get_store_quote_from_network( &self, record_address: NetworkAddress, ignore_peers: Vec, - ) -> Result { + ) -> Result> { // The requirement of having at least CLOSE_GROUP_SIZE // close nodes will be checked internally automatically. let mut close_nodes = self @@ -392,12 +389,12 @@ impl Network { close_nodes.retain(|peer_id| !ignore_peers.contains(peer_id)); if close_nodes.is_empty() { - error!("Cann't get store_cost of {record_address:?}, as all close_nodes are ignored"); + error!("Can't get store_cost of {record_address:?}, as all close_nodes are ignored"); return Err(NetworkError::NoStoreCostResponses); } // Client shall decide whether to carry out storage verification or not. - let request = Request::Query(Query::GetStoreCost { + let request = Request::Query(Query::GetStoreQuote { key: record_address.clone(), nonce: None, difficulty: 0, @@ -406,64 +403,58 @@ impl Network { .send_and_get_responses(&close_nodes, &request, true) .await; - // loop over responses, generating an average fee and storing all responses along side - let mut all_costs = vec![]; + // consider data to be already paid for if 1/2 of the close nodes already have it + let mut peer_already_have_it = 0; + let enough_peers_already_have_it = close_nodes.len() / 2; + + // loop over responses let mut all_quotes = vec![]; - for response in responses.into_values().flatten() { - info!( - "StoreCostReq for {record_address:?} received response: {:?}", - response - ); + let mut quotes_to_pay = vec![]; + for (peer, response) in responses { + info!("StoreCostReq for {record_address:?} received response: {response:?}"); match response { - Response::Query(QueryResponse::GetStoreCost { + Ok(Response::Query(QueryResponse::GetStoreQuote { quote: Ok(quote), - payment_address, peer_address, storage_proofs, - }) => { + })) => { if !storage_proofs.is_empty() { - debug!("Storage proofing during GetStoreCost to be implemented."); + debug!("Storage proofing during GetStoreQuote to be implemented."); } // Check the quote itself is valid. - if quote.cost - != AttoTokens::from_u64(calculate_cost_for_records( - quote.quoting_metrics.close_records_stored, - )) - { + if !quote.check_is_signed_by_claimed_peer(peer) { warn!("Received invalid quote from {peer_address:?}, {quote:?}"); continue; } - all_costs.push((peer_address.clone(), payment_address, quote.clone())); - all_quotes.push((peer_address, quote)); + all_quotes.push((peer_address.clone(), quote.clone())); + quotes_to_pay.push((peer, quote)); } - Response::Query(QueryResponse::GetStoreCost { + Ok(Response::Query(QueryResponse::GetStoreQuote { quote: Err(ProtocolError::RecordExists(_)), - payment_address, peer_address, storage_proofs, - }) => { + })) => { if !storage_proofs.is_empty() { - debug!("Storage proofing during GetStoreCost to be implemented."); + debug!("Storage proofing during GetStoreQuote to be implemented."); + } + peer_already_have_it += 1; + info!("Address {record_address:?} was already paid for according to {peer_address:?} ({peer_already_have_it}/{enough_peers_already_have_it})"); + if peer_already_have_it >= enough_peers_already_have_it { + info!("Address {record_address:?} was already paid for according to {peer_already_have_it} peers, ending quote request"); + return Ok(vec![]); } - all_costs.push((peer_address, payment_address, PaymentQuote::zero())); + } + Err(err) => { + error!("Got an error while requesting quote from peer {peer:?}: {err}"); } _ => { - error!("Non store cost response received, was {:?}", response); + error!("Got an unexpected response while requesting quote from peer {peer:?}: {response:?}"); } } } - for peer_id in close_nodes.iter() { - let request = Request::Cmd(Cmd::QuoteVerification { - target: NetworkAddress::from_peer(*peer_id), - quotes: all_quotes.clone(), - }); - - self.send_req_ignore_reply(request, *peer_id); - } - - get_fees_from_store_cost_responses(all_costs) + Ok(quotes_to_pay) } /// Get register from network. @@ -775,13 +766,13 @@ impl Network { Ok(None) } - /// Get the cost of storing the next record from the network - pub async fn get_local_storecost( + /// Get the quoting metrics for storing the next record from the network + pub async fn get_local_quoting_metrics( &self, key: RecordKey, - ) -> Result<(AttoTokens, QuotingMetrics, Vec)> { + ) -> Result<(QuotingMetrics, bool)> { let (sender, receiver) = oneshot::channel(); - self.send_local_swarm_cmd(LocalSwarmCmd::GetLocalStoreCost { key, sender }); + self.send_local_swarm_cmd(LocalSwarmCmd::GetLocalQuotingMetrics { key, sender }); receiver .await @@ -1208,42 +1199,6 @@ impl Network { } } -/// Given `all_costs` it will return the closest / lowest cost -/// Closest requiring it to be within CLOSE_GROUP nodes -fn get_fees_from_store_cost_responses( - all_costs: Vec<(NetworkAddress, RewardsAddress, PaymentQuote)>, -) -> Result { - // Find the minimum cost using a linear scan with random tie break - let mut rng = rand::thread_rng(); - let payee = all_costs - .into_iter() - .min_by( - |(_address_a, _main_key_a, cost_a), (_address_b, _main_key_b, cost_b)| { - let cmp = cost_a.cost.cmp(&cost_b.cost); - if cmp == std::cmp::Ordering::Equal { - if rng.gen() { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - } - } else { - cmp - } - }, - ) - .ok_or(NetworkError::NoStoreCostResponses)?; - - info!("Final fees calculated as: {payee:?}"); - // we dont need to have the address outside of here for now - let payee_id = if let Some(peer_id) = payee.0.as_peer_id() { - peer_id - } else { - error!("Can't get PeerId from payee {:?}", payee.0); - return Err(NetworkError::NoStoreCostResponses); - }; - Ok((payee_id, payee.1, payee.2)) -} - /// Get the value of the provided Quorum pub fn get_quorum_value(quorum: &Quorum) -> usize { match quorum { @@ -1368,69 +1323,7 @@ pub(crate) fn send_network_swarm_cmd( #[cfg(test)] mod tests { - use eyre::bail; - use super::*; - use ant_evm::PaymentQuote; - - #[test] - fn test_get_fee_from_store_cost_responses() -> Result<()> { - // for a vec of different costs of CLOSE_GROUP size - // ensure we return the CLOSE_GROUP / 2 indexed price - let mut costs = vec![]; - for i in 1..CLOSE_GROUP_SIZE { - let addr = ant_evm::utils::dummy_address(); - costs.push(( - NetworkAddress::from_peer(PeerId::random()), - addr, - PaymentQuote::test_dummy(Default::default(), AttoTokens::from_u64(i as u64)), - )); - } - let expected_price = costs[0].2.cost.as_atto(); - let (_peer_id, _key, price) = get_fees_from_store_cost_responses(costs)?; - - assert_eq!( - price.cost.as_atto(), - expected_price, - "price should be {expected_price}" - ); - - Ok(()) - } - - #[test] - fn test_get_some_fee_from_store_cost_responses_even_if_one_errs_and_sufficient( - ) -> eyre::Result<()> { - // for a vec of different costs of CLOSE_GROUP size - let responses_count = CLOSE_GROUP_SIZE as u64 - 1; - let mut costs = vec![]; - for i in 1..responses_count { - // push random addr and Nano - let addr = ant_evm::utils::dummy_address(); - costs.push(( - NetworkAddress::from_peer(PeerId::random()), - addr, - PaymentQuote::test_dummy(Default::default(), AttoTokens::from_u64(i)), - )); - println!("price added {i}"); - } - - // this should be the lowest price - let expected_price = costs[0].2.cost.as_atto(); - - let (_peer_id, _key, price) = match get_fees_from_store_cost_responses(costs) { - Err(_) => bail!("Should not have errored as we have enough responses"), - Ok(cost) => cost, - }; - - assert_eq!( - price.cost.as_atto(), - expected_price, - "price should be {expected_price}" - ); - - Ok(()) - } #[test] fn test_network_sign_verify() -> eyre::Result<()> { diff --git a/ant-networking/src/log_markers.rs b/ant-networking/src/log_markers.rs index 99bcd6726d..71787c0a65 100644 --- a/ant-networking/src/log_markers.rs +++ b/ant-networking/src/log_markers.rs @@ -6,7 +6,7 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use ant_evm::{Amount, QuotingMetrics}; +use ant_evm::QuotingMetrics; use libp2p::PeerId; // this gets us to_string easily enough use strum::Display; @@ -19,12 +19,8 @@ use strum::Display; pub enum Marker<'a> { /// Close records held (Used in VDash) CloseRecordsLen(usize), - /// Store cost - StoreCost { - /// Cost - cost: Amount, - quoting_metrics: &'a QuotingMetrics, - }, + /// Quoting metrics + QuotingMetrics { quoting_metrics: &'a QuotingMetrics }, /// The peer has been considered as bad PeerConsideredAsBad { bad_peer: &'a PeerId }, /// We have been flagged as a bad node by a peer. diff --git a/ant-networking/src/metrics/mod.rs b/ant-networking/src/metrics/mod.rs index cb0081d963..89e63202de 100644 --- a/ant-networking/src/metrics/mod.rs +++ b/ant-networking/src/metrics/mod.rs @@ -45,8 +45,7 @@ pub(crate) struct NetworkMetricsRecorder { pub(crate) peers_in_routing_table: Gauge, pub(crate) records_stored: Gauge, - // store cost - store_cost: Gauge, + // quoting metrics relevant_records: Gauge, max_records: Gauge, received_payment_count: Gauge, @@ -149,13 +148,7 @@ impl NetworkMetricsRecorder { process_cpu_usage_percentage.clone(), ); - // store cost - let store_cost = Gauge::default(); - sub_registry.register( - "store_cost", - "The store cost of the node", - store_cost.clone(), - ); + // quoting metrics let relevant_records = Gauge::default(); sub_registry.register( "relevant_records", @@ -222,7 +215,6 @@ impl NetworkMetricsRecorder { connected_peers, open_connections, peers_in_routing_table, - store_cost, relevant_records, max_records, received_payment_count, @@ -292,11 +284,7 @@ impl NetworkMetricsRecorder { } }); } - Marker::StoreCost { - cost, - quoting_metrics, - } => { - let _ = self.store_cost.set(cost.try_into().unwrap_or(i64::MAX)); + Marker::QuotingMetrics { quoting_metrics } => { let _ = self.relevant_records.set( quoting_metrics .close_records_stored diff --git a/ant-networking/src/record_store.rs b/ant-networking/src/record_store.rs index 744a7fd807..16f7917abe 100644 --- a/ant-networking/src/record_store.rs +++ b/ant-networking/src/record_store.rs @@ -16,7 +16,7 @@ use aes_gcm_siv::{ aead::{Aead, KeyInit}, Aes256GcmSiv, Key as AesKey, Nonce, }; -use ant_evm::{AttoTokens, QuotingMetrics}; +use ant_evm::QuotingMetrics; use ant_protocol::{ storage::{RecordHeader, RecordKind, RecordType}, NetworkAddress, PrettyPrintRecordKey, @@ -61,12 +61,6 @@ const MAX_RECORDS_CACHE_SIZE: usize = 25; /// File name of the recorded historical quoting metrics. const HISTORICAL_QUOTING_METRICS_FILENAME: &str = "historic_quoting_metrics"; -/// Max store cost for a chunk. -const MAX_STORE_COST: u64 = 1_000_000; - -// Min store cost for a chunk. -const MIN_STORE_COST: u64 = 1; - fn derive_aes256gcm_siv_from_seed(seed: &[u8; 16]) -> (Aes256GcmSiv, [u8; 4]) { // shall be unique for purpose. let salt = b"autonomi_record_store"; @@ -724,12 +718,13 @@ impl NodeRecordStore { Ok(()) } - /// Calculate the cost to store data for our current store state - pub(crate) fn store_cost( + /// Return the quoting metrics used to calculate the cost of storing a record + /// and whether the record is already stored locally + pub(crate) fn quoting_metrics( &self, key: &Key, network_size: Option, - ) -> (AttoTokens, QuotingMetrics) { + ) -> (QuotingMetrics, bool) { let records_stored = self.records.len(); let live_time = if let Ok(elapsed) = self.timestamp.elapsed() { @@ -758,15 +753,12 @@ impl NodeRecordStore { info!("Basing cost of _total_ records stored."); }; - let cost = if self.contains(key) { - 0 - } else { - calculate_cost_for_records(quoting_metrics.close_records_stored) - }; + // NB TODO tell happybeing! // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): - info!("Cost is now {cost:?} for quoting_metrics {quoting_metrics:?}"); + info!("Quoting_metrics {quoting_metrics:?}"); - (AttoTokens::from_u64(cost), quoting_metrics) + let is_stored = self.contains(key); + (quoting_metrics, is_stored) } /// Notify the node received a payment. @@ -1002,39 +994,13 @@ impl RecordStore for ClientRecordStore { fn remove_provider(&mut self, _key: &Key, _provider: &PeerId) {} } -// Using a linear growth function tweaked by `max_records`, -// and gives an exponential pricing curve when storage reaches high. -// and give extra reward (lower the quoting price to gain a better chance) to long lived nodes. -pub fn calculate_cost_for_records(records_stored: usize) -> u64 { - use std::cmp::{max, min}; - - let max_records = MAX_RECORDS_COUNT; - - let ori_cost = positive_input_0_1_sigmoid(records_stored as f64 / max_records as f64) - * MAX_STORE_COST as f64; - - // Deploy a lower cap safe_guard to the store_cost - let charge = max(MIN_STORE_COST, ori_cost as u64); - // Deploy an upper cap safe_guard to the store_cost - min(MAX_STORE_COST, charge) -} - -fn positive_input_0_1_sigmoid(x: f64) -> f64 { - 1.0 / (1.0 + (-30.0 * (x - 0.5)).exp()) -} - #[expect(trivial_casts)] #[cfg(test)] mod tests { - - use crate::get_fees_from_store_cost_responses; - use super::*; use bls::SecretKey; use xor_name::XorName; - use ant_evm::utils::dummy_address; - use ant_evm::{PaymentQuote, RewardsAddress}; use ant_protocol::storage::{ try_deserialize_record, try_serialize_record, Chunk, ChunkAddress, Scratchpad, }; @@ -1043,12 +1009,9 @@ mod tests { TempDir, }; use bytes::Bytes; - use eyre::{bail, ContextCompat}; - use libp2p::kad::K_VALUE; + use eyre::ContextCompat; use libp2p::{core::multihash::Multihash, kad::RecordKey}; use quickcheck::*; - use std::collections::BTreeMap; - use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use tokio::runtime::Runtime; use tokio::time::{sleep, Duration}; @@ -1087,70 +1050,6 @@ mod tests { } } - #[test] - fn test_calculate_max_cost_for_records() { - let sut = calculate_cost_for_records(MAX_RECORDS_COUNT + 1); - assert_eq!(sut, MAX_STORE_COST - 1); - } - - #[test] - fn test_calculate_50_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 50 / 100; - let sut = calculate_cost_for_records(percent); - - // at this point we should be at max cost - assert_eq!(sut, 500000); - } - #[test] - fn test_calculate_60_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 60 / 100; - let sut = calculate_cost_for_records(percent); - - // at this point we should be at max cost - assert_eq!(sut, 952541); - } - - #[test] - fn test_calculate_65_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 65 / 100; - let sut = calculate_cost_for_records(percent); - - // at this point we should be at max cost - assert_eq!(sut, 989001); - } - - #[test] - fn test_calculate_70_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 70 / 100; - let sut = calculate_cost_for_records(percent); - - // at this point we should be at max cost - assert_eq!(sut, 997523); - } - - #[test] - fn test_calculate_80_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 80 / 100; - let sut = calculate_cost_for_records(percent); - - // at this point we should be at max cost - assert_eq!(sut, 999876); - } - - #[test] - fn test_calculate_90_percent_cost_for_records() { - let percent = MAX_RECORDS_COUNT * 90 / 100; - let sut = calculate_cost_for_records(percent); - // at this point we should be at max cost - assert_eq!(sut, 999993); - } - - #[test] - fn test_calculate_min_cost_for_records() { - let sut = calculate_cost_for_records(0); - assert_eq!(sut, MIN_STORE_COST); - } - #[test] fn put_get_remove_record() { fn prop(r: ArbitraryRecord) { @@ -1176,16 +1075,9 @@ mod tests { swarm_cmd_sender, ); - let store_cost_before = store.store_cost(&r.key, None); // An initial unverified put should not write to disk assert!(store.put(r.clone()).is_ok()); assert!(store.get(&r.key).is_none()); - // Store cost should not change if no PUT has been added - assert_eq!( - store.store_cost(&r.key, None).0, - store_cost_before.0, - "store cost should not change over unverified put" - ); let returned_record = if let Some(event) = network_event_receiver.recv().await { if let NetworkEvent::UnverifiedRecord(record) = event { @@ -1736,255 +1628,4 @@ mod tests { Ok(()) } - - struct PeerStats { - address: NetworkAddress, - rewards_addr: RewardsAddress, - records_stored: AtomicUsize, - nanos_earned: AtomicU64, - payments_received: AtomicUsize, - } - - // takes a long time to run - #[ignore] - #[test] - fn address_distribution_sim() { - use rayon::prelude::*; - - // as network saturates, we can see that peers all eventually earn similarly - let num_of_peers = 5_000; - let num_of_chunks_per_hour = 1_000_000; - let max_hours = 50; - - // - let k = K_VALUE.get(); - - let replication_group_size = k / 3; - - // Initialize peers with random addresses - let mut peers: Vec = (0..num_of_peers) - .into_par_iter() - .map(|_| PeerStats { - address: NetworkAddress::from_peer(PeerId::random()), - records_stored: AtomicUsize::new(0), - nanos_earned: AtomicU64::new(0), - payments_received: AtomicUsize::new(0), - rewards_addr: dummy_address(), - }) - .collect(); - - let mut hour = 0; - let mut total_received_payment_count = 0; - - let peers_len = peers.len(); - - // Generate a random sorting target address - let sorting_target_address = - NetworkAddress::from_chunk_address(ChunkAddress::new(XorName::default())); - - // Sort all peers based on their distance to the sorting target - peers.par_sort_by(|a, b| { - sorting_target_address - .distance(&a.address) - .cmp(&sorting_target_address.distance(&b.address)) - }); - - loop { - // Parallel processing of chunks - let _chunk_results: Vec<_> = (0..num_of_chunks_per_hour) - .into_par_iter() - .map(|_| { - // Generate a random chunk address - let name = xor_name::rand::random(); - let chunk_address = NetworkAddress::from_chunk_address(ChunkAddress::new(name)); - - let chunk_distance_to_sorting = sorting_target_address.distance(&chunk_address); - // Binary search to find the insertion point for the chunk - let partition_point = peers.partition_point(|peer| { - sorting_target_address.distance(&peer.address) < chunk_distance_to_sorting - }); - - // Collect close_group_size closest peers - let mut close_group = Vec::with_capacity(replication_group_size); - let mut left = partition_point; - let mut right = partition_point; - - while close_group.len() < replication_group_size - && (left > 0 || right < peers_len) - { - if left > 0 { - left -= 1; - close_group.push(left); - } - if close_group.len() < replication_group_size && right < peers_len { - close_group.push(right); - right += 1; - } - } - - // Truncate to ensure we have exactly close_group_size peers - close_group.truncate(replication_group_size); - - // Find the cheapest payee among the close group - let Ok((payee_index, cost)) = pick_cheapest_payee(&peers, &close_group) else { - bail!("Failed to find a payee"); - }; - - for &peer_index in &close_group { - let peer = &peers[peer_index]; - peer.records_stored.fetch_add(1, Ordering::Relaxed); - - if peer_index == payee_index { - peer.nanos_earned.fetch_add( - cost.as_atto().try_into().unwrap_or(u64::MAX), - Ordering::Relaxed, - ); - peer.payments_received.fetch_add(1, Ordering::Relaxed); - } - } - - Ok(()) - }) - .collect(); - - // Parallel reduction to calculate statistics - let ( - received_payment_count, - empty_earned_nodes, - min_earned, - max_earned, - min_store_cost, - max_store_cost, - ) = peers - .par_iter() - .map(|peer| { - let cost = - calculate_cost_for_records(peer.records_stored.load(Ordering::Relaxed)); - let earned = peer.nanos_earned.load(Ordering::Relaxed); - ( - peer.payments_received.load(Ordering::Relaxed), - if earned == 0 { 1 } else { 0 }, - earned, - earned, - cost, - cost, - ) - }) - .reduce( - || (0, 0, u64::MAX, 0, u64::MAX, 0), - |a, b| { - let ( - a_received_payment_count, - a_empty_earned_nodes, - a_min_earned, - a_max_earned, - a_min_store_cost, - a_max_store_cost, - ) = a; - let ( - b_received_payment_count, - b_empty_earned_nodes, - b_min_earned, - b_max_earned, - b_min_store_cost, - b_max_store_cost, - ) = b; - ( - a_received_payment_count + b_received_payment_count, - a_empty_earned_nodes + b_empty_earned_nodes, - a_min_earned.min(b_min_earned), - a_max_earned.max(b_max_earned), - a_min_store_cost.min(b_min_store_cost), - a_max_store_cost.max(b_max_store_cost), - ) - }, - ); - - total_received_payment_count += num_of_chunks_per_hour; - assert_eq!(total_received_payment_count, received_payment_count); - - println!("After the completion of hour {hour} with {num_of_chunks_per_hour} chunks put, there are {empty_earned_nodes} nodes which earned nothing"); - println!("\t\t with storecost variation of (min {min_store_cost} - max {max_store_cost}), and earned variation of (min {min_earned} - max {max_earned})"); - - hour += 1; - - // Check termination condition - if hour == max_hours { - let acceptable_percentage = 0.01; //% - - // Calculate acceptable empty nodes based on % of total nodes - let acceptable_empty_nodes = - (num_of_peers as f64 * acceptable_percentage).ceil() as usize; - - // Assert conditions for termination - assert!( - empty_earned_nodes <= acceptable_empty_nodes, - "More than {acceptable_percentage}% of nodes ({acceptable_empty_nodes}) still not earning: {empty_earned_nodes}" - ); - assert!( - (max_store_cost / min_store_cost) < 1000000, - "store cost is not 'balanced', expected ratio max/min to be < 1000000, but was {}", - max_store_cost / min_store_cost - ); - assert!( - (max_earned / min_earned) < 500000000, - "earning distribution is not balanced, expected to be < 500000000, but was {}", - max_earned / min_earned - ); - break; - } - } - } - - fn pick_cheapest_payee( - peers: &[PeerStats], - close_group: &[usize], - ) -> eyre::Result<(usize, AttoTokens)> { - let mut costs_vec = Vec::with_capacity(close_group.len()); - let mut address_to_index = BTreeMap::new(); - - for &i in close_group { - let peer = &peers[i]; - address_to_index.insert(peer.address.clone(), i); - - let close_records_stored = peer.records_stored.load(Ordering::Relaxed); - let cost = AttoTokens::from(calculate_cost_for_records(close_records_stored)); - - let quote = PaymentQuote { - content: XorName::default(), // unimportant for cost calc - cost, - timestamp: std::time::SystemTime::now(), - quoting_metrics: QuotingMetrics { - close_records_stored: peer.records_stored.load(Ordering::Relaxed), - max_records: MAX_RECORDS_COUNT, - received_payment_count: 1, // unimportant for cost calc - live_time: 0, // unimportant for cost calc - network_density: None, - network_size: None, - }, - bad_nodes: vec![], - pub_key: bls::SecretKey::random().public_key().to_bytes().to_vec(), - signature: vec![], - rewards_address: peer.rewards_addr, // unimportant for cost calc - }; - - costs_vec.push((peer.address.clone(), peer.rewards_addr, quote)); - } - - // sort by address first - costs_vec.sort_by(|(a_addr, _, _), (b_addr, _, _)| a_addr.cmp(b_addr)); - - let Ok((recip_id, _pk, q)) = get_fees_from_store_cost_responses(costs_vec) else { - bail!("Failed to get fees from store cost responses") - }; - - let Some(index) = address_to_index - .get(&NetworkAddress::from_peer(recip_id)) - .copied() - else { - bail!("Cannot find the index for the cheapest payee"); - }; - - Ok((index, q.cost)) - } } diff --git a/ant-networking/src/record_store_api.rs b/ant-networking/src/record_store_api.rs index 7923c0d1b3..2aeb33a9a2 100644 --- a/ant-networking/src/record_store_api.rs +++ b/ant-networking/src/record_store_api.rs @@ -8,7 +8,7 @@ #![allow(clippy::mutable_key_type)] // for the Bytes in NetworkAddress use crate::record_store::{ClientRecordStore, NodeRecordStore}; -use ant_evm::{AttoTokens, QuotingMetrics}; +use ant_evm::QuotingMetrics; use ant_protocol::{storage::RecordType, NetworkAddress}; use libp2p::kad::{ store::{RecordStore, Result}, @@ -111,17 +111,19 @@ impl UnifiedRecordStore { } } - pub(crate) fn store_cost( + /// Return the quoting metrics used to calculate the cost of storing a record + /// and whether the record is already stored locally + pub(crate) fn quoting_metrics( &self, key: &RecordKey, network_size: Option, - ) -> (AttoTokens, QuotingMetrics) { + ) -> (QuotingMetrics, bool) { match self { Self::Client(_) => { - warn!("Calling store cost calculation at Client. This should not happen"); - (AttoTokens::zero(), Default::default()) + warn!("Calling quoting metrics calculation at Client. This should not happen"); + Default::default() } - Self::Node(store) => store.store_cost(key, network_size), + Self::Node(store) => store.quoting_metrics(key, network_size), } } diff --git a/ant-node/src/error.rs b/ant-node/src/error.rs index 86aba2df5c..e17c4ab111 100644 --- a/ant-node/src/error.rs +++ b/ant-node/src/error.rs @@ -69,9 +69,8 @@ pub enum Error { /// Missing network royalties payment #[error("Missing network royalties payment in proof received with record: {0:?}.")] NoNetworkRoyaltiesPayment(PrettyPrintRecordKey<'static>), - /// The amount paid by payment proof is not the required for the received content - #[error("The amount paid by payment proof is not the required for the received content, paid {paid}, expected {expected}")] - PaymentProofInsufficientAmount { + #[error("The amount paid is less than the storecost, paid {paid}, expected {expected}")] + PaymentInsufficientAmount { paid: AttoTokens, expected: AttoTokens, }, diff --git a/ant-node/src/node.rs b/ant-node/src/node.rs index c1ea235239..36a7c3e765 100644 --- a/ant-node/src/node.rs +++ b/ant-node/src/node.rs @@ -12,7 +12,7 @@ use super::{ #[cfg(feature = "open-metrics")] use crate::metrics::NodeMetricsRecorder; use crate::RunningNode; -use ant_evm::{AttoTokens, RewardsAddress}; +use ant_evm::RewardsAddress; #[cfg(feature = "open-metrics")] use ant_networking::MetricsRegistries; use ant_networking::{Instant, Network, NetworkBuilder, NetworkEvent, NodeIssue, SwarmDriver}; @@ -552,16 +552,17 @@ impl Node { payment_address: RewardsAddress, ) -> Response { let resp: QueryResponse = match query { - Query::GetStoreCost { + Query::GetStoreQuote { key, nonce, difficulty, } => { - debug!("Got GetStoreCost request for {key:?} with difficulty {difficulty}"); + debug!("Got GetStoreQuote request for {key:?} with difficulty {difficulty}"); let record_key = key.to_record_key(); let self_id = network.peer_id(); - let store_cost = network.get_local_storecost(record_key.clone()).await; + let maybe_quoting_metrics = + network.get_local_quoting_metrics(record_key.clone()).await; let storage_proofs = if let Some(nonce) = nonce { Self::respond_x_closest_record_proof( @@ -576,39 +577,37 @@ impl Node { vec![] }; - match store_cost { - Ok((cost, quoting_metrics, bad_nodes)) => { - if cost == AttoTokens::zero() { - QueryResponse::GetStoreCost { + match maybe_quoting_metrics { + Ok((quoting_metrics, is_already_stored)) => { + if is_already_stored { + QueryResponse::GetStoreQuote { quote: Err(ProtocolError::RecordExists( PrettyPrintRecordKey::from(&record_key).into_owned(), )), - payment_address, peer_address: NetworkAddress::from_peer(self_id), storage_proofs, } } else { - QueryResponse::GetStoreCost { + QueryResponse::GetStoreQuote { quote: Self::create_quote_for_storecost( network, - cost, &key, "ing_metrics, - bad_nodes, &payment_address, ), - payment_address, peer_address: NetworkAddress::from_peer(self_id), storage_proofs, } } } - Err(_) => QueryResponse::GetStoreCost { - quote: Err(ProtocolError::GetStoreCostFailed), - payment_address, - peer_address: NetworkAddress::from_peer(self_id), - storage_proofs, - }, + Err(err) => { + warn!("GetStoreQuote failed for {key:?}: {err}"); + QueryResponse::GetStoreQuote { + quote: Err(ProtocolError::GetStoreQuoteFailed), + peer_address: NetworkAddress::from_peer(self_id), + storage_proofs, + } + } } } Query::GetRegisterRecord { requester, key } => { diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index 29876081b9..3709ca3bc7 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -7,7 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::{node::Node, Error, Marker, Result}; -use ant_evm::{ProofOfPayment, QUOTE_EXPIRATION_SECS}; +use ant_evm::payment_vault::verify_data_payment; +use ant_evm::{AttoTokens, ProofOfPayment}; use ant_networking::NetworkError; use ant_protocol::storage::Transaction; use ant_protocol::{ @@ -19,7 +20,6 @@ use ant_protocol::{ }; use ant_registers::SignedRegister; use libp2p::kad::{Record, RecordKey}; -use std::time::{Duration, UNIX_EPOCH}; use xor_name::XorName; impl Node { @@ -602,41 +602,46 @@ impl Node { debug!("Validating record payment for {pretty_key}"); // check if the quote is valid - let storecost = payment.quote.cost; let self_peer_id = self.network().peer_id(); - if !payment.quote.check_is_signed_by_claimed_peer(self_peer_id) { - warn!("Payment quote signature is not valid for record {pretty_key}"); + if !payment.verify_for(self_peer_id) { + warn!("Payment is not valid for record {pretty_key}"); return Err(Error::InvalidRequest(format!( - "Payment quote signature is not valid for record {pretty_key}" + "Payment is not valid for record {pretty_key}" ))); } - debug!("Payment quote signature is valid for record {pretty_key}"); - - // verify quote timestamp - let quote_timestamp = payment.quote.timestamp; - let quote_expiration_time = quote_timestamp + Duration::from_secs(QUOTE_EXPIRATION_SECS); - let quote_expiration_time_in_secs = quote_expiration_time - .duration_since(UNIX_EPOCH) - .map_err(|e| { - Error::InvalidRequest(format!( - "Payment quote timestamp is invalid for record {pretty_key}: {e}" - )) - })? - .as_secs(); + debug!("Payment is valid for record {pretty_key}"); + // verify quote expiration + if payment.has_expired() { + warn!("Payment quote has expired for record {pretty_key}"); + return Err(Error::InvalidRequest(format!( + "Payment quote has expired for record {pretty_key}" + ))); + } + + // verify the claimed payees are all known to us within the certain range. + let closest_k_peers = self.network().get_closest_k_value_local_peers().await?; + let mut payees = payment.payees(); + payees.retain(|peer_id| !closest_k_peers.contains(peer_id)); + if !payees.is_empty() { + return Err(Error::InvalidRequest(format!( + "Payment quote has out-of-range payees {payees:?}" + ))); + } + + let owned_payment_quotes = payment + .quotes_by_peer(&self_peer_id) + .iter() + .map(|quote| quote.hash()) + .collect(); // check if payment is valid on chain + let payments_to_verify = payment.digest(); debug!("Verifying payment for record {pretty_key}"); - self.evm_network() - .verify_data_payment( - payment.tx_hash, - payment.quote.hash(), - *self.reward_address(), - storecost.as_atto(), - quote_expiration_time_in_secs, - ) - .await - .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; - debug!("Payment is valid for record {pretty_key}"); + let reward_amount = + verify_data_payment(self.evm_network(), owned_payment_quotes, payments_to_verify) + .await + .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; + debug!("Payment of {reward_amount:?} is valid for record {pretty_key}"); // Notify `record_store` that the node received a payment. self.network().notify_payment_received(); @@ -646,22 +651,27 @@ impl Node { // FIXME: We would reach the MAX if the storecost is scaled up. let current_value = metrics_recorder.current_reward_wallet_balance.get(); let new_value = - current_value.saturating_add(storecost.as_atto().try_into().unwrap_or(i64::MAX)); + current_value.saturating_add(reward_amount.try_into().unwrap_or(i64::MAX)); let _ = metrics_recorder .current_reward_wallet_balance .set(new_value); } self.events_channel() - .broadcast(crate::NodeEvent::RewardReceived(storecost, address.clone())); + .broadcast(crate::NodeEvent::RewardReceived( + AttoTokens::from(reward_amount), + address.clone(), + )); // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): - info!("Total payment of {storecost:?} atto tokens accepted for record {pretty_key}"); + info!("Total payment of {reward_amount:?} atto tokens accepted for record {pretty_key}"); // loud mode: print a celebratory message to console #[cfg(feature = "loud")] { println!("🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟 RECEIVED REWARD 🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟"); - println!("Total payment of {storecost:?} atto tokens accepted for record {pretty_key}"); + println!( + "Total payment of {reward_amount:?} atto tokens accepted for record {pretty_key}" + ); println!("🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟"); } diff --git a/ant-node/src/quote.rs b/ant-node/src/quote.rs index fa3defd843..f7c61b2af8 100644 --- a/ant-node/src/quote.rs +++ b/ant-node/src/quote.rs @@ -7,8 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::{node::Node, Error, Result}; -use ant_evm::{AttoTokens, PaymentQuote, QuotingMetrics, RewardsAddress}; -use ant_networking::{calculate_cost_for_records, Network, NodeIssue}; +use ant_evm::{PaymentQuote, QuotingMetrics, RewardsAddress}; +use ant_networking::Network; use ant_protocol::{error::Error as ProtocolError, storage::ChunkAddress, NetworkAddress}; use libp2p::PeerId; use std::time::Duration; @@ -16,23 +16,14 @@ use std::time::Duration; impl Node { pub(crate) fn create_quote_for_storecost( network: &Network, - cost: AttoTokens, address: &NetworkAddress, quoting_metrics: &QuotingMetrics, - bad_nodes: Vec, payment_address: &RewardsAddress, ) -> Result { let content = address.as_xorname().unwrap_or_default(); let timestamp = std::time::SystemTime::now(); - let serialised_bad_nodes = rmp_serde::to_vec(&bad_nodes).unwrap_or_default(); - let bytes = PaymentQuote::bytes_for_signing( - content, - cost, - timestamp, - quoting_metrics, - &serialised_bad_nodes, - payment_address, - ); + let bytes = + PaymentQuote::bytes_for_signing(content, timestamp, quoting_metrics, payment_address); let Ok(signature) = network.sign(&bytes) else { return Err(ProtocolError::QuoteGenerationFailed); @@ -40,10 +31,8 @@ impl Node { let quote = PaymentQuote { content, - cost, timestamp, quoting_metrics: quoting_metrics.clone(), - bad_nodes: serialised_bad_nodes, pub_key: network.get_pub_key(), rewards_address: *payment_address, signature, @@ -87,8 +76,7 @@ pub(crate) fn verify_quote_for_storecost( // 3, quote is no longer valid // // Following metrics will be considered as node's bad quote. -// 1, Price calculation is incorrect -// 2, QuoteMetrics doesn't match the historical quotes collected by self +// 1, QuoteMetrics doesn't match the historical quotes collected by self pub(crate) async fn quotes_verification(network: &Network, quotes: Vec<(PeerId, PaymentQuote)>) { // Do nothing if self is not one of the quoters. if let Some((_, self_quote)) = quotes @@ -98,12 +86,11 @@ pub(crate) async fn quotes_verification(network: &Network, quotes: Vec<(PeerId, let target_address = NetworkAddress::from_chunk_address(ChunkAddress::new(self_quote.content)); if verify_quote_for_storecost(network, self_quote.clone(), &target_address).is_ok() { - let mut quotes_for_nodes_duty: Vec<_> = quotes + let quotes_for_nodes_duty: Vec<_> = quotes .iter() .filter(|(peer_id, quote)| { let is_same_target = quote.content == self_quote.content; let is_not_self = *peer_id != network.peer_id(); - let is_not_zero_quote = quote.cost != AttoTokens::zero(); let time_gap = Duration::from_secs(10); let is_around_same_time = if quote.timestamp > self_quote.timestamp { @@ -117,25 +104,12 @@ pub(crate) async fn quotes_verification(network: &Network, quotes: Vec<(PeerId, is_same_target && is_not_self - && is_not_zero_quote && is_around_same_time && is_signed_by_the_claimed_peer }) .cloned() .collect(); - quotes_for_nodes_duty.retain(|(peer_id, quote)| { - let cost = calculate_cost_for_records(quote.quoting_metrics.close_records_stored); - let is_same_as_expected = quote.cost == AttoTokens::from_u64(cost); - - if !is_same_as_expected { - info!("Quote from {peer_id:?} using a different quoting_metrics to achieve the claimed cost. Quote {quote:?} can only result in cost {cost:?}"); - network.record_node_issues(*peer_id, NodeIssue::BadQuoting); - } - - is_same_as_expected - }); - // Pass down to swarm_driver level for further bad quote detection // against historical collected quotes. network.historical_verify_quotes(quotes_for_nodes_duty); diff --git a/ant-protocol/src/error.rs b/ant-protocol/src/error.rs index 7db10f9612..bc784860e1 100644 --- a/ant-protocol/src/error.rs +++ b/ant-protocol/src/error.rs @@ -57,7 +57,7 @@ pub enum Error { // ---------- payment errors #[error("There was an error getting the storecost from kademlia store")] - GetStoreCostFailed, + GetStoreQuoteFailed, #[error("There was an error generating the payment quote")] QuoteGenerationFailed, diff --git a/ant-protocol/src/messages/cmd.rs b/ant-protocol/src/messages/cmd.rs index cec0629259..1437c6540b 100644 --- a/ant-protocol/src/messages/cmd.rs +++ b/ant-protocol/src/messages/cmd.rs @@ -8,7 +8,6 @@ #![allow(clippy::mutable_key_type)] // for Bytes in NetworkAddress use crate::{storage::RecordType, NetworkAddress}; -pub use ant_evm::PaymentQuote; use serde::{Deserialize, Serialize}; /// Data and CashNote cmds - recording transactions or creating, updating, and removing data. @@ -28,11 +27,6 @@ pub enum Cmd { /// Keys of copy that shall be replicated. keys: Vec<(NetworkAddress, RecordType)>, }, - /// Write operation to notify nodes a list of PaymentQuote collected. - QuoteVerification { - target: NetworkAddress, - quotes: Vec<(NetworkAddress, PaymentQuote)>, - }, /// Notify the peer it is now being considered as BAD due to the included behaviour PeerConsideredAsBad { detected_by: NetworkAddress, @@ -52,11 +46,6 @@ impl std::fmt::Debug for Cmd { .field("first_ten_keys", &first_ten_keys) .finish() } - Cmd::QuoteVerification { target, quotes } => f - .debug_struct("Cmd::QuoteVerification") - .field("target", target) - .field("quotes_len", "es.len()) - .finish(), Cmd::PeerConsideredAsBad { detected_by, bad_peer, @@ -76,7 +65,6 @@ impl Cmd { pub fn dst(&self) -> NetworkAddress { match self { Cmd::Replicate { holder, .. } => holder.clone(), - Cmd::QuoteVerification { target, .. } => target.clone(), Cmd::PeerConsideredAsBad { bad_peer, .. } => bad_peer.clone(), } } @@ -93,13 +81,6 @@ impl std::fmt::Display for Cmd { keys.len() ) } - Cmd::QuoteVerification { target, quotes } => { - write!( - f, - "Cmd::QuoteVerification(sent to {target:?} has {} quotes)", - quotes.len() - ) - } Cmd::PeerConsideredAsBad { detected_by, bad_peer, diff --git a/ant-protocol/src/messages/query.rs b/ant-protocol/src/messages/query.rs index 60392d7651..b685ad524e 100644 --- a/ant-protocol/src/messages/query.rs +++ b/ant-protocol/src/messages/query.rs @@ -18,9 +18,9 @@ use serde::{Deserialize, Serialize}; /// [`protocol`]: crate #[derive(Eq, PartialEq, PartialOrd, Clone, Serialize, Deserialize, Debug)] pub enum Query { - /// Retrieve the cost of storing a record at the given address. + /// Retrieve the quote to store a record at the given address. /// The storage verification is optional to be undertaken - GetStoreCost { + GetStoreQuote { /// The Address of the record to be stored. key: NetworkAddress, /// The random nonce that nodes use to produce the Proof (i.e., hash(record+nonce)) @@ -87,7 +87,7 @@ impl Query { Query::CheckNodeInProblem(address) => address.clone(), // Shall not be called for this, as this is a `one-to-one` message, // and the destination shall be decided by the requester already. - Query::GetStoreCost { key, .. } + Query::GetStoreQuote { key, .. } | Query::GetReplicatedRecord { key, .. } | Query::GetRegisterRecord { key, .. } | Query::GetChunkExistenceProof { key, .. } @@ -99,12 +99,12 @@ impl Query { impl std::fmt::Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Query::GetStoreCost { + Query::GetStoreQuote { key, nonce, difficulty, } => { - write!(f, "Query::GetStoreCost({key:?} {nonce:?} {difficulty})") + write!(f, "Query::GetStoreQuote({key:?} {nonce:?} {difficulty})") } Query::GetReplicatedRecord { key, requester } => { write!(f, "Query::GetReplicatedRecord({requester:?} {key:?})") diff --git a/ant-protocol/src/messages/response.rs b/ant-protocol/src/messages/response.rs index a7f8bf9220..48b332c60b 100644 --- a/ant-protocol/src/messages/response.rs +++ b/ant-protocol/src/messages/response.rs @@ -9,7 +9,7 @@ use crate::{error::Result, NetworkAddress}; use super::ChunkProof; -use ant_evm::{PaymentQuote, RewardsAddress}; +use ant_evm::PaymentQuote; use bytes::Bytes; use core::fmt; use libp2p::Multiaddr; @@ -19,16 +19,14 @@ use std::fmt::Debug; /// The response to a query, containing the query result. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum QueryResponse { - // ===== GetStoreCost ===== + // ===== GetStoreQuote ===== // - /// Response to [`GetStoreCost`] + /// Response to [`GetStoreQuote`] /// - /// [`GetStoreCost`]: crate::messages::Query::GetStoreCost - GetStoreCost { + /// [`GetStoreQuote`]: crate::messages::Query::GetStoreQuote + GetStoreQuote { /// The store cost quote for storing the next record. quote: Result, - /// The rewards address to pay this node's store cost to. - payment_address: RewardsAddress, /// Node's Peer Address peer_address: NetworkAddress, /// Storage proofs based on requested target address and difficulty @@ -80,15 +78,15 @@ pub enum QueryResponse { impl Debug for QueryResponse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - QueryResponse::GetStoreCost { + QueryResponse::GetStoreQuote { quote, - payment_address, peer_address, storage_proofs, } => { + let payment_address = quote.as_ref().map(|q| q.rewards_address).ok(); write!( f, - "GetStoreCost(quote: {quote:?}, from {peer_address:?} w/ payment_address: {payment_address:?}, and {} storage proofs)", + "GetStoreQuote(quote: {quote:?}, from {peer_address:?} w/ payment_address: {payment_address:?}, and {} storage proofs)", storage_proofs.len() ) } @@ -152,11 +150,6 @@ pub enum CmdResponse { /// Response to replication cmd Replicate(Result<()>), // - // ===== QuoteVerification ===== - // - /// Response to quote verification cmd - QuoteVerification(Result<()>), - // // ===== PeerConsideredAsBad ===== // /// Response to the considered as bad notification diff --git a/autonomi/Cargo.toml b/autonomi/Cargo.toml index 88d61c711a..f5cbf14744 100644 --- a/autonomi/Cargo.toml +++ b/autonomi/Cargo.toml @@ -15,7 +15,7 @@ crate-type = ["cdylib", "rlib"] [features] data = [] -default = ["data", "vault"] +default = ["data", "vault", "external-signer"] external-signer = ["ant-evm/external-signer", "data"] extension-module = ["pyo3/extension-module"] fs = ["tokio/fs", "data"] @@ -61,7 +61,7 @@ wasm-bindgen-futures = "0.4.43" xor_name = "5.0.0" [dev-dependencies] -alloy = { version = "0.5.3", default-features = false, features = ["std", "reqwest-rustls-tls", "provider-anvil-node", "sol-types", "json", "signers", "contract", "signer-local", "network"] } +alloy = { version = "0.7.3", default-features = false, features = ["contract", "json-rpc", "network", "node-bindings", "provider-http", "reqwest-rustls-tls", "rpc-client", "rpc-types", "signer-local", "std"] } ant-logging = { path = "../ant-logging", version = "0.2.40" } ant-peers-acquisition = { path = "../ant-peers-acquisition", version = "0.5.7" } eyre = "0.6.5" diff --git a/autonomi/src/client/data.rs b/autonomi/src/client/data.rs index 113e0511a5..bf150f41c9 100644 --- a/autonomi/src/client/data.rs +++ b/autonomi/src/client/data.rs @@ -8,17 +8,17 @@ use bytes::Bytes; use libp2p::kad::Quorum; +use std::collections::HashSet; -use std::collections::{HashMap, HashSet}; use std::sync::LazyLock; use xor_name::XorName; -use crate::client::payment::PaymentOption; +use crate::client::payment::{PaymentOption, Receipt}; use crate::client::utils::process_tasks_with_max_concurrency; use crate::client::{ClientEvent, UploadSummary}; use crate::{self_encryption::encrypt, Client}; +use ant_evm::EvmWalletError; use ant_evm::{Amount, AttoTokens}; -use ant_evm::{EvmWalletError, ProofOfPayment}; use ant_networking::{GetRecordCfg, NetworkError}; use ant_protocol::{ storage::{try_deserialize_record, Chunk, ChunkAddress, RecordHeader, RecordKind}, @@ -84,6 +84,8 @@ pub enum PutError { VaultBadOwner, #[error("Payment unexpectedly invalid for {0:?}")] PaymentUnexpectedlyInvalid(NetworkAddress), + #[error("The payment proof contains no payees.")] + PayeesMissing, } /// Errors that can occur during the pay operation. @@ -121,8 +123,12 @@ pub enum CostError { CouldNotGetStoreQuote(XorName), #[error("Could not get store costs: {0:?}")] CouldNotGetStoreCosts(NetworkError), + #[error("Not enough node quotes for {0:?}, got: {1:?} and need at least {2:?}")] + NotEnoughNodeQuotes(XorName, usize, usize), #[error("Failed to serialize {0}")] Serialization(String), + #[error("Market price error: {0:?}")] + MarketPriceError(#[from] ant_evm::payment_vault::error::Error), } impl Client { @@ -194,7 +200,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let tokens_spent = receipt .values() - .map(|proof| proof.quote.cost.as_atto()) + .map(|(_proof, price)| price.as_atto()) .sum::(); let summary = UploadSummary { @@ -257,16 +263,19 @@ impl Client { content_addrs.len() ); - let cost_map = self + let store_quote = self .get_store_quotes(content_addrs.into_iter()) .await .inspect_err(|err| error!("Error getting store quotes: {err:?}"))?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); + Ok(total_cost) } @@ -274,7 +283,7 @@ impl Client { pub(crate) async fn upload_chunks_with_retries<'a>( &self, mut chunks: Vec<&'a Chunk>, - receipt: &HashMap, + receipt: &Receipt, ) -> Vec<(&'a Chunk, PutError)> { let mut current_attempt: usize = 1; @@ -284,7 +293,7 @@ impl Client { let self_clone = self.clone(); let address = *chunk.address(); - let Some(proof) = receipt.get(chunk.name()) else { + let Some((proof, _)) = receipt.get(chunk.name()) else { debug!("Chunk at {address:?} was already paid for so skipping"); continue; }; diff --git a/autonomi/src/client/data_private.rs b/autonomi/src/client/data_private.rs index 5f2dd1793c..d1288bb193 100644 --- a/autonomi/src/client/data_private.rs +++ b/autonomi/src/client/data_private.rs @@ -100,7 +100,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let tokens_spent = receipt .values() - .map(|proof| proof.quote.cost.as_atto()) + .map(|(_proof, price)| price.as_atto()) .sum::(); let summary = UploadSummary { diff --git a/autonomi/src/client/external_signer.rs b/autonomi/src/client/external_signer.rs index 6a4e46d524..aee672f29b 100644 --- a/autonomi/src/client/external_signer.rs +++ b/autonomi/src/client/external_signer.rs @@ -1,36 +1,39 @@ use crate::client::data::PutError; -use crate::client::utils::extract_quote_payments; use crate::self_encryption::encrypt; use crate::Client; -use ant_evm::{PaymentQuote, QuotePayment}; +use ant_evm::QuotePayment; use ant_protocol::storage::Chunk; use bytes::Bytes; use std::collections::HashMap; use xor_name::XorName; -use crate::utils::cost_map_to_quotes; #[allow(unused_imports)] pub use ant_evm::external_signer::*; +use super::quote::QuoteForAddress; + impl Client { /// Get quotes for data. /// Returns a cost map, data payments to be executed and a list of free (already paid for) chunks. pub async fn get_quotes_for_content_addresses( &self, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, ) -> Result< ( - HashMap, + HashMap, Vec, Vec, ), PutError, > { - let cost_map = self.get_store_quotes(content_addrs).await?; - let (quote_payments, free_chunks) = extract_quote_payments(&cost_map); - let quotes = cost_map_to_quotes(cost_map); + let quote = self.get_store_quotes(content_addrs.clone()).await?; + let payments = quote.payments(); + let free_chunks = content_addrs + .filter(|addr| !quote.0.contains_key(addr)) + .collect(); + let quotes_per_addr = quote.0.into_iter().collect(); - Ok((quotes, quote_payments, free_chunks)) + Ok((quotes_per_addr, payments, free_chunks)) } } diff --git a/autonomi/src/client/mod.rs b/autonomi/src/client/mod.rs index f039d097a0..9ce93b9150 100644 --- a/autonomi/src/client/mod.rs +++ b/autonomi/src/client/mod.rs @@ -8,6 +8,7 @@ pub mod address; pub mod payment; +pub mod quote; #[cfg(feature = "data")] pub mod archive; @@ -36,6 +37,7 @@ mod utils; pub use ant_evm::Amount; +use crate::EvmNetwork; use ant_networking::{interval, multiaddr_is_global, Network, NetworkBuilder, NetworkEvent}; use ant_protocol::{version::IDENTIFY_PROTOCOL_STR, CLOSE_GROUP_SIZE}; use libp2p::{identity::Keypair, Multiaddr}; @@ -66,6 +68,7 @@ const CLIENT_EVENT_CHANNEL_SIZE: usize = 100; pub struct Client { pub(crate) network: Network, pub(crate) client_event_sender: Arc>>, + pub(crate) evm_network: EvmNetwork, } /// Error returned by [`Client::connect`]. @@ -119,6 +122,7 @@ impl Client { Ok(Self { network, client_event_sender: Arc::new(None), + evm_network: Default::default(), }) } @@ -129,6 +133,10 @@ impl Client { self.client_event_sender = Arc::new(Some(client_event_sender)); client_event_receiver } + + pub fn set_evm_network(&mut self, evm_network: EvmNetwork) { + self.evm_network = evm_network; + } } fn build_client_and_run_swarm(local: bool) -> (Network, mpsc::Receiver) { diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs index c4cdb88a03..29a8f11576 100644 --- a/autonomi/src/client/payment.rs +++ b/autonomi/src/client/payment.rs @@ -1,11 +1,39 @@ use crate::client::data::PayError; +use crate::client::quote::StoreQuote; use crate::Client; -use ant_evm::{EvmWallet, ProofOfPayment}; +use ant_evm::{AttoTokens, EncodedPeerId, EvmWallet, ProofOfPayment}; use std::collections::HashMap; use xor_name::XorName; -/// Contains the proof of payment for XOR addresses. -pub type Receipt = HashMap; +/// Contains the proof of payments for each XOR address and the amount paid +pub type Receipt = HashMap; + +pub fn receipt_from_store_quotes(quotes: StoreQuote) -> Receipt { + let mut receipt = Receipt::new(); + + for (content_addr, quote_for_address) in quotes.0 { + let price = AttoTokens::from_atto(quote_for_address.price()); + + let mut proof_of_payment = ProofOfPayment { + peer_quotes: vec![], + }; + + for (peer_id, quote, _amount) in quote_for_address.0 { + proof_of_payment + .peer_quotes + .push((EncodedPeerId::from(peer_id), quote)); + } + + // skip empty proofs + if proof_of_payment.peer_quotes.is_empty() { + continue; + } + + receipt.insert(content_addr, (proof_of_payment, price)); + } + + receipt +} /// Payment options for data payments. #[derive(Clone)] @@ -35,12 +63,12 @@ impl From for PaymentOption { impl Client { pub(crate) async fn pay_for_content_addrs( &self, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, payment_option: PaymentOption, ) -> Result { match payment_option { PaymentOption::Wallet(wallet) => { - let (receipt, _) = self.pay(content_addrs, &wallet).await?; + let receipt = self.pay(content_addrs, &wallet).await?; Ok(receipt) } PaymentOption::Receipt(receipt) => Ok(receipt), diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs new file mode 100644 index 0000000000..2c527fafd2 --- /dev/null +++ b/autonomi/src/client/quote.rs @@ -0,0 +1,178 @@ +// Copyright 2024 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use super::{data::CostError, Client}; +use ant_evm::payment_vault::get_market_price; +use ant_evm::{Amount, PaymentQuote, QuotePayment}; +use ant_networking::target_arch::{sleep, Duration, Instant}; +use ant_networking::{Network, NetworkError}; +use ant_protocol::{storage::ChunkAddress, NetworkAddress}; +use libp2p::PeerId; +use std::collections::HashMap; +use xor_name::XorName; + +// set rate limit to 2 req/s +const TIME_BETWEEN_RPC_CALLS_IN_MS: u64 = 500; + +/// A quote for a single address +pub struct QuoteForAddress(pub(crate) Vec<(PeerId, PaymentQuote, Amount)>); + +impl QuoteForAddress { + pub fn price(&self) -> Amount { + self.0.iter().map(|(_, _, price)| price).sum() + } +} + +/// A quote for many addresses +pub struct StoreQuote(pub(crate) HashMap); + +impl StoreQuote { + pub fn price(&self) -> Amount { + self.0.values().map(|quote| quote.price()).sum() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn payments(&self) -> Vec { + let mut quote_payments = vec![]; + for (_address, quote) in self.0.iter() { + for (_peer, quote, price) in quote.0.iter() { + quote_payments.push((quote.hash(), quote.rewards_address, *price)); + } + } + quote_payments + } +} + +impl Client { + pub(crate) async fn get_store_quotes( + &self, + content_addrs: impl Iterator, + ) -> Result { + // get all quotes from nodes + let futures: Vec<_> = content_addrs + .into_iter() + .map(|content_addr| fetch_store_quote_with_retries(&self.network, content_addr)) + .collect(); + let raw_quotes_per_addr = futures::future::try_join_all(futures).await?; + + debug!("Fetched store quotes: {raw_quotes_per_addr:?}"); + + // choose the quotes to pay for each address + let mut quotes_to_pay_per_addr = HashMap::new(); + for (content_addr, raw_quotes) in raw_quotes_per_addr { + // ask smart contract for the market price + let mut prices = vec![]; + + // rate limit + let mut maybe_last_call: Option = None; + + for (peer, quote) in raw_quotes { + // NB TODO @mick we need to batch this smart contract call + // check if we have to wait for the rate limit + if let Some(last_call) = maybe_last_call { + let elapsed = Instant::now() - last_call; + let time_to_sleep_ms = + TIME_BETWEEN_RPC_CALLS_IN_MS as u128 - elapsed.as_millis(); + if time_to_sleep_ms > 0 { + sleep(Duration::from_millis(time_to_sleep_ms as u64)).await; + } + } + + let price = + get_market_price(&self.evm_network, quote.quoting_metrics.clone()).await?; + + maybe_last_call = Some(Instant::now()); + + prices.push((peer, quote, price)); + } + + // sort by price + prices.sort_by(|(_, _, price_a), (_, _, price_b)| price_a.cmp(price_b)); + + // we need at least 5 valid quotes to pay for the data + const MINIMUM_QUOTES_TO_PAY: usize = 5; + match &prices[..] { + [first, second, third, fourth, fifth, ..] => { + let (p1, q1, _) = first; + let (p2, q2, _) = second; + + // don't pay for the cheapest 2 quotes but include them + let first = (*p1, q1.clone(), Amount::ZERO); + let second = (*p2, q2.clone(), Amount::ZERO); + + // pay for the rest + quotes_to_pay_per_addr.insert( + content_addr, + QuoteForAddress(vec![ + first, + second, + third.clone(), + fourth.clone(), + fifth.clone(), + ]), + ); + } + _ => { + return Err(CostError::NotEnoughNodeQuotes( + content_addr, + prices.len(), + MINIMUM_QUOTES_TO_PAY, + )); + } + } + } + + Ok(StoreQuote(quotes_to_pay_per_addr)) + } +} + +/// Fetch a store quote for a content address. +async fn fetch_store_quote( + network: &Network, + content_addr: XorName, +) -> Result, NetworkError> { + network + .get_store_quote_from_network( + NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), + vec![], + ) + .await +} + +/// Fetch a store quote for a content address with a retry strategy. +async fn fetch_store_quote_with_retries( + network: &Network, + content_addr: XorName, +) -> Result<(XorName, Vec<(PeerId, PaymentQuote)>), CostError> { + let mut retries = 0; + + loop { + match fetch_store_quote(network, content_addr).await { + Ok(quote) => { + break Ok((content_addr, quote)); + } + Err(err) if retries < 2 => { + retries += 1; + error!("Error while fetching store quote: {err:?}, retry #{retries}"); + } + Err(err) => { + error!( + "Error while fetching store quote: {err:?}, stopping after {retries} retries" + ); + break Err(CostError::CouldNotGetStoreQuote(content_addr)); + } + } + } +} diff --git a/autonomi/src/client/registers.rs b/autonomi/src/client/registers.rs index c405fd6cf7..d7237c27fa 100644 --- a/autonomi/src/client/registers.rs +++ b/autonomi/src/client/registers.rs @@ -49,6 +49,8 @@ pub enum RegisterError { CouldNotSign(#[source] ant_registers::Error), #[error("Received invalid quote from node, this node is possibly malfunctioning, try another node by trying another register name")] InvalidQuote, + #[error("The payment proof contains no payees.")] + PayeesMissing, } #[derive(Clone, Debug)] @@ -237,11 +239,13 @@ impl Client { // get cost to store register // NB TODO: register should be priced differently from other data - let cost_map = self.get_store_quotes(std::iter::once(reg_xor)).await?; + let store_quote = self.get_store_quotes(std::iter::once(reg_xor)).await?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); @@ -292,24 +296,30 @@ impl Client { let reg_xor = address.xorname(); debug!("Paying for register at address: {address}"); - let (payment_proofs, _skipped) = self + let payment_proofs = self .pay(std::iter::once(reg_xor), wallet) .await .inspect_err(|err| { error!("Failed to pay for register at address: {address} : {err}") })?; - let proof = if let Some(proof) = payment_proofs.get(®_xor) { - proof + let (proof, price) = if let Some((proof, price)) = payment_proofs.get(®_xor) { + (proof, price) } else { // register was skipped, meaning it was already paid for error!("Register at address: {address} was already paid for"); return Err(RegisterError::Network(NetworkError::RegisterAlreadyExists)); }; - let payee = proof - .to_peer_id_payee() - .ok_or(RegisterError::InvalidQuote) - .inspect_err(|err| error!("Failed to get payee from payment proof: {err}"))?; + let payees = proof.payees(); + + if payees.is_empty() { + error!( + "Failed to get payees from payment proof: {:?}", + RegisterError::PayeesMissing + ); + return Err(RegisterError::PayeesMissing); + } + let signed_register = register.signed_reg.clone(); let record = Record { @@ -331,10 +341,11 @@ impl Client { expected_holders: Default::default(), is_register: true, }; + let put_cfg = PutRecordCfg { put_quorum: Quorum::All, retry_strategy: None, - use_put_record_to: Some(vec![payee]), + use_put_record_to: Some(payees), // CODE REVIEW: should we put to all or just one here? verification: Some((VerificationKind::Network, get_cfg)), }; @@ -349,7 +360,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let summary = UploadSummary { record_count: 1, - tokens_spent: proof.quote.cost.as_atto(), + tokens_spent: price.as_atto(), }; if let Err(err) = channel.send(ClientEvent::UploadComplete(summary)).await { error!("Failed to send client event: {err}"); diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 4962b400eb..91b60e2eff 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -6,27 +6,23 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::client::payment::Receipt; -use crate::utils::receipt_from_cost_map_and_payments; -use ant_evm::{EvmWallet, ProofOfPayment, QuotePayment}; -use ant_networking::{ - GetRecordCfg, Network, NetworkError, PayeeQuote, PutRecordCfg, VerificationKind, -}; +use crate::client::payment::{receipt_from_store_quotes, Receipt}; +use ant_evm::{EvmWallet, ProofOfPayment}; +use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind}; use ant_protocol::{ messages::ChunkProof, - storage::{try_serialize_record, Chunk, ChunkAddress, RecordKind, RetryStrategy}, - NetworkAddress, + storage::{try_serialize_record, Chunk, RecordKind, RetryStrategy}, }; use bytes::Bytes; use futures::stream::{FuturesUnordered, StreamExt}; use libp2p::kad::{Quorum, Record}; use rand::{thread_rng, Rng}; use self_encryption::{decrypt_full_set, DataMap, EncryptedChunk}; -use std::{collections::HashMap, future::Future, num::NonZero}; +use std::{future::Future, num::NonZero}; use xor_name::XorName; use super::{ - data::{CostError, GetError, PayError, PutError, CHUNK_DOWNLOAD_BATCH_SIZE}, + data::{GetError, PayError, PutError, CHUNK_DOWNLOAD_BATCH_SIZE}, Client, }; use crate::self_encryption::DataMapLevel; @@ -103,9 +99,13 @@ impl Client { chunk: &Chunk, payment: ProofOfPayment, ) -> Result<(), PutError> { - let storing_node = payment.to_peer_id_payee().expect("Missing node Peer ID"); + let storing_nodes = payment.payees(); + + if storing_nodes.is_empty() { + return Err(PutError::PayeesMissing); + } - debug!("Storing chunk: {chunk:?} to {:?}", storing_node); + debug!("Storing chunk: {chunk:?} to {:?}", storing_nodes); let key = chunk.network_address().to_record_key(); @@ -150,21 +150,21 @@ impl Client { let put_cfg = PutRecordCfg { put_quorum: Quorum::One, retry_strategy: Some(RetryStrategy::Balanced), - use_put_record_to: Some(vec![storing_node]), + use_put_record_to: Some(storing_nodes), // CODE REVIEW: do we put to all payees or just one? verification, }; + Ok(self.network.put_record(record, &put_cfg).await?) } /// Pay for the chunks and get the proof of payment. pub(crate) async fn pay( &self, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, wallet: &EvmWallet, - ) -> Result<(Receipt, Vec), PayError> { - let cost_map = self.get_store_quotes(content_addrs).await?; - - let (quote_payments, skipped_chunks) = extract_quote_payments(&cost_map); + ) -> Result { + let number_of_content_addrs = content_addrs.clone().count(); + let quotes = self.get_store_quotes(content_addrs).await?; // Make sure nobody else can use the wallet while we are paying debug!("Waiting for wallet lock"); @@ -174,8 +174,8 @@ impl Client { // TODO: the error might contain some succeeded quote payments as well. These should be returned on err, so that they can be skipped when retrying. // TODO: retry when it fails? // Execute chunk payments - let payments = wallet - .pay_for_quotes(quote_payments) + let _payments = wallet + .pay_for_quotes(quotes.payments()) .await .map_err(|err| PayError::from(err.0))?; @@ -183,87 +183,17 @@ impl Client { drop(lock_guard); debug!("Unlocked wallet"); - let proofs = receipt_from_cost_map_and_payments(cost_map, &payments); - + let skipped_chunks = number_of_content_addrs - quotes.len(); trace!( "Chunk payments of {} chunks completed. {} chunks were free / already paid for", - proofs.len(), - skipped_chunks.len() + quotes.len(), + skipped_chunks ); - Ok((proofs, skipped_chunks)) - } - - pub(crate) async fn get_store_quotes( - &self, - content_addrs: impl Iterator, - ) -> Result, CostError> { - let futures: Vec<_> = content_addrs - .into_iter() - .map(|content_addr| fetch_store_quote_with_retries(&self.network, content_addr)) - .collect(); - - let quotes = futures::future::try_join_all(futures).await?; - - Ok(quotes.into_iter().collect::>()) - } -} - -/// Fetch a store quote for a content address with a retry strategy. -async fn fetch_store_quote_with_retries( - network: &Network, - content_addr: XorName, -) -> Result<(XorName, PayeeQuote), CostError> { - let mut retries = 0; - - loop { - match fetch_store_quote(network, content_addr).await { - Ok(quote) => { - break Ok((content_addr, quote)); - } - Err(err) if retries < 2 => { - retries += 1; - error!("Error while fetching store quote: {err:?}, retry #{retries}"); - } - Err(err) => { - error!( - "Error while fetching store quote: {err:?}, stopping after {retries} retries" - ); - break Err(CostError::CouldNotGetStoreQuote(content_addr)); - } - } - } -} - -/// Fetch a store quote for a content address. -async fn fetch_store_quote( - network: &Network, - content_addr: XorName, -) -> Result { - network - .get_store_costs_from_network( - NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), - vec![], - ) - .await -} + let receipt = receipt_from_store_quotes(quotes); -/// Form to be executed payments and already executed payments from a cost map. -pub(crate) fn extract_quote_payments( - cost_map: &HashMap, -) -> (Vec, Vec) { - let mut to_be_paid = vec![]; - let mut already_paid = vec![]; - - for (chunk_address, (_, _, quote)) in cost_map.iter() { - if quote.cost.is_zero() { - already_paid.push(*chunk_address); - } else { - to_be_paid.push((quote.hash(), quote.rewards_address, quote.cost.as_atto())); - } + Ok(receipt) } - - (to_be_paid, already_paid) } pub(crate) async fn process_tasks_with_max_concurrency(tasks: I, batch_size: usize) -> Vec diff --git a/autonomi/src/client/vault.rs b/autonomi/src/client/vault.rs index baa86ed120..995ea89a9a 100644 --- a/autonomi/src/client/vault.rs +++ b/autonomi/src/client/vault.rs @@ -151,11 +151,13 @@ impl Client { let vault_xor = scratch.network_address().as_xorname().unwrap_or_default(); // NB TODO: vault should be priced differently from other data - let cost_map = self.get_store_quotes(std::iter::once(vault_xor)).await?; + let store_quote = self.get_store_quotes(std::iter::once(vault_xor)).await?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); @@ -196,12 +198,12 @@ impl Client { error!("Failed to pay for new vault at addr: {scratch_address:?} : {err}"); })?; - let proof = match receipt.values().next() { + let (proof, price) = match receipt.values().next() { Some(proof) => proof, None => return Err(PutError::PaymentUnexpectedlyInvalid(scratch_address)), }; - total_cost = proof.quote.cost; + total_cost = *price; Record { key: scratch_key, diff --git a/autonomi/src/lib.rs b/autonomi/src/lib.rs index 705623a833..3352dd2a54 100644 --- a/autonomi/src/lib.rs +++ b/autonomi/src/lib.rs @@ -41,14 +41,11 @@ extern crate tracing; pub mod client; #[cfg(feature = "data")] mod self_encryption; -mod utils; pub use ant_evm::get_evm_network_from_env; pub use ant_evm::EvmNetwork; pub use ant_evm::EvmWallet as Wallet; pub use ant_evm::RewardsAddress; -#[cfg(feature = "external-signer")] -pub use utils::receipt_from_quotes_and_payments; #[doc(no_inline)] // Place this under 'Re-exports' in the docs. pub use bytes::Bytes; diff --git a/autonomi/src/utils.rs b/autonomi/src/utils.rs deleted file mode 100644 index 1348c0c685..0000000000 --- a/autonomi/src/utils.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::client::payment::Receipt; -use ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; -use ant_networking::PayeeQuote; -use std::collections::{BTreeMap, HashMap}; -use xor_name::XorName; - -pub fn cost_map_to_quotes( - cost_map: HashMap, -) -> HashMap { - cost_map.into_iter().map(|(k, (_, _, v))| (k, v)).collect() -} - -pub fn receipt_from_cost_map_and_payments( - cost_map: HashMap, - payments: &BTreeMap, -) -> Receipt { - let quotes = cost_map_to_quotes(cost_map); - receipt_from_quotes_and_payments("es, payments) -} - -pub fn receipt_from_quotes_and_payments( - quotes: &HashMap, - payments: &BTreeMap, -) -> Receipt { - quotes - .iter() - .filter_map(|(xor_name, quote)| { - payments.get("e.hash()).map(|tx_hash| { - ( - *xor_name, - ProofOfPayment { - quote: quote.clone(), - tx_hash: *tx_hash, - }, - ) - }) - }) - .collect() -} diff --git a/autonomi/tests/external_signer.rs b/autonomi/tests/external_signer.rs index a9755400a4..9ef392f8a0 100644 --- a/autonomi/tests/external_signer.rs +++ b/autonomi/tests/external_signer.rs @@ -7,10 +7,10 @@ use ant_logging::LogBuilder; use autonomi::client::archive::Metadata; use autonomi::client::archive_private::PrivateArchive; use autonomi::client::external_signer::encrypt_data; -use autonomi::client::payment::Receipt; +use autonomi::client::payment::{receipt_from_store_quotes_and_payments, Receipt}; use autonomi::client::vault::user_data::USER_DATA_VAULT_CONTENT_IDENTIFIER; use autonomi::client::vault::VaultSecretKey; -use autonomi::{receipt_from_quotes_and_payments, Client, Wallet}; +use autonomi::{Client, Wallet}; use bytes::Bytes; use std::collections::BTreeMap; use std::time::Duration; @@ -35,7 +35,7 @@ async fn pay_for_data(client: &Client, wallet: &Wallet, data: Bytes) -> eyre::Re async fn pay_for_content_addresses( client: &Client, wallet: &Wallet, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, ) -> eyre::Result { let (quotes, quote_payments, _free_chunks) = client .get_quotes_for_content_addresses(content_addrs) @@ -94,7 +94,7 @@ async fn pay_for_content_addresses( } // Payment proofs - Ok(receipt_from_quotes_and_payments("es, &payments)) + Ok(receipt_from_store_quotes_and_payments("es, payments)) } // Example of how put would be done using external signers. diff --git a/evmlib/Cargo.toml b/evmlib/Cargo.toml index cb567e24e3..5e4a5b805e 100644 --- a/evmlib/Cargo.toml +++ b/evmlib/Cargo.toml @@ -14,7 +14,7 @@ local = [] external-signer = [] [dependencies] -alloy = { version = "0.5.3", default-features = false, features = ["std", "reqwest-rustls-tls", "provider-anvil-node", "sol-types", "json", "signers", "contract", "signer-local", "network"] } +alloy = { version = "0.7.3", default-features = false, features = ["contract", "json-rpc", "network", "node-bindings", "provider-http", "reqwest-rustls-tls", "rpc-client", "rpc-types", "signer-local", "std"] } dirs-next = "~2.0.0" serde = "=1.0.210" serde_with = { version = "3.11.0", features = ["macros"] } diff --git a/evmlib/abi/IPaymentVault.json b/evmlib/abi/IPaymentVault.json new file mode 100644 index 0000000000..d2bc495a5f --- /dev/null +++ b/evmlib/abi/IPaymentVault.json @@ -0,0 +1,220 @@ +[ + { + "inputs": [], + "name": "AntTokenNull", + "type": "error" + }, + { + "inputs": [], + "name": "BatchLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInputLength", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "name": "DataPaymentMade", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "closeRecordsStored", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRecords", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedPaymentCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liveTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkDensity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkSize", + "type": "uint256" + } + ], + "internalType": "struct IPaymentVault.QuotingMetrics", + "name": "_metrics", + "type": "tuple" + } + ], + "name": "getQuote", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "internalType": "struct IPaymentVault.DataPayment[]", + "name": "_payments", + "type": "tuple[]" + } + ], + "name": "payForQuotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "closeRecordsStored", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRecords", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedPaymentCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liveTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkDensity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkSize", + "type": "uint256" + } + ], + "internalType": "struct IPaymentVault.QuotingMetrics", + "name": "metrics", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "internalType": "struct IPaymentVault.DataPayment", + "name": "dataPayment", + "type": "tuple" + } + ], + "internalType": "struct IPaymentVault.PaymentVerification[]", + "name": "_payments", + "type": "tuple[]" + } + ], + "name": "verifyPayment", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountPaid", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "internalType": "struct IPaymentVault.PaymentVerificationResult[3]", + "name": "verificationResults", + "type": "tuple[3]" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/evmlib/artifacts/DataPayments.json b/evmlib/artifacts/DataPayments.json deleted file mode 100644 index a72afa0b8b..0000000000 --- a/evmlib/artifacts/DataPayments.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "DataPayments", - "sourceName": "contracts/DataPayments.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "rewardsAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "quoteHash", - "type": "bytes32" - } - ], - "name": "DataPaymentMade", - "type": "event" - }, - { - "inputs": [], - "name": "PAYMENT_TOKEN_ADDRESS", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "rewardsAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "quoteHash", - "type": "bytes32" - } - ], - "internalType": "struct DataPayments.DataPayment[]", - "name": "dataPayments", - "type": "tuple[]" - } - ], - "name": "submitDataPayments", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60a060405234801561001057600080fd5b506040516105f73803806105f783398101604081905261002f916100a6565b6001600160a01b0381166100955760405162461bcd60e51b8152602060048201526024808201527f546f6b656e20616464726573732063616e6e6f74206265207a65726f206164646044820152637265737360e01b606482015260840160405180910390fd5b6001600160a01b03166080526100d6565b6000602082840312156100b857600080fd5b81516001600160a01b03811681146100cf57600080fd5b9392505050565b6080516104f26101056000396000818160400152818161015101528181610253015261035301526104f26000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635c0d32861461003b578063dee1dfa01461007e575b600080fd5b6100627f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61009161008c3660046103c6565b610093565b005b60005b8181101561012b57368383838181106100b1576100b161043b565b6060029190910191506100d79050336100cd6020840184610451565b8360200135610130565b6040810135602082018035906100ed9084610451565b6001600160a01b03167ff998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d58060405160405180910390a450600101610096565b505050565b6040516370a0823160e01b81526001600160a01b03848116600483015282917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa15801561019a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101be9190610481565b101561021c5760405162461bcd60e51b815260206004820152602260248201527f57616c6c657420646f6573206e6f74206861766520656e6f75676820746f6b656044820152616e7360f01b60648201526084015b60405180910390fd5b6001600160a01b038316301461032557604051636eb1769f60e11b81526001600160a01b03848116600483015230602483015282917f00000000000000000000000000000000000000000000000000000000000000009091169063dd62ed3e90604401602060405180830381865afa15801561029c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c09190610481565b10156103255760405162461bcd60e51b815260206004820152602e60248201527f436f6e7472616374206973206e6f7420616c6c6f77656420746f207370656e6460448201526d20656e6f75676820746f6b656e7360901b6064820152608401610213565b6040516323b872dd60e01b81526001600160a01b0384811660048301528381166024830152604482018390527f000000000000000000000000000000000000000000000000000000000000000016906323b872dd906064016020604051808303816000875af115801561039c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103c0919061049a565b50505050565b600080602083850312156103d957600080fd5b823567ffffffffffffffff808211156103f157600080fd5b818501915085601f83011261040557600080fd5b81358181111561041457600080fd5b86602060608302850101111561042957600080fd5b60209290920196919550909350505050565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561046357600080fd5b81356001600160a01b038116811461047a57600080fd5b9392505050565b60006020828403121561049357600080fd5b5051919050565b6000602082840312156104ac57600080fd5b8151801515811461047a57600080fdfea26469706673582212206f3a305284dc687832455d7d49b202dcf22b32d76aff5ccd14c3c8539596bcf464736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80635c0d32861461003b578063dee1dfa01461007e575b600080fd5b6100627f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200160405180910390f35b61009161008c3660046103c6565b610093565b005b60005b8181101561012b57368383838181106100b1576100b161043b565b6060029190910191506100d79050336100cd6020840184610451565b8360200135610130565b6040810135602082018035906100ed9084610451565b6001600160a01b03167ff998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d58060405160405180910390a450600101610096565b505050565b6040516370a0823160e01b81526001600160a01b03848116600483015282917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa15801561019a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101be9190610481565b101561021c5760405162461bcd60e51b815260206004820152602260248201527f57616c6c657420646f6573206e6f74206861766520656e6f75676820746f6b656044820152616e7360f01b60648201526084015b60405180910390fd5b6001600160a01b038316301461032557604051636eb1769f60e11b81526001600160a01b03848116600483015230602483015282917f00000000000000000000000000000000000000000000000000000000000000009091169063dd62ed3e90604401602060405180830381865afa15801561029c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c09190610481565b10156103255760405162461bcd60e51b815260206004820152602e60248201527f436f6e7472616374206973206e6f7420616c6c6f77656420746f207370656e6460448201526d20656e6f75676820746f6b656e7360901b6064820152608401610213565b6040516323b872dd60e01b81526001600160a01b0384811660048301528381166024830152604482018390527f000000000000000000000000000000000000000000000000000000000000000016906323b872dd906064016020604051808303816000875af115801561039c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103c0919061049a565b50505050565b600080602083850312156103d957600080fd5b823567ffffffffffffffff808211156103f157600080fd5b818501915085601f83011261040557600080fd5b81358181111561041457600080fd5b86602060608302850101111561042957600080fd5b60209290920196919550909350505050565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561046357600080fd5b81356001600160a01b038116811461047a57600080fd5b9392505050565b60006020828403121561049357600080fd5b5051919050565b6000602082840312156104ac57600080fd5b8151801515811461047a57600080fdfea26469706673582212206f3a305284dc687832455d7d49b202dcf22b32d76aff5ccd14c3c8539596bcf464736f6c63430008180033", - "linkReferences": {}, - "deployedLinkReferences": {} -} \ No newline at end of file diff --git a/evmlib/artifacts/PaymentVaultNoProxy.json b/evmlib/artifacts/PaymentVaultNoProxy.json new file mode 100644 index 0000000000..5514cc77f7 --- /dev/null +++ b/evmlib/artifacts/PaymentVaultNoProxy.json @@ -0,0 +1,351 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "PaymentVaultNoProxy", + "sourceName": "contracts/PaymentVaultNoProxy.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_antToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_batchLimit", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "AntTokenNull", + "type": "error" + }, + { + "inputs": [], + "name": "BatchLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInputLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "name": "DataPaymentMade", + "type": "event" + }, + { + "inputs": [], + "name": "antToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "closeRecordsStored", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRecords", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedPaymentCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liveTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkDensity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkSize", + "type": "uint256" + } + ], + "internalType": "struct IPaymentVault.QuotingMetrics", + "name": "", + "type": "tuple" + } + ], + "name": "getQuote", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "internalType": "struct IPaymentVault.DataPayment[]", + "name": "_payments", + "type": "tuple[]" + } + ], + "name": "payForQuotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "payments", + "outputs": [ + { + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "requiredPaymentVerificationLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "closeRecordsStored", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRecords", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedPaymentCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liveTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkDensity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "networkSize", + "type": "uint256" + } + ], + "internalType": "struct IPaymentVault.QuotingMetrics", + "name": "metrics", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "rewardsAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ], + "internalType": "struct IPaymentVault.DataPayment", + "name": "dataPayment", + "type": "tuple" + } + ], + "internalType": "struct IPaymentVault.PaymentVerification[]", + "name": "_payments", + "type": "tuple[]" + } + ], + "name": "verifyPayment", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountPaid", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "internalType": "struct IPaymentVault.PaymentVerificationResult[3]", + "name": "verificationResults", + "type": "tuple[3]" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600f57600080fd5b50604051610cce380380610cce833981016040819052602c91607f565b6001600160a01b038216605257604051632d06160b60e21b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b039390931692909217909155600055600560035560b7565b60008060408385031215609157600080fd5b82516001600160a01b038116811460a757600080fd5b6020939093015192949293505050565b610c08806100c66000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80634ec42e8e1161005b5780634ec42e8e14610111578063b6c2141b1461013c578063bcb2c1da14610151578063c7170bb61461017157600080fd5b80630716326d146100825780633c150bf2146100e6578063474740b114610108575b600080fd5b6100bc61009036600461082f565b60026020819052600091825260409091208054600182015491909201546001600160a01b039092169183565b604080516001600160a01b0390941684526020840192909252908201526060015b60405180910390f35b6100fa6100f4366004610848565b50600190565b6040519081526020016100dd565b6100fa60005481565b600154610124906001600160a01b031681565b6040516001600160a01b0390911681526020016100dd565b61014f61014a366004610863565b61017a565b005b61016461015f3660046108da565b61026a565b6040516100dd9190610942565b6100fa60035481565b600054819081111561019f57604051630d67f41160e21b815260040160405180910390fd5b60005b8181101561026457368484838181106101bd576101bd610992565b6060029190910191506101f19050336101d960208401846109bd565b6001546001600160a01b0316919060208501356103f5565b6040808201356000908152600260205220819061020e82826109da565b505060408101356020820180359061022690846109bd565b6001600160a01b03167ff998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d58060405160405180910390a4506001016101a2565b50505050565b61027261075c565b600354821461029457604051637db491eb60e01b815260040160405180910390fd5b60006102a0848461044f565b905060005b60038110156103ed576000600260008484600381106102c6576102c6610992565b602090810291909101518101516040908101518352828201939093529082016000908120835160608101855281546001600160a01b031681526001820154938101939093526002015492820192909252915083836003811061032a5761032a610992565b602002015160200151602001518260200151149050600084846003811061035357610353610992565b602002015160200151600001516001600160a01b031683600001516001600160a01b03161490506000604051806060016040528087876003811061039957610399610992565b602002015160200151604001518152602001856020015181526020018480156103bf5750835b151590529050808786600381106103d8576103d8610992565b60200201525050600190920191506102a59050565b505092915050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b1790526102649085906105b5565b61045761079b565b60005b828110156105ae578151602090810151015184848381811061047e5761047e610992565b9050610120020160c0016020013511156104d8576020820180516040840152825190528383828181106104b3576104b3610992565b905061012002018036038101906104ca9190610af1565b8260005b60200201526105a6565b60208083015181015101518484838181106104f5576104f5610992565b9050610120020160c001602001351115610544576020820151604083015283838281811061052557610525610992565b9050610120020180360381019061053c9190610af1565b8260016104ce565b6040820151602090810151015184848381811061056357610563610992565b9050610120020160c0016020013511156105a65783838281811061058957610589610992565b905061012002018036038101906105a09190610af1565b60408301525b60010161045a565b5092915050565b60006105ca6001600160a01b03841683610622565b905080516000141580156105ef5750808060200190518101906105ed9190610b81565b155b1561061d57604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b505050565b606061063083836000610637565b9392505050565b60608147101561065c5760405163cd78605960e01b8152306004820152602401610614565b600080856001600160a01b031684866040516106789190610ba3565b60006040518083038185875af1925050503d80600081146106b5576040519150601f19603f3d011682016040523d82523d6000602084013e6106ba565b606091505b50915091506106ca8683836106d4565b9695505050505050565b6060826106e9576106e482610730565b610630565b815115801561070057506001600160a01b0384163b155b1561072957604051639996b31560e01b81526001600160a01b0385166004820152602401610614565b5080610630565b8051156107405780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60405180606001604052806003905b604080516060810182526000808252602080830182905292820152825260001990920191018161076b5790505090565b60405180606001604052806003905b6107b26107c8565b8152602001906001900390816107aa5790505090565b604051806040016040528061080c6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b815260408051606081018252600080825260208281018290529282015291015290565b60006020828403121561084157600080fd5b5035919050565b600060c082840312801561085b57600080fd5b509092915050565b6000806020838503121561087657600080fd5b823567ffffffffffffffff81111561088d57600080fd5b8301601f8101851361089e57600080fd5b803567ffffffffffffffff8111156108b557600080fd5b8560206060830284010111156108ca57600080fd5b6020919091019590945092505050565b600080602083850312156108ed57600080fd5b823567ffffffffffffffff81111561090457600080fd5b8301601f8101851361091557600080fd5b803567ffffffffffffffff81111561092c57600080fd5b856020610120830284010111156108ca57600080fd5b6101208101818360005b600381101561098957815180518452602081015160208501526040810151151560408501525060608301925060208201915060018101905061094c565b50505092915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038116811461075957600080fd5b6000602082840312156109cf57600080fd5b8135610630816109a8565b81356109e5816109a8565b81546001600160a01b0319166001600160a01b039190911617815560208201356001820155604090910135600290910155565b6040805190810167ffffffffffffffff81118282101715610a4957634e487b7160e01b600052604160045260246000fd5b60405290565b60405160c0810167ffffffffffffffff81118282101715610a4957634e487b7160e01b600052604160045260246000fd5b600060608284031215610a9257600080fd5b6040516060810167ffffffffffffffff81118282101715610ac357634e487b7160e01b600052604160045260246000fd5b6040529050808235610ad4816109a8565b815260208381013590820152604092830135920191909152919050565b600081830361012081128015610b0657600080fd5b506000610b11610a18565b60c0831215610b1e578182fd5b610b26610a4f565b853581526020808701359082015260408087013590820152606080870135908201526080808701359082015260a0808701359082018190528183529093509150610b738660c08701610a80565b602082015295945050505050565b600060208284031215610b9357600080fd5b8151801515811461063057600080fd5b6000825160005b81811015610bc45760208186018101518583015201610baa565b50600092019182525091905056fea2646970667358221220fd6ef361aaba52d0f9503b51aea1d0b7a8363a9a66c9502aa7b931f1f44c507f64736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80634ec42e8e1161005b5780634ec42e8e14610111578063b6c2141b1461013c578063bcb2c1da14610151578063c7170bb61461017157600080fd5b80630716326d146100825780633c150bf2146100e6578063474740b114610108575b600080fd5b6100bc61009036600461082f565b60026020819052600091825260409091208054600182015491909201546001600160a01b039092169183565b604080516001600160a01b0390941684526020840192909252908201526060015b60405180910390f35b6100fa6100f4366004610848565b50600190565b6040519081526020016100dd565b6100fa60005481565b600154610124906001600160a01b031681565b6040516001600160a01b0390911681526020016100dd565b61014f61014a366004610863565b61017a565b005b61016461015f3660046108da565b61026a565b6040516100dd9190610942565b6100fa60035481565b600054819081111561019f57604051630d67f41160e21b815260040160405180910390fd5b60005b8181101561026457368484838181106101bd576101bd610992565b6060029190910191506101f19050336101d960208401846109bd565b6001546001600160a01b0316919060208501356103f5565b6040808201356000908152600260205220819061020e82826109da565b505060408101356020820180359061022690846109bd565b6001600160a01b03167ff998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d58060405160405180910390a4506001016101a2565b50505050565b61027261075c565b600354821461029457604051637db491eb60e01b815260040160405180910390fd5b60006102a0848461044f565b905060005b60038110156103ed576000600260008484600381106102c6576102c6610992565b602090810291909101518101516040908101518352828201939093529082016000908120835160608101855281546001600160a01b031681526001820154938101939093526002015492820192909252915083836003811061032a5761032a610992565b602002015160200151602001518260200151149050600084846003811061035357610353610992565b602002015160200151600001516001600160a01b031683600001516001600160a01b03161490506000604051806060016040528087876003811061039957610399610992565b602002015160200151604001518152602001856020015181526020018480156103bf5750835b151590529050808786600381106103d8576103d8610992565b60200201525050600190920191506102a59050565b505092915050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b1790526102649085906105b5565b61045761079b565b60005b828110156105ae578151602090810151015184848381811061047e5761047e610992565b9050610120020160c0016020013511156104d8576020820180516040840152825190528383828181106104b3576104b3610992565b905061012002018036038101906104ca9190610af1565b8260005b60200201526105a6565b60208083015181015101518484838181106104f5576104f5610992565b9050610120020160c001602001351115610544576020820151604083015283838281811061052557610525610992565b9050610120020180360381019061053c9190610af1565b8260016104ce565b6040820151602090810151015184848381811061056357610563610992565b9050610120020160c0016020013511156105a65783838281811061058957610589610992565b905061012002018036038101906105a09190610af1565b60408301525b60010161045a565b5092915050565b60006105ca6001600160a01b03841683610622565b905080516000141580156105ef5750808060200190518101906105ed9190610b81565b155b1561061d57604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b505050565b606061063083836000610637565b9392505050565b60608147101561065c5760405163cd78605960e01b8152306004820152602401610614565b600080856001600160a01b031684866040516106789190610ba3565b60006040518083038185875af1925050503d80600081146106b5576040519150601f19603f3d011682016040523d82523d6000602084013e6106ba565b606091505b50915091506106ca8683836106d4565b9695505050505050565b6060826106e9576106e482610730565b610630565b815115801561070057506001600160a01b0384163b155b1561072957604051639996b31560e01b81526001600160a01b0385166004820152602401610614565b5080610630565b8051156107405780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60405180606001604052806003905b604080516060810182526000808252602080830182905292820152825260001990920191018161076b5790505090565b60405180606001604052806003905b6107b26107c8565b8152602001906001900390816107aa5790505090565b604051806040016040528061080c6040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b815260408051606081018252600080825260208281018290529282015291015290565b60006020828403121561084157600080fd5b5035919050565b600060c082840312801561085b57600080fd5b509092915050565b6000806020838503121561087657600080fd5b823567ffffffffffffffff81111561088d57600080fd5b8301601f8101851361089e57600080fd5b803567ffffffffffffffff8111156108b557600080fd5b8560206060830284010111156108ca57600080fd5b6020919091019590945092505050565b600080602083850312156108ed57600080fd5b823567ffffffffffffffff81111561090457600080fd5b8301601f8101851361091557600080fd5b803567ffffffffffffffff81111561092c57600080fd5b856020610120830284010111156108ca57600080fd5b6101208101818360005b600381101561098957815180518452602081015160208501526040810151151560408501525060608301925060208201915060018101905061094c565b50505092915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038116811461075957600080fd5b6000602082840312156109cf57600080fd5b8135610630816109a8565b81356109e5816109a8565b81546001600160a01b0319166001600160a01b039190911617815560208201356001820155604090910135600290910155565b6040805190810167ffffffffffffffff81118282101715610a4957634e487b7160e01b600052604160045260246000fd5b60405290565b60405160c0810167ffffffffffffffff81118282101715610a4957634e487b7160e01b600052604160045260246000fd5b600060608284031215610a9257600080fd5b6040516060810167ffffffffffffffff81118282101715610ac357634e487b7160e01b600052604160045260246000fd5b6040529050808235610ad4816109a8565b815260208381013590820152604092830135920191909152919050565b600081830361012081128015610b0657600080fd5b506000610b11610a18565b60c0831215610b1e578182fd5b610b26610a4f565b853581526020808701359082015260408087013590820152606080870135908201526080808701359082015260a0808701359082018190528183529093509150610b738660c08701610a80565b602082015295945050505050565b600060208284031215610b9357600080fd5b8151801515811461063057600080fd5b6000825160005b81811015610bc45760208186018101518583015201610baa565b50600092019182525091905056fea2646970667358221220fd6ef361aaba52d0f9503b51aea1d0b7a8363a9a66c9502aa7b931f1f44c507f64736f6c634300081c0033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/evmlib/src/contract/data_payments/error.rs b/evmlib/src/contract/data_payments/error.rs deleted file mode 100644 index 95ec1c1c27..0000000000 --- a/evmlib/src/contract/data_payments/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. Please review the Licences for the specific language governing -// permissions and limitations relating to use of the SAFE Network Software. - -use crate::contract::network_token; -use alloy::transports::{RpcError, TransportErrorKind}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - ContractError(#[from] alloy::contract::Error), - #[error(transparent)] - RpcError(#[from] RpcError), - #[error(transparent)] - NetworkTokenError(#[from] network_token::Error), - #[error(transparent)] - PendingTransactionError(#[from] alloy::providers::PendingTransactionError), - #[error("The transfer limit of 256 has been exceeded")] - TransferLimitExceeded, -} diff --git a/evmlib/src/contract/data_payments/mod.rs b/evmlib/src/contract/data_payments/mod.rs deleted file mode 100644 index 79f90f9b04..0000000000 --- a/evmlib/src/contract/data_payments/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. Please review the Licences for the specific language governing -// permissions and limitations relating to use of the SAFE Network Software. - -pub mod error; - -use crate::common; -use crate::common::{Address, Calldata, TxHash}; -use crate::contract::data_payments::error::Error; -use crate::contract::data_payments::DataPaymentsContract::DataPaymentsContractInstance; -use alloy::network::TransactionBuilder; -use alloy::providers::{Network, Provider}; -use alloy::sol; -use alloy::transports::Transport; - -/// The max amount of transfers within one data payments transaction. -pub const MAX_TRANSFERS_PER_TRANSACTION: usize = 512; - -sol!( - #[allow(clippy::too_many_arguments)] - #[allow(missing_docs)] - #[sol(rpc)] - DataPaymentsContract, - "artifacts/DataPayments.json" -); - -pub struct DataPaymentsHandler, N: Network> { - pub contract: DataPaymentsContractInstance, -} - -impl DataPaymentsHandler -where - T: Transport + Clone, - P: Provider, - N: Network, -{ - /// Create a new ChunkPayments contract instance. - pub fn new(contract_address: Address, provider: P) -> Self { - let contract = DataPaymentsContract::new(contract_address, provider); - DataPaymentsHandler { contract } - } - - /// Deploys the ChunkPayments smart contract to the network of the provider. - /// ONLY DO THIS IF YOU KNOW WHAT YOU ARE DOING! - pub async fn deploy(provider: P, payment_token_address: Address) -> Self { - let contract = DataPaymentsContract::deploy(provider, payment_token_address) - .await - .expect("Could not deploy contract"); - - DataPaymentsHandler { contract } - } - - pub fn set_provider(&mut self, provider: P) { - let address = *self.contract.address(); - self.contract = DataPaymentsContract::new(address, provider); - } - - /// Pay for quotes. - /// Input: (quote_id, reward_address, amount). - pub async fn pay_for_quotes>( - &self, - data_payments: I, - ) -> Result { - let (calldata, to) = self.pay_for_quotes_calldata(data_payments)?; - - let transaction_request = self - .contract - .provider() - .transaction_request() - .with_to(to) - .with_input(calldata); - - let tx_hash = self - .contract - .provider() - .send_transaction(transaction_request) - .await? - .watch() - .await?; - - Ok(tx_hash) - } - - /// Pay for quotes. - /// Input: (quote_id, reward_address, amount). - /// Returns the transaction calldata. - pub fn pay_for_quotes_calldata>( - &self, - data_payments: I, - ) -> Result<(Calldata, Address), Error> { - let data_payments: Vec = data_payments - .into_iter() - .map(|(hash, addr, amount)| DataPayments::DataPayment { - rewardsAddress: addr, - amount, - quoteHash: hash, - }) - .collect(); - - if data_payments.len() > MAX_TRANSFERS_PER_TRANSACTION { - return Err(Error::TransferLimitExceeded); - } - - let calldata = self - .contract - .submitDataPayments(data_payments) - .calldata() - .to_owned(); - - Ok((calldata, *self.contract.address())) - } -} diff --git a/evmlib/src/contract/mod.rs b/evmlib/src/contract/mod.rs index d428880800..405f0c7fd5 100644 --- a/evmlib/src/contract/mod.rs +++ b/evmlib/src/contract/mod.rs @@ -6,5 +6,5 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -pub mod data_payments; pub mod network_token; +pub mod payment_vault; diff --git a/evmlib/src/contract/payment_vault/error.rs b/evmlib/src/contract/payment_vault/error.rs new file mode 100644 index 0000000000..f4a5b76cce --- /dev/null +++ b/evmlib/src/contract/payment_vault/error.rs @@ -0,0 +1,15 @@ +use alloy::transports::{RpcError, TransportErrorKind}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + ContractError(#[from] alloy::contract::Error), + #[error(transparent)] + RpcError(#[from] RpcError), + #[error(transparent)] + PendingTransactionError(#[from] alloy::providers::PendingTransactionError), + #[error("Payment is invalid.")] + PaymentInvalid, + #[error("Payment verification length must be 3.")] + PaymentVerificationLengthInvalid, +} diff --git a/evmlib/src/contract/payment_vault/handler.rs b/evmlib/src/contract/payment_vault/handler.rs new file mode 100644 index 0000000000..5f21e5574e --- /dev/null +++ b/evmlib/src/contract/payment_vault/handler.rs @@ -0,0 +1,107 @@ +use crate::common::{Address, Amount, Calldata, TxHash}; +use crate::contract::payment_vault::error::Error; +use crate::contract::payment_vault::interface::IPaymentVault::IPaymentVaultInstance; +use crate::contract::payment_vault::interface::{ + IPaymentVault, REQUIRED_PAYMENT_VERIFICATION_LENGTH, +}; +use alloy::network::{Network, TransactionBuilder}; +use alloy::providers::Provider; +use alloy::transports::Transport; + +pub struct PaymentVaultHandler, N: Network> { + pub contract: IPaymentVaultInstance, +} + +impl PaymentVaultHandler +where + T: Transport + Clone, + P: Provider, + N: Network, +{ + /// Create a new PaymentVaultHandler instance from a (proxy) contract's address + pub fn new(contract_address: Address, provider: P) -> Self { + let contract = IPaymentVault::new(contract_address, provider); + Self { contract } + } + + /// Set the provider + pub fn set_provider(&mut self, provider: P) { + let address = *self.contract.address(); + self.contract = IPaymentVault::new(address, provider); + } + + /// Fetch a quote from the contract + pub async fn get_quote>( + &self, + metrics: I, + ) -> Result { + let amount = self.contract.getQuote(metrics.into()).call().await?.price; + Ok(amount) + } + + /// Pay for quotes. + pub async fn pay_for_quotes>>( + &self, + data_payments: I, + ) -> Result { + let (calldata, to) = self.pay_for_quotes_calldata(data_payments)?; + + let transaction_request = self + .contract + .provider() + .transaction_request() + .with_to(to) + .with_input(calldata); + + let tx_hash = self + .contract + .provider() + .send_transaction(transaction_request) + .await? + .watch() + .await?; + + Ok(tx_hash) + } + + /// Returns the pay for quotes transaction calldata. + pub fn pay_for_quotes_calldata>>( + &self, + data_payments: I, + ) -> Result<(Calldata, Address), Error> { + let data_payments: Vec = + data_payments.into_iter().map(|item| item.into()).collect(); + + let calldata = self + .contract + .payForQuotes(data_payments) + .calldata() + .to_owned(); + + Ok((calldata, *self.contract.address())) + } + + /// Verify if payments are valid + pub async fn verify_payment>>( + &self, + payment_verifications: I, + ) -> Result<[IPaymentVault::PaymentVerificationResult; 3], Error> { + let payment_verifications: Vec = payment_verifications + .into_iter() + .map(|v| v.into()) + .collect(); + + if payment_verifications.len() != REQUIRED_PAYMENT_VERIFICATION_LENGTH { + return Err(Error::PaymentVerificationLengthInvalid); + } + + let results = self + .contract + .verifyPayment(payment_verifications) + .call() + .await? + .verificationResults; + + Ok(results) + } +} diff --git a/evmlib/src/contract/payment_vault/implementation.rs b/evmlib/src/contract/payment_vault/implementation.rs new file mode 100644 index 0000000000..64fd9da1f9 --- /dev/null +++ b/evmlib/src/contract/payment_vault/implementation.rs @@ -0,0 +1,30 @@ +use crate::common::{Address, U256}; +use alloy::network::Network; +use alloy::providers::Provider; +use alloy::sol; +use alloy::transports::Transport; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + PaymentVaultImplementation, + "artifacts/PaymentVaultNoProxy.json" +); + +/// Deploys the payment vault contract and returns the contract address +pub async fn deploy( + provider: &P, + network_token_address: Address, + batch_limit: U256, +) -> Address +where + T: Transport + Clone, + P: Provider, + N: Network, +{ + let contract = PaymentVaultImplementation::deploy(provider, network_token_address, batch_limit) + .await + .expect("Could not deploy payment vault implementation contract"); + + *contract.address() +} diff --git a/evmlib/src/contract/payment_vault/interface.rs b/evmlib/src/contract/payment_vault/interface.rs new file mode 100644 index 0000000000..36ec3ee6b8 --- /dev/null +++ b/evmlib/src/contract/payment_vault/interface.rs @@ -0,0 +1,47 @@ +use crate::common::{Address, Amount, QuoteHash, U256}; +use crate::quoting_metrics::QuotingMetrics; +use alloy::primitives::FixedBytes; +use alloy::sol; + +pub const REQUIRED_PAYMENT_VERIFICATION_LENGTH: usize = 5; + +sol!( + #[allow(missing_docs)] + #[derive(Debug)] + #[sol(rpc)] + IPaymentVault, + "abi/IPaymentVault.json" +); + +impl From<(QuoteHash, QuotingMetrics, Address)> for IPaymentVault::PaymentVerification { + fn from(value: (QuoteHash, QuotingMetrics, Address)) -> Self { + Self { + metrics: value.1.into(), + dataPayment: (value.0, value.2, Amount::ZERO).into(), + } + } +} + +impl From<(QuoteHash, Address, Amount)> for IPaymentVault::DataPayment { + fn from(value: (QuoteHash, Address, Amount)) -> Self { + Self { + rewardsAddress: value.1, + amount: value.2, + quoteHash: value.0, + } + } +} + +impl From for IPaymentVault::QuotingMetrics { + fn from(value: QuotingMetrics) -> Self { + Self { + closeRecordsStored: U256::from(value.close_records_stored), + maxRecords: U256::from(value.max_records), + receivedPaymentCount: U256::from(value.received_payment_count), + liveTime: U256::from(value.live_time), + networkDensity: FixedBytes::<32>::from(value.network_density.unwrap_or_default()) + .into(), + networkSize: value.network_size.map(U256::from).unwrap_or_default(), + } + } +} diff --git a/evmlib/src/contract/payment_vault/mod.rs b/evmlib/src/contract/payment_vault/mod.rs new file mode 100644 index 0000000000..efa6f4fbd0 --- /dev/null +++ b/evmlib/src/contract/payment_vault/mod.rs @@ -0,0 +1,55 @@ +use crate::common::{Address, Amount, QuoteHash}; +use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::quoting_metrics::QuotingMetrics; +use crate::utils::http_provider; +use crate::Network; + +pub mod error; +pub mod handler; +pub mod implementation; +pub mod interface; + +pub const MAX_TRANSFERS_PER_TRANSACTION: usize = 256; + +/// Helper function to return a quote for the given quoting metrics. +pub async fn get_market_price( + network: &Network, + quoting_metrics: QuotingMetrics, +) -> Result { + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + payment_vault.get_quote(quoting_metrics).await +} + +/// Helper function to verify whether a data payment is valid. +/// Returns the amount paid to the owned quote hashes. +pub async fn verify_data_payment( + network: &Network, + owned_quote_hashes: Vec, + payment: Vec<(QuoteHash, QuotingMetrics, Address)>, +) -> Result { + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + + let mut amount = Amount::ZERO; + + let payment_verifications: Vec<_> = payment + .into_iter() + .map(interface::IPaymentVault::PaymentVerification::from) + .collect(); + + let payment_verification_results = payment_vault.verify_payment(payment_verifications).await?; + + for payment_verification_result in payment_verification_results { + // CODE REVIEW: should we fail on a single invalid payment? + if !payment_verification_result.isValid { + return Err(error::Error::PaymentInvalid); + } + + if owned_quote_hashes.contains(&payment_verification_result.quoteHash) { + amount += payment_verification_result.amountPaid; + } + } + + Ok(amount) +} diff --git a/evmlib/src/event.rs b/evmlib/src/event.rs deleted file mode 100644 index 5cdda3d91e..0000000000 --- a/evmlib/src/event.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. Please review the Licences for the specific language governing -// permissions and limitations relating to use of the SAFE Network Software. - -use crate::common::{Address, Hash, U256}; -use alloy::primitives::{b256, FixedBytes}; -use alloy::rpc::types::Log; - -// Should be updated when the smart contract changes! -pub(crate) const DATA_PAYMENT_EVENT_SIGNATURE: FixedBytes<32> = - b256!("f998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d580"); // DevSkim: ignore DS173237 - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Topics amount is unexpected. Was expecting 4")] - TopicsAmountUnexpected, - #[error("Event signature is missing")] - EventSignatureMissing, - #[error("Event signature does not match")] - EventSignatureDoesNotMatch, -} - -/// Struct for the ChunkPaymentEvent emitted by the ChunkPayments smart contract. -#[derive(Debug)] -pub(crate) struct ChunkPaymentEvent { - pub rewards_address: Address, - pub amount: U256, - pub quote_hash: Hash, -} - -impl TryFrom for ChunkPaymentEvent { - type Error = Error; - - fn try_from(log: Log) -> Result { - // Verify the amount of topics - if log.topics().len() != 4 { - error!("Topics amount is unexpected. Was expecting 4"); - return Err(Error::TopicsAmountUnexpected); - } - - let topic0 = log - .topics() - .first() - .ok_or(Error::EventSignatureMissing) - .inspect_err(|_| error!("Event signature is missing"))?; - - // Verify the event signature - if topic0 != &DATA_PAYMENT_EVENT_SIGNATURE { - error!( - "Event signature does not match. Expected: {:?}, got: {:?}", - DATA_PAYMENT_EVENT_SIGNATURE, topic0 - ); - return Err(Error::EventSignatureDoesNotMatch); - } - - // Extract the data - let rewards_address = Address::from_slice(&log.topics()[1][12..]); - let amount = U256::from_be_slice(&log.topics()[2][12..]); - let quote_hash = Hash::from_slice(log.topics()[3].as_slice()); - - Ok(Self { - rewards_address, - amount, - quote_hash, - }) - } -} diff --git a/evmlib/src/external_signer.rs b/evmlib/src/external_signer.rs index 20c3aa95df..b7f7ce9b6d 100644 --- a/evmlib/src/external_signer.rs +++ b/evmlib/src/external_signer.rs @@ -7,9 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::common::{Address, Amount, Calldata, QuoteHash, QuotePayment, U256}; -use crate::contract::data_payments::{DataPaymentsHandler, MAX_TRANSFERS_PER_TRANSACTION}; -use crate::contract::network_token::NetworkToken; -use crate::contract::{data_payments, network_token}; +use crate::contract::network_token::{self, NetworkToken}; +use crate::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; use crate::utils::http_provider; use crate::Network; use serde::{Deserialize, Serialize}; @@ -20,7 +19,7 @@ pub enum Error { #[error("Network token contract error: {0}")] NetworkTokenContract(#[from] network_token::Error), #[error("Data payments contract error: {0}")] - DataPaymentsContract(#[from] data_payments::error::Error), + DataPaymentsContract(#[from] crate::contract::payment_vault::error::Error), } /// Approve an address / smart contract to spend this wallet's payment tokens. @@ -73,7 +72,10 @@ pub fn pay_for_quotes_calldata>( let approve_amount = total_amount; let provider = http_provider(network.rpc_url().clone()); - let data_payments = DataPaymentsHandler::new(*network.data_payments_address(), provider); + let data_payments = crate::contract::payment_vault::handler::PaymentVaultHandler::new( + *network.data_payments_address(), + provider, + ); // Divide transfers over multiple transactions if they exceed the max per transaction. let chunks = payments.chunks(MAX_TRANSFERS_PER_TRANSACTION); diff --git a/evmlib/src/lib.rs b/evmlib/src/lib.rs index e0df96d466..6de2343462 100644 --- a/evmlib/src/lib.rs +++ b/evmlib/src/lib.rs @@ -6,8 +6,7 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::common::{Address, QuoteHash, TxHash, U256}; -use crate::transaction::verify_data_payment; +use crate::common::Address; use alloy::primitives::address; use alloy::transports::http::reqwest; use serde::{Deserialize, Serialize}; @@ -21,11 +20,10 @@ extern crate tracing; pub mod common; pub mod contract; pub mod cryptography; -pub(crate) mod event; #[cfg(feature = "external-signer")] pub mod external_signer; +pub mod quoting_metrics; pub mod testnet; -pub mod transaction; pub mod utils; pub mod wallet; @@ -49,7 +47,7 @@ const ARBITRUM_SEPOLIA_PAYMENT_TOKEN_ADDRESS: Address = // Should be updated when the smart contract changes! const ARBITRUM_ONE_DATA_PAYMENTS_ADDRESS: Address = - address!("887930F30EDEb1B255Cd2273C3F4400919df2EFe"); + address!("607483B50C5F06c25cDC316b6d1E071084EeC9f5"); const ARBITRUM_SEPOLIA_DATA_PAYMENTS_ADDRESS: Address = address!("Dd56b03Dae2Ab8594D80269EC4518D13F1A110BD"); @@ -133,23 +131,4 @@ impl Network { Network::Custom(custom) => &custom.data_payments_address, } } - - pub async fn verify_data_payment( - &self, - tx_hash: TxHash, - quote_hash: QuoteHash, - reward_addr: Address, - amount: U256, - quote_expiration_timestamp_in_secs: u64, - ) -> Result<(), transaction::Error> { - verify_data_payment( - self, - tx_hash, - quote_hash, - reward_addr, - amount, - quote_expiration_timestamp_in_secs, - ) - .await - } } diff --git a/evmlib/src/quoting_metrics.rs b/evmlib/src/quoting_metrics.rs new file mode 100644 index 0000000000..801ee4c97c --- /dev/null +++ b/evmlib/src/quoting_metrics.rs @@ -0,0 +1,47 @@ +// Copyright 2024 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use serde::{Deserialize, Serialize}; + +/// Quoting metrics used to generate a quote, or to track peer's status. +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, Debug)] +pub struct QuotingMetrics { + /// the records stored + pub close_records_stored: usize, + /// the max_records configured + pub max_records: usize, + /// number of times that got paid + pub received_payment_count: usize, + /// the duration that node keeps connected to the network, measured in hours + pub live_time: u64, + /// network density from this node's perspective, which is the responsible_range as well + /// This could be calculated via sampling, or equation calculation. + pub network_density: Option<[u8; 32]>, + /// estimated network size + pub network_size: Option, +} + +impl QuotingMetrics { + /// construct an empty QuotingMetrics + pub fn new() -> Self { + Self { + close_records_stored: 0, + max_records: 0, + received_payment_count: 0, + live_time: 0, + network_density: None, + network_size: None, + } + } +} + +impl Default for QuotingMetrics { + fn default() -> Self { + Self::new() + } +} diff --git a/evmlib/src/testnet.rs b/evmlib/src/testnet.rs index e5f1f79708..d9c25bcffd 100644 --- a/evmlib/src/testnet.rs +++ b/evmlib/src/testnet.rs @@ -6,9 +6,10 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::common::Address; -use crate::contract::data_payments::DataPaymentsHandler; +use crate::common::{Address, Amount}; use crate::contract::network_token::NetworkToken; +use crate::contract::payment_vault; +use crate::contract::payment_vault::handler::PaymentVaultHandler; use crate::reqwest::Url; use crate::{CustomNetwork, Network}; use alloy::hex::ToHexExt; @@ -21,6 +22,8 @@ use alloy::providers::{Identity, ProviderBuilder, ReqwestProvider}; use alloy::signers::local::PrivateKeySigner; use alloy::transports::http::{Client, Http}; +const BATCH_LIMIT: u16 = 256; + pub struct Testnet { anvil: AnvilInstance, rpc_url: Url, @@ -120,7 +123,7 @@ pub async fn deploy_data_payments_contract( rpc_url: &Url, anvil: &AnvilInstance, token_address: Address, -) -> DataPaymentsHandler< +) -> PaymentVaultHandler< Http, FillProvider< JoinFill< @@ -146,5 +149,10 @@ pub async fn deploy_data_payments_contract( .on_http(rpc_url.clone()); // Deploy the contract. - DataPaymentsHandler::deploy(provider, token_address).await + let payment_vault_contract_address = + payment_vault::implementation::deploy(&provider, token_address, Amount::from(BATCH_LIMIT)) + .await; + + // Create a handler for the deployed contract + PaymentVaultHandler::new(payment_vault_contract_address, provider) } diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs deleted file mode 100644 index dc8609a4d5..0000000000 --- a/evmlib/src/transaction.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. Please review the Licences for the specific language governing -// permissions and limitations relating to use of the SAFE Network Software. - -use crate::common::{Address, QuoteHash, TxHash, U256}; -use crate::event::{ChunkPaymentEvent, DATA_PAYMENT_EVENT_SIGNATURE}; -use crate::Network; -use alloy::eips::BlockNumberOrTag; -use alloy::primitives::FixedBytes; -use alloy::providers::{Provider, ProviderBuilder}; -use alloy::rpc::types::{Block, Filter, Log, TransactionReceipt}; -use alloy::transports::{RpcError, TransportErrorKind}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - RpcError(#[from] RpcError), - #[error("Transaction is not confirmed")] - TransactionUnconfirmed, - #[error("Transaction was not found")] - TransactionNotFound, - #[error("Transaction has not been included in a block yet")] - TransactionNotInBlock, - #[error("Block was not found")] - BlockNotFound, - #[error("No event proof found")] - EventProofNotFound, - #[error("Payment was done after the quote expired")] - QuoteExpired, -} - -/// Get a transaction receipt by its hash. -pub async fn get_transaction_receipt_by_hash( - network: &Network, - transaction_hash: TxHash, -) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let maybe_receipt = provider - .get_transaction_receipt(transaction_hash) - .await - .inspect_err(|err| error!("Error getting transaction receipt for transaction_hash: {transaction_hash:?} : {err:?}", ))?; - Ok(maybe_receipt) -} - -/// Get a block by its block number. -async fn get_block_by_number(network: &Network, block_number: u64) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let block = provider - .get_block_by_number(BlockNumberOrTag::Number(block_number), true) - .await - .inspect_err(|err| error!("Error getting block by number for {block_number} : {err:?}",))?; - Ok(block) -} - -/// Get transaction logs using a filter. -async fn get_transaction_logs(network: &Network, filter: Filter) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let logs = provider - .get_logs(&filter) - .await - .inspect_err(|err| error!("Error getting logs for filter: {filter:?} : {err:?}"))?; - Ok(logs) -} - -/// Get a DataPaymentMade event, filtered by a hashed chunk address and a node address. -/// Useful for a node if it wants to check if payment for a certain chunk has been made. -async fn get_data_payment_event( - network: &Network, - block_number: u64, - quote_hash: QuoteHash, - reward_addr: Address, - amount: U256, -) -> Result, Error> { - debug!( - "Getting data payment event for quote_hash: {quote_hash:?}, reward_addr: {reward_addr:?}" - ); - let topic1: FixedBytes<32> = FixedBytes::left_padding_from(reward_addr.as_slice()); - - let filter = Filter::new() - .event_signature(DATA_PAYMENT_EVENT_SIGNATURE) - .topic1(topic1) - .topic2(amount) - .topic3(quote_hash) - .from_block(block_number) - .to_block(block_number); - - get_transaction_logs(network, filter).await -} - -/// Verify if a data payment is confirmed. -pub async fn verify_data_payment( - network: &Network, - tx_hash: TxHash, - quote_hash: QuoteHash, - reward_addr: Address, - amount: U256, - quote_expiration_timestamp_in_secs: u64, -) -> Result<(), Error> { - debug!("Verifying data payment for tx_hash: {tx_hash:?}"); - let transaction = get_transaction_receipt_by_hash(network, tx_hash) - .await? - .ok_or(Error::TransactionNotFound)?; - - // If the status is True, it means the tx is confirmed. - if !transaction.status() { - error!("Transaction {tx_hash:?} is not confirmed"); - return Err(Error::TransactionUnconfirmed); - } - - let block_number = transaction - .block_number - .ok_or(Error::TransactionNotInBlock) - .inspect_err(|_| error!("Transaction {tx_hash:?} has not been included in a block yet"))?; - - let block = get_block_by_number(network, block_number) - .await? - .ok_or(Error::BlockNotFound)?; - - // Check if payment was done within the quote expiration timeframe. - if quote_expiration_timestamp_in_secs < block.header.timestamp { - error!("Payment for tx_hash: {tx_hash:?} was done after the quote expired"); - return Err(Error::QuoteExpired); - } - - let logs = - get_data_payment_event(network, block_number, quote_hash, reward_addr, amount).await?; - - for log in logs { - if log.transaction_hash != Some(tx_hash) { - // Wrong transaction. - continue; - } - - if let Ok(event) = ChunkPaymentEvent::try_from(log) { - // Check if the event matches what we expect. - if event.quote_hash == quote_hash - && event.rewards_address == reward_addr - && event.amount >= amount - { - return Ok(()); - } - } - } - - error!("No event proof found for tx_hash: {tx_hash:?}"); - - Err(Error::EventProofNotFound) -} - -#[cfg(test)] -mod tests { - use crate::common::{Address, U256}; - use crate::transaction::{ - get_data_payment_event, get_transaction_receipt_by_hash, verify_data_payment, - }; - use crate::Network; - use alloy::hex::FromHex; - use alloy::primitives::b256; - - #[tokio::test] - async fn test_get_transaction_receipt_by_hash() { - let network = Network::ArbitrumOne; - - let tx_hash = b256!("3304465f38fa0bd9670a426108dd1ddd193e059dcb7c13982d31424646217a36"); // DevSkim: ignore DS173237 - - assert!(get_transaction_receipt_by_hash(&network, tx_hash) - .await - .unwrap() - .is_some()); - } - - #[tokio::test] - async fn test_get_data_payment_event() { - let network = Network::ArbitrumOne; - - let block_number: u64 = 260246302; - let reward_address = Address::from_hex("8AB15A43305854e4AE4E6FBEa0CD1CC0AB4ecB2A").unwrap(); // DevSkim: ignore DS173237 - let amount = U256::from(1); - let quote_hash = b256!("EBD943C38C0422901D4CF22E677DD95F2591CA8D6EBFEA8BAF1BFE9FF5506ECE"); // DevSkim: ignore DS173237 - - let logs = - get_data_payment_event(&network, block_number, quote_hash, reward_address, amount) - .await - .unwrap(); - - assert_eq!(logs.len(), 1); - } - - #[tokio::test] - async fn test_verify_data_payment() { - let network = Network::ArbitrumOne; - - let tx_hash = b256!("3304465f38fa0bd9670a426108dd1ddd193e059dcb7c13982d31424646217a36"); // DevSkim: ignore DS173237 - let quote_hash = b256!("EBD943C38C0422901D4CF22E677DD95F2591CA8D6EBFEA8BAF1BFE9FF5506ECE"); // DevSkim: ignore DS173237 - let reward_address = Address::from_hex("8AB15A43305854e4AE4E6FBEa0CD1CC0AB4ecB2A").unwrap(); // DevSkim: ignore DS173237 - let amount = U256::from(1); - - let result = verify_data_payment( - &network, - tx_hash, - quote_hash, - reward_address, - amount, - 4102441200, - ) - .await; - - assert!(result.is_ok(), "Error: {:?}", result.err()); - } -} diff --git a/evmlib/src/utils.rs b/evmlib/src/utils.rs index 800fa7cc99..f400e4cf58 100644 --- a/evmlib/src/utils.rs +++ b/evmlib/src/utils.rs @@ -164,7 +164,7 @@ fn local_evm_network_from_csv() -> Result { } #[allow(clippy::type_complexity)] -pub(crate) fn http_provider( +pub fn http_provider( rpc_url: reqwest::Url, ) -> FillProvider< JoinFill< diff --git a/evmlib/src/wallet.rs b/evmlib/src/wallet.rs index 643d14bdf9..0f6ba3acea 100644 --- a/evmlib/src/wallet.rs +++ b/evmlib/src/wallet.rs @@ -7,9 +7,10 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::common::{Address, Amount, QuoteHash, QuotePayment, TxHash, U256}; -use crate::contract::data_payments::{DataPaymentsHandler, MAX_TRANSFERS_PER_TRANSACTION}; use crate::contract::network_token::NetworkToken; -use crate::contract::{data_payments, network_token}; +use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; +use crate::contract::{network_token, payment_vault}; use crate::utils::http_provider; use crate::Network; use alloy::hex::ToHexExt; @@ -36,7 +37,7 @@ pub enum Error { #[error("Network token contract error: {0}")] NetworkTokenContract(#[from] network_token::Error), #[error("Chunk payments contract error: {0}")] - ChunkPaymentsContract(#[from] data_payments::error::Error), + ChunkPaymentsContract(#[from] payment_vault::error::Error), } #[derive(Clone)] @@ -119,26 +120,13 @@ impl Wallet { approve_to_spend_tokens(self.wallet.clone(), &self.network, spender, amount).await } - /// Pays for a single quote. Returns transaction hash of the payment. - pub async fn pay_for_quote( - &self, - quote_hash: QuoteHash, - rewards_addr: Address, - amount: U256, - ) -> Result { - self.pay_for_quotes([(quote_hash, rewards_addr, amount)]) - .await - .map(|v| v.values().last().cloned().expect("Infallible")) - .map_err(|err| err.0) - } - /// Function for batch payments of quotes. It accepts an iterator of QuotePayment and returns /// transaction hashes of the payments by quotes. pub async fn pay_for_quotes>( &self, - data_payments: I, + quote_payments: I, ) -> Result, PayForQuotesError> { - pay_for_quotes(self.wallet.clone(), &self.network, data_payments).await + pay_for_quotes(self.wallet.clone(), &self.network, quote_payments).await } /// Build a provider using this wallet. @@ -331,15 +319,22 @@ pub async fn pay_for_quotes>( } let provider = http_provider_with_wallet(network.rpc_url().clone(), wallet); - let data_payments = DataPaymentsHandler::new(*network.data_payments_address(), provider); + let data_payments = PaymentVaultHandler::new(*network.data_payments_address(), provider); + + // remove payments with 0 amount as they don't need to be paid for + let payment_for_batch: Vec = payments + .into_iter() + .filter(|(_, _, amount)| *amount > Amount::ZERO) + .collect(); // Divide transfers over multiple transactions if they exceed the max per transaction. - let chunks = payments.chunks(MAX_TRANSFERS_PER_TRANSACTION); + let chunks = payment_for_batch.chunks(MAX_TRANSFERS_PER_TRANSACTION); let mut tx_hashes_by_quote = BTreeMap::new(); for batch in chunks { let batch: Vec = batch.to_vec(); + debug!( "Paying for batch of quotes of len: {}, {batch:?}", batch.len() @@ -349,6 +344,7 @@ pub async fn pay_for_quotes>( .pay_for_quotes(batch.clone()) .await .map_err(|err| PayForQuotesError(Error::from(err), tx_hashes_by_quote.clone()))?; + info!("Paid for batch of quotes with final tx hash: {tx_hash}"); for (quote_hash, _, _) in batch { diff --git a/evmlib/tests/common/quote.rs b/evmlib/tests/common/quote.rs index 21d05cf189..28f8cbd3a8 100644 --- a/evmlib/tests/common/quote.rs +++ b/evmlib/tests/common/quote.rs @@ -5,6 +5,6 @@ use evmlib::utils::{dummy_address, dummy_hash}; pub fn random_quote_payment() -> QuotePayment { let quote_hash = dummy_hash(); let reward_address = dummy_address(); - let amount = Amount::from(200); + let amount = Amount::from(1); (quote_hash, reward_address, amount) } diff --git a/evmlib/tests/data_payments.rs b/evmlib/tests/payment_vault.rs similarity index 59% rename from evmlib/tests/data_payments.rs rename to evmlib/tests/payment_vault.rs index 26223cfcc1..fe2df5905f 100644 --- a/evmlib/tests/data_payments.rs +++ b/evmlib/tests/payment_vault.rs @@ -11,11 +11,17 @@ use alloy::providers::fillers::{ use alloy::providers::{Identity, ProviderBuilder, ReqwestProvider, WalletProvider}; use alloy::signers::local::{LocalSigner, PrivateKeySigner}; use alloy::transports::http::{Client, Http}; -use evmlib::common::U256; -use evmlib::contract::data_payments::{DataPaymentsHandler, MAX_TRANSFERS_PER_TRANSACTION}; +use evmlib::common::{Amount, U256}; use evmlib::contract::network_token::NetworkToken; +use evmlib::contract::payment_vault::handler::PaymentVaultHandler; +use evmlib::contract::payment_vault::interface::IPaymentVault::DataPayment; +use evmlib::contract::payment_vault::interface::REQUIRED_PAYMENT_VERIFICATION_LENGTH; +use evmlib::contract::payment_vault::{interface, MAX_TRANSFERS_PER_TRANSACTION}; +use evmlib::quoting_metrics::QuotingMetrics; use evmlib::testnet::{deploy_data_payments_contract, deploy_network_token_contract, start_node}; +use evmlib::utils::http_provider; use evmlib::wallet::wallet_address; +use evmlib::Network; async fn setup() -> ( AnvilInstance, @@ -38,7 +44,7 @@ async fn setup() -> ( >, Ethereum, >, - DataPaymentsHandler< + PaymentVaultHandler< Http, FillProvider< JoinFill< @@ -111,9 +117,69 @@ async fn test_deploy() { setup().await; } +#[tokio::test] +async fn test_proxy_reachable() { + let network = Network::ArbitrumOne; + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + + let amount = payment_vault + .get_quote(QuotingMetrics::default()) + .await + .unwrap(); + + assert_eq!(amount, Amount::from(1)); +} + +#[tokio::test] +async fn test_verify_payment() { + let (_anvil, network_token, mut payment_vault) = setup().await; + + let mut quote_payments = vec![]; + + for _ in 0..REQUIRED_PAYMENT_VERIFICATION_LENGTH { + let quote_payment = random_quote_payment(); + quote_payments.push(quote_payment); + } + + let _ = network_token + .approve(*payment_vault.contract.address(), U256::MAX) + .await + .unwrap(); + + // Contract provider has a different account coupled to it, + // so we set it to the same as the network token contract + payment_vault.set_provider(network_token.contract.provider().clone()); + + let result = payment_vault.pay_for_quotes(quote_payments.clone()).await; + + assert!(result.is_ok(), "Failed with error: {:?}", result.err()); + + let payment_verifications: Vec<_> = quote_payments + .into_iter() + .map(|v| interface::IPaymentVault::PaymentVerification { + metrics: QuotingMetrics::default().into(), + dataPayment: DataPayment { + rewardsAddress: v.1, + amount: v.2, + quoteHash: v.0, + }, + }) + .collect(); + + let results = payment_vault + .verify_payment(payment_verifications) + .await + .expect("Verify payment failed"); + + for result in results { + assert!(result.isValid); + } +} + #[tokio::test] async fn test_pay_for_quotes() { - let (_anvil, network_token, mut data_payments) = setup().await; + let (_anvil, network_token, mut payment_vault) = setup().await; let mut quote_payments = vec![]; @@ -123,15 +189,15 @@ async fn test_pay_for_quotes() { } let _ = network_token - .approve(*data_payments.contract.address(), U256::MAX) + .approve(*payment_vault.contract.address(), U256::MAX) .await .unwrap(); // Contract provider has a different account coupled to it, // so we set it to the same as the network token contract - data_payments.set_provider(network_token.contract.provider().clone()); + payment_vault.set_provider(network_token.contract.provider().clone()); - let result = data_payments.pay_for_quotes(quote_payments).await; + let result = payment_vault.pay_for_quotes(quote_payments).await; assert!(result.is_ok(), "Failed with error: {:?}", result.err()); } diff --git a/evmlib/tests/wallet.rs b/evmlib/tests/wallet.rs index 905f719fc3..e9e5f0a077 100644 --- a/evmlib/tests/wallet.rs +++ b/evmlib/tests/wallet.rs @@ -8,9 +8,9 @@ use alloy::providers::ext::AnvilApi; use alloy::providers::{ProviderBuilder, WalletProvider}; use alloy::signers::local::{LocalSigner, PrivateKeySigner}; use evmlib::common::{Amount, TxHash}; -use evmlib::contract::data_payments::MAX_TRANSFERS_PER_TRANSACTION; +use evmlib::contract::payment_vault::{verify_data_payment, MAX_TRANSFERS_PER_TRANSACTION}; +use evmlib::quoting_metrics::QuotingMetrics; use evmlib::testnet::{deploy_data_payments_contract, deploy_network_token_contract, start_node}; -use evmlib::transaction::verify_data_payment; use evmlib::wallet::{transfer_tokens, wallet_address, Wallet}; use evmlib::{CustomNetwork, Network}; use std::collections::HashSet; @@ -67,7 +67,6 @@ async fn funded_wallet(network: &Network, genesis_wallet: EthereumWallet) -> Wal #[tokio::test] async fn test_pay_for_quotes_and_data_payment_verification() { const TRANSFERS: usize = 600; - const EXPIRATION_TIMESTAMP_IN_SECS: u64 = 4102441200; // The year 2100 let (_anvil, network, genesis_wallet) = local_testnet().await; let wallet = funded_wallet(&network, genesis_wallet).await; @@ -87,23 +86,17 @@ async fn test_pay_for_quotes_and_data_payment_verification() { unique_tx_hashes.len(), TRANSFERS.div_ceil(MAX_TRANSFERS_PER_TRANSACTION) ); - - for quote_payment in quote_payments.iter() { - let tx_hash = *tx_hashes.get("e_payment.0).unwrap(); - + for (quote_hash, reward_addr, _) in quote_payments.iter() { let result = verify_data_payment( &network, - tx_hash, - quote_payment.0, - quote_payment.1, - quote_payment.2, - EXPIRATION_TIMESTAMP_IN_SECS, + vec![*quote_hash], + vec![(*quote_hash, QuotingMetrics::default(), *reward_addr)], ) .await; assert!( result.is_ok(), - "Verification failed for: {quote_payment:?}. Error: {:?}", + "Verification failed for: {quote_hash:?}. Error: {:?}", result.err() ); }