diff --git a/Cargo.lock b/Cargo.lock index 943315a..77cdb8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,47 +136,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -585,7 +586,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.33", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -637,9 +638,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -1002,9 +1003,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ "jobserver", "libc", @@ -1186,9 +1187,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "comfy-table" @@ -1225,9 +1226,9 @@ checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1619,15 +1620,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1635,9 +1636,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2082,9 +2083,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdlimit" @@ -2121,9 +2122,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "file-per-thread-logger" @@ -2159,7 +2160,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "scale-info", ] @@ -2183,9 +2184,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", @@ -2830,9 +2831,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2844,7 +2845,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3003,7 +3004,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.14", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3020,7 +3021,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.11", + "rustls 0.21.12", "rustls-native-certs", "tokio", "tokio-rustls", @@ -3166,7 +3167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3232,7 +3233,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.6", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -3255,6 +3256,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -3272,9 +3279,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -3341,7 +3348,7 @@ dependencies = [ "globset", "hyper", "jsonrpsee-types", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "rustc-hash", "serde", @@ -3476,7 +3483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -3487,7 +3494,7 @@ checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" dependencies = [ "kvdb", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "regex", "rocksdb", "smallvec", @@ -3507,9 +3514,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -3601,7 +3608,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "quick-protobuf", "rand", @@ -3621,7 +3628,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "smallvec", "trust-dns-resolver", ] @@ -3783,7 +3790,7 @@ dependencies = [ "libp2p-identity", "libp2p-tls", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "quinn-proto", "rand", "rustls 0.20.9", @@ -3899,7 +3906,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "quicksink", "rw-stream-sink", "soketto", @@ -4061,11 +4068,29 @@ dependencies = [ "keystream", ] +[[package]] +name = "lite-json" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0e787ffe1153141a0f6f6d759fdf1cc34b1226e088444523812fd412a5cca2" +dependencies = [ + "lite-parser", +] + +[[package]] +name = "lite-parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d5f9dc37c52d889a21fd701983d02bb6a84f852c5140a6c80ef4557f7dc29e" +dependencies = [ + "paste", +] + [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4230,7 +4255,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.33", + "rustix 0.38.34", ] [[package]] @@ -4314,7 +4339,7 @@ dependencies = [ "hashlink", "lioness", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "rand_chacha", "rand_distr", @@ -4581,6 +4606,7 @@ dependencies = [ "sp-inherents", "sp-io", "sp-keyring", + "sp-keystore", "sp-runtime", "sp-timestamp", "substrate-build-script-utils", @@ -4599,6 +4625,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "log", "pallet-aura", "pallet-balances", "pallet-grandpa", @@ -4716,9 +4743,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -4920,11 +4947,16 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "lite-json", + "log", "parity-scale-codec", "scale-info", + "serde", + "serde_json", "sp-core", "sp-io", "sp-runtime", + "sp-std 8.0.0", ] [[package]] @@ -5005,7 +5037,7 @@ dependencies = [ "log", "lz4", "memmap2", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "siphasher", "snap", @@ -5070,12 +5102,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -5094,15 +5126,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -5149,9 +5181,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -5160,9 +5192,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -5170,9 +5202,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", @@ -5183,9 +5215,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -5301,15 +5333,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite 0.2.14", - "rustix 0.38.33", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -5502,15 +5534,15 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", "lazy_static", "memchr", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "thiserror", ] @@ -5522,7 +5554,7 @@ checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prometheus-client-derive-encode", ] @@ -5786,6 +5818,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -6037,9 +6078,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.33" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -6062,9 +6103,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -6278,7 +6319,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -6309,7 +6350,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-state-db", "schnellru", @@ -6333,7 +6374,7 @@ dependencies = [ "libp2p-identity", "log", "mockall", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-utils", "serde", @@ -6391,7 +6432,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "sc-block-builder", "sc-chain-spec", @@ -6447,7 +6488,7 @@ version = "0.10.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-executor-common", "sc-executor-wasmtime", "schnellru", @@ -6484,7 +6525,7 @@ dependencies = [ "cfg-if", "libc", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rustix 0.36.17", "sc-allocator", "sc-executor-common", @@ -6516,7 +6557,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "array-bytes 6.2.2", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "serde_json", "sp-application-crypto", "sp-core", @@ -6540,7 +6581,7 @@ dependencies = [ "mixnet", "multiaddr", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-network", "sc-transaction-pool-api", @@ -6573,7 +6614,7 @@ dependencies = [ "log", "mockall", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "partial_sort", "pin-project", "rand", @@ -6745,7 +6786,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "sc-client-api", "sc-network", @@ -6780,7 +6821,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -6850,7 +6891,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-chain-spec", "sc-client-api", "sc-transaction-pool-api", @@ -6880,7 +6921,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "rand", "sc-chain-spec", @@ -6937,7 +6978,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 dependencies = [ "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sp-core", ] @@ -6948,7 +6989,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 dependencies = [ "log", "parity-db", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-keystore", "sp-api", @@ -6989,7 +7030,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "rand", "sc-utils", @@ -7011,7 +7052,7 @@ dependencies = [ "libc", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "regex", "rustc-hash", "sc-client-api", @@ -7051,7 +7092,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-transaction-pool-api", "sc-utils", @@ -7092,7 +7133,7 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prometheus", "sp-arithmetic", ] @@ -7227,11 +7268,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -7240,9 +7281,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -7274,9 +7315,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -7292,9 +7333,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -7482,9 +7523,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -7606,7 +7647,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "schnellru", "sp-api", "sp-consensus", @@ -7720,7 +7761,7 @@ dependencies = [ "log", "merlin", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "paste", "primitive-types", "rand", @@ -7769,7 +7810,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -7792,7 +7833,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -7808,7 +7849,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "proc-macro2", "quote", @@ -7829,7 +7870,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "environmental", "parity-scale-codec", @@ -7901,7 +7942,7 @@ version = "0.27.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sp-core", "sp-externalities 0.19.0", "thiserror", @@ -8014,7 +8055,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8046,7 +8087,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "Inflector", "expander", @@ -8093,7 +8134,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "smallvec", "sp-core", @@ -8138,7 +8179,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" [[package]] name = "sp-storage" @@ -8156,7 +8197,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8193,7 +8234,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "parity-scale-codec", "tracing", @@ -8236,7 +8277,7 @@ dependencies = [ "memory-db", "nohash-hasher", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "scale-info", "schnellru", @@ -8293,7 +8334,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "impl-trait-for-tuples", "log", @@ -8617,7 +8658,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.33", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -8636,7 +8677,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.33", + "rustix 0.38.34", "windows-sys 0.48.0", ] @@ -8767,10 +8808,10 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project-lite 0.2.14", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -8803,7 +8844,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.11", + "rustls 0.21.12", "tokio", ] @@ -8821,9 +8862,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -8831,7 +8872,6 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.14", "tokio", - "tracing", ] [[package]] @@ -8896,7 +8936,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.6", + "winnow 0.6.8", ] [[package]] @@ -9115,7 +9155,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "resolv-conf", "smallvec", "thiserror", @@ -9231,9 +9271,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -9750,14 +9790,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.33", + "rustix 0.38.34", ] [[package]] name = "wide" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a1851a719f11d1d2fea40e15c72f6c00de8c142d7ac47c1441cc7e4d0d5bc6" +checksum = "0f0e39d2c603fdc0504b12b458cf1f34e0b937ed2f4f2dc20796e3e86f34e11f" dependencies = [ "bytemuck", "safe_arch", @@ -9787,11 +9827,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -10044,9 +10084,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -10120,7 +10160,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "static_assertions", ] @@ -10136,18 +10176,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", diff --git a/node/Cargo.toml b/node/Cargo.toml index 398c95a..4d8c781 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -41,6 +41,7 @@ sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk. sp-timestamp = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } sp-inherents = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } sp-keyring = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +sp-keystore = {git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-system = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } diff --git a/node/src/service.rs b/node/src/service.rs index fabacf9..bfb792f 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -66,6 +66,23 @@ pub fn new_partial( )?; let client = Arc::new(client); + + let keystore = keystore_container.keystore(); + if config.offchain_worker.enabled { + // Initialize seed for signing transaction using off-chain workers. This is a convenience + // so learners can see the transactions submitted simply running the node. + // Typically these keys should be inserted with RPC calls to `author_insertKey`. + + // For pallet-ocw + sp_keystore::Keystore::sr25519_generate_new( + &*keystore, + node_template_runtime::pallet_template::KEY_TYPE, + Some("//Alice"), + ).expect("Creating key with account Alice should succeed."); + + // For pallet-example-offchain-worker + } + let telemetry = telemetry.map(|(worker, telemetry)| { task_manager.spawn_handle().spawn("telemetry", None, worker.run()); telemetry diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml index 424b6d8..d090e1e 100644 --- a/pallets/template/Cargo.toml +++ b/pallets/template/Cargo.toml @@ -16,15 +16,22 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } +lite-json = { version = "0.2.0", default-features = false } + +serde = { version = "1.0.197", features = ["derive"], optional = true } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +log = { version = "0.4.21", default-features = false } +sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-std = { version = "8.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } [dev-dependencies] -sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } -sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } -sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } [features] default = ["std"] @@ -34,6 +41,12 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", + "log/std", + "sp-io/std", + "serde", + "sp-std/std", + "lite-json/std", + "serde_json/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 9550d3d..05779ca 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -1,108 +1,1059 @@ -#![cfg_attr(not(feature = "std"), no_std)] +// This file is part of Substrate. -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// -pub use pallet::*; +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 -#[cfg(test)] -mod mock; +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! +//! # Offchain Worker Example Pallet +//! +//! The Offchain Worker Example: A simple pallet demonstrating +//! concepts, APIs and structures common to most offchain workers. +//! +//! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's +//! documentation. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to +//! be used in production.** +//! +//! ## Overview +//! +//! In this example we are going to build a very simplistic, naive and definitely NOT +//! production-ready oracle for BTC/USD price. +//! Offchain Worker (OCW) will be triggered after every block, fetch the current price +//! and prepare either signed or unsigned transaction to feed the result back on chain. +//! The on-chain logic will simply aggregate the results and store last `64` values to compute +//! the average price. +//! Additional logic in OCW is put in place to prevent spamming the network with both signed +//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only +//! one unsigned transaction floating in the network. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::traits::Get; +use frame_system::{ + self as system, + offchain::{ + AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction, + SignedPayload, Signer, SigningTypes, SubmitTransaction, + }, + pallet_prelude::BlockNumberFor, +}; +use lite_json::json::JsonValue; +use scale_info::prelude::string::String; +use sp_core::crypto::KeyTypeId; +use sp_runtime::{ + offchain::{ + http, + storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, + Duration, + }, + traits::Zero, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + BoundedVec, RuntimeDebug, +}; +use sp_std::vec::Vec; #[cfg(test)] mod tests; -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; -pub mod weights; -pub use weights::*; +/// Defines application identifier for crypto keys of this module. +/// +/// Every module that deals with signatures needs to declare its unique identifier for +/// its crypto keys. +/// When offchain worker is signing transactions it's going to request keys of type +/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction. +/// The keys can be inserted manually via RPC (see `author_insertKey`). +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!"); + +/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers. +/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment +/// the types with this pallet-specific identifier. +pub mod crypto { + use super::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + MultiSignature, MultiSigner, + }; + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + // implemented for mock runtime in test + impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> + for TestAuthId + { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + +pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::pallet_prelude::{OptionQuery, *}; use frame_system::pallet_prelude::*; - #[pallet::pallet] - pub struct Pallet(_); - - /// Configure the pallet by specifying the parameters and types on which it depends. + /// This pallet's configuration trait #[pallet::config] - pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. + pub trait Config: CreateSignedTransaction> + frame_system::Config { + /// The identifier type for an offchain worker. + type AuthorityId: AppCrypto; + + /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Type representing the weight of this pallet - type WeightInfo: WeightInfo; + + // Configuration parameters + + /// A grace period after we send transaction. + /// + /// To avoid sending too many transactions, we only attempt to send one + /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate + /// sending between distinct runs of this offchain worker. + #[pallet::constant] + type GracePeriod: Get>; + + /// Number of blocks of cooldown after unsigned transaction is included. + /// + /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval` + /// blocks. + #[pallet::constant] + type UnsignedInterval: Get>; + + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple pallets send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + + /// Maximum number of prices. + #[pallet::constant] + type MaxPrices: Get; + + #[pallet::constant] + type StringLimit: Get; } - // The pallet's runtime storage items. - // https://docs.substrate.io/main-docs/build/runtime-storage/ - #[pallet::storage] - #[pallet::getter(fn something)] - // Learn more about declaring storage items: - // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items - pub type Something = StorageValue<_, u32>; + // Define string_limit variable of type Get - // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/main-docs/build/events-errors/ - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Event documentation should end with an array that provides descriptive names for event - /// parameters. [something, who] - SomethingStored { something: u32, who: T::AccountId }, + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + #[derive( + Encode, + Decode, + Clone, + PartialEq, + Eq, + MaxEncodedLen, + frame_support::pallet_prelude::RuntimeDebugNoBound, + TypeInfo, + )] + #[scale_info(skip_type_params(T))] + pub struct PropertyInfoData { + pub id: u32, + pub bedrooms: u32, + pub bathrooms: u32, + pub summary: BoundedVec::StringLimit>, + pub property_sub_type: BoundedVec::StringLimit>, + pub first_visible_date: BoundedVec::StringLimit>, + pub display_size: BoundedVec::StringLimit>, + pub display_address: BoundedVec::StringLimit>, + // pub property_images: BoundedVec::StringLimit>, } - // Errors inform users that something went wrong. - #[pallet::error] - pub enum Error { - /// Error names should be descriptive. - NoneValue, - /// Errors should have helpful documentation associated with them. - StorageOverflow, + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Offchain Worker entry point. + /// + /// By implementing `fn offchain_worker` you declare a new offchain worker. + /// This function will be called when the node is fully synced and a new best block is + /// successfully imported. + /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might + /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs), + /// so the code should be able to handle that. + /// You can use `Local Storage` API to coordinate runs of the worker. + fn offchain_worker(block_number: BlockNumberFor) { + // Note that having logs compiled to WASM may cause the size of the blob to increase + // significantly. You can use `RuntimeDebug` custom derive to hide details of the types + // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable + // all logging and thus, remove any logging from the WASM. + log::info!("Hello World from offchain workers!"); + + let price = Self::fetch_price().map_err(|_| "Failed to fetch price"); + TestProperties::::put(price.unwrap()); + + // Since off-chain workers are just part of the runtime code, they have direct access + // to the storage and other included pallets. + // + // We can easily import `frame_system` and retrieve a block hash of the parent block. + let parent_hash = >::block_hash(block_number - 1u32.into()); + log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash); + + // It's a good practice to keep `fn offchain_worker()` function minimal, and move most + // of the code to separate `impl` block. + // Here we call a helper function to calculate current average price. + // This function reads storage entries of the current state. + let average: Option = Self::average_price(); + log::debug!("Current price: {:?}", average); + + // For this example we are going to send both signed and unsigned transactions + // depending on the block number. + // Usually it's enough to choose one or the other. + let should_send = Self::choose_transaction_type(block_number); + let res = match should_send { + TransactionType::Signed => Self::fetch_price_and_send_signed(), + TransactionType::UnsignedForAny => + Self::fetch_price_and_send_unsigned_for_any_account(block_number), + TransactionType::UnsignedForAll => + Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), + TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), + TransactionType::None => Ok(()), + }; + if let Err(e) = res { + log::error!("Error: {}", e); + } + } } - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + /// A public part of the pallet. #[pallet::call] impl Pallet { - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. + /// Submit new price to the list. + /// + /// This method is a public function of the module and can be called from within + /// a transaction. It appends given `price` to current list of prices. + /// In our example the `offchain worker` will create, sign & submit a transaction that + /// calls this function passing the price. + /// + /// The transaction needs to be signed (see `ensure_signed`) check, so that the caller + /// pays a fee to execute it. + /// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting + /// excessive transactions, but note that it doesn't ensure the price oracle is actually + /// working and receives (and provides) meaningful data. + /// This example is not focused on correctness of the oracle itself, but rather its + /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::do_something())] - pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { - // Check that the extrinsic was signed and get the signer. - // This function will return an error if the extrinsic is not signed. - // https://docs.substrate.io/main-docs/build/origins/ + #[pallet::weight({0})] + pub fn submit_price( + origin: OriginFor, + price: PropertyInfoData, + ) -> DispatchResultWithPostInfo { + // Retrieve sender of the transaction. let who = ensure_signed(origin)?; + // Add the price to the on-chain list. + // Self::add_price(Some(who), price); + TestProperties::::put(price.clone()); + Self::deposit_event(Event::NewPrice { price: price.id.clone(), who }); + Ok(().into()) + } - // Update storage. - >::put(something); + /// Submit new price to the list via unsigned transaction. + /// + /// Works exactly like the `submit_price` function, but since we allow sending the + /// transaction without a signature, and hence without paying any fees, + /// we need a way to make sure that only some transactions are accepted. + /// This function can be called only once every `T::UnsignedInterval` blocks. + /// Transactions that call that function are de-duplicated on the pool level + /// via `validate_unsigned` implementation and also are rendered invalid if + /// the function has already been called in current "session". + /// + /// It's important to specify `weight` for unsigned calls as well, because even though + /// they don't charge fees, we still don't want a single block to contain unlimited + /// number of such transactions. + /// + /// This example is not focused on correctness of the oracle itself, but rather its + /// purpose is to showcase offchain worker capabilities. + #[pallet::call_index(1)] + #[pallet::weight({0})] + pub fn submit_price_unsigned( + origin: OriginFor, + _block_number: BlockNumberFor, + price: PropertyInfoData, + ) -> DispatchResultWithPostInfo { + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the price to the on-chain list, but mark it as coming from an empty address. + // Self::add_price(None, price); + // now increment the block number at which we expect next unsigned transaction. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) + } - // Emit an event. - Self::deposit_event(Event::SomethingStored { something, who }); - // Return a successful DispatchResultWithPostInfo - Ok(()) + #[pallet::call_index(2)] + #[pallet::weight({0})] + pub fn submit_price_unsigned_with_signed_payload( + origin: OriginFor, + price_payload: PricePayload>, + _signature: T::Signature, + ) -> DispatchResultWithPostInfo { + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the price to the on-chain list, but mark it as coming from an empty address. + // Self::add_price(None, price_payload.price); + // now increment the block number at which we expect next unsigned transaction. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) } + } - /// An example dispatchable that may throw a custom error. - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::cause_error())] - pub fn cause_error(origin: OriginFor) -> DispatchResult { - let _who = ensure_signed(origin)?; - - // Read a value from storage. - match >::get() { - // Return an error if the value has not been set. - None => return Err(Error::::NoneValue.into()), - Some(old) => { - // Increment the value read from storage; will error in the event of overflow. - let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; - // Update the value in storage with the incremented result. - >::put(new); - Ok(()) + /// Events for the pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event generated when new price is accepted to contribute to the average. + NewPrice { price: u32, who: T::AccountId }, + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + /// Validate unsigned call to this module. + /// + /// By default unsigned transactions are disallowed, but implementing the validator + /// here we make sure that some particular calls (the ones produced by offchain worker) + /// are being whitelisted and marked as valid. + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + // Firstly let's check that we call the right function. + if let Call::submit_price_unsigned_with_signed_payload { + price_payload: ref payload, + ref signature, + } = call + { + let signature_valid = + SignedPayload::::verify::(payload, signature.clone()); + if !signature_valid { + return InvalidTransaction::BadProof.into() + } + Self::validate_transaction_parameters(&payload.block_number) + } else if let Call::submit_price_unsigned { block_number, price: new_price } = call { + Self::validate_transaction_parameters(block_number) + } else { + InvalidTransaction::Call.into() + } + } + } + + /// A vector of recently submitted prices. + /// + /// This is used to calculate average price, should have bounded size. + #[pallet::storage] + pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>; + + /// Defines the block when next unsigned transaction will be accepted. + /// + /// To prevent spam of unsigned (and unpaid!) transactions on the network, + /// we only allow one transaction every `T::UnsignedInterval` blocks. + /// This storage entry defines when new transaction is going to be accepted. + #[pallet::storage] + pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; + + // A List of test properties + #[pallet::storage] + #[pallet::getter(fn testproperties)] + pub type TestProperties = StorageValue<_, PropertyInfoData, OptionQuery>; +} + +/// Payload used by this example crate to hold price +/// data required to submit a transaction. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +pub struct PricePayload { + block_number: BlockNumber, + price: u32, + public: Public, +} + +impl SignedPayload for PricePayload> { + fn public(&self) -> T::Public { + self.public.clone() + } +} + +enum TransactionType { + Signed, + UnsignedForAny, + UnsignedForAll, + Raw, + None, +} + +impl Pallet { + /// Chooses which transaction type to send. + /// + /// This function serves mostly to showcase `StorageValue` helper + /// and local storage usage. + /// + /// Returns a type of transaction that should be produced in current run. + fn choose_transaction_type(block_number: BlockNumberFor) -> TransactionType { + /// A friendlier name for the error that is going to be returned in case we are in the grace + /// period. + const RECENTLY_SENT: () = (); + + // Start off by creating a reference to Local Storage value. + // Since the local storage is common for all offchain workers, it's a good practice + // to prepend your entry with the module name. + let val = StorageValueRef::persistent(b"example_ocw::last_send"); + // The Local Storage is persisted and shared between runs of the offchain workers, + // and offchain workers may run concurrently. We can use the `mutate` function, to + // write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set` + // low-level method of local storage API, which means that only one worker + // will be able to "acquire a lock" and send a transaction if multiple workers + // happen to be executed concurrently. + let res = + val.mutate(|last_send: Result>, StorageRetrievalError>| { + match last_send { + // If we already have a value in storage and the block number is recent enough + // we avoid sending another transaction at this time. + Ok(Some(block)) if block_number < block + T::GracePeriod::get() => + Err(RECENTLY_SENT), + // In every other case we attempt to acquire the lock and send a transaction. + _ => Ok(block_number), + } + }); + + // The result of `mutate` call will give us a nested `Result` type. + // The first one matches the return of the closure passed to `mutate`, i.e. + // if we return `Err` from the closure, we get an `Err` here. + // In case we return `Ok`, here we will have another (inner) `Result` that indicates + // if the value has been set to the storage correctly - i.e. if it wasn't + // written to in the meantime. + match res { + // The value has been set correctly, which means we can safely send a transaction now. + Ok(block_number) => { + // We will send different transactions based on a random number. + // Note that this logic doesn't really guarantee that the transactions will be sent + // in an alternating fashion (i.e. fairly distributed). Depending on the execution + // order and lock acquisition, we may end up for instance sending two `Signed` + // transactions in a row. If a strict order is desired, it's better to use + // the storage entry for that. (for instance store both block number and a flag + // indicating the type of next transaction to send). + let transaction_type = block_number % 4u32.into(); + if transaction_type == Zero::zero() { + TransactionType::Signed + } else if transaction_type == BlockNumberFor::::from(1u32) { + TransactionType::UnsignedForAny + } else if transaction_type == BlockNumberFor::::from(2u32) { + TransactionType::UnsignedForAll + } else { + TransactionType::Raw + } + }, + // We are in the grace period, we should not send a transaction this time. + Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None, + // We wanted to send a transaction, but failed to write the block number (acquire a + // lock). This indicates that another offchain worker that was running concurrently + // most likely executed the same logic and succeeded at writing to storage. + // Thus we don't really want to send the transaction, knowing that the other run + // already did. + Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None, + } + } + + /// A helper function to fetch the price and send signed transaction. + fn fetch_price_and_send_signed() -> Result<(), &'static str> { + let signer = Signer::::all_accounts(); + if !signer.can_sign() { + return Err( + "No local accounts available. Consider adding one via `author_insertKey` RPC.", + ) + } + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Using `send_signed_transaction` associated type we create and submit a transaction + // representing the call, we've just created. + // Submit signed will return a vector of results for all accounts that were found in the + // local keystore with expected `KEY_TYPE`. + let results = signer.send_signed_transaction(|_account| { + // Received price is wrapped into a call to `submit_price` public function of this + // pallet. This means that the transaction, when executed, will simply call that + // function passing `price` as an argument. + Call::submit_price { price: price.clone() } + }); + + for (acc, res) in &results { + match res { + Ok(()) => log::info!("[{:?}] Submitted price of {:?} cents", acc.id, price), + Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), + } + } + + Ok(()) + } + + /// A helper function to fetch the price and send a raw unsigned transaction. + fn fetch_price_and_send_raw_unsigned( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + TestProperties::::put(price.clone()); + + // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `price` as an argument. + let call = Call::submit_price_unsigned { block_number, price }; + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // + // By default unsigned transactions are disallowed, so we need to whitelist this case + // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully + // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // attack vectors. See validation logic docs for more details. + // + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_any_account( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using any account + let (_, result) = Signer::::any_account() + .send_unsigned_transaction( + |account| PricePayload { + price: price.id.clone(), + block_number, + public: account.public.clone(), + }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, }, + ) + .ok_or("No local accounts accounts available.")?; + result.map_err(|()| "Unable to submit transaction")?; + + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_all_accounts( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using all accounts + let transaction_results = Signer::::all_accounts() + .send_unsigned_transaction( + |account| PricePayload { + price: price.id.clone(), + block_number, + public: account.public.clone(), + }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, + }, + ); + for (_account_id, result) in transaction_results.into_iter() { + if result.is_err() { + return Err("Unable to submit transaction") } } + + Ok(()) + } + + /// Fetch current price and return the result in cents. + fn fetch_price() -> Result, http::Error> { + // We want to keep the offchain worker execution time reasonable, so we set a hard-coded + // deadline to 2s to complete the external call. + // You can also wait indefinitely for the response, however you may still get a timeout + // coming from the host machine. + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); + // Initiate an external HTTP GET request. + // This is using high-level wrappers from `sp_runtime`, for the low-level calls that + // you can find in `sp_io`. The API is trying to be similar to `request`, but + // since we are running in a custom WASM execution environment we can't simply + // import the library here. + + let request = http::Request::get( + "https://ipfs.io/ipfs/QmZ3Dn5B2UMuv9PFr1Ba3NGSKft2rwToBKCPaCTCmSab4k?filename=testing_data.json" + ); + + // We set the deadline for sending of the request, note that awaiting response can + // have a separate deadline. Next we send the request, before that it's also possible + // to alter request headers or stream body content in case of non-GET requests. + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + + // The request is already being processed by the host, we are free to do anything + // else in the worker (we can send multiple concurrent requests too). + // At some point however we probably want to check the response though, + // so we can block current thread and wait for it to finish. + // Note that since the request is being driven by the host, we don't have to wait + // for the request to have it complete, we will just not read the response. + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + // Let's check the status code before we proceed to reading the response. + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + + // Next we want to fully read the response body and collect it to a vector of bytes. + // Note that the return object allows you to read the body in chunks as well + // with a way to control the deadline. + let body = response.body().collect::>(); + + // Create a str slice from the body. + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF8 body"); + http::Error::Unknown + })?; + + let price = match Self::parse_price(body_str) { + Some(price) => Ok(price), + None => { + log::warn!("Unable to extract price from the response: {:?}", body_str); + Err(http::Error::Unknown) + }, + }?; + + // log::warn!("Got property: {:?} cents", price); + + Ok(price) + } + + /// Parse the price from the given JSON string using `lite-json`. + /// + /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. + fn parse_price(price_str: &str) -> Option> { + let val = lite_json::parse_json(price_str); + let id = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'id' field in the first object + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + let val = lite_json::parse_json(price_str); + let bedrooms = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'bedrooms' field in the first object + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bedrooms".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + let val = lite_json::parse_json(price_str); + let bathrooms = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'bathrooms' field in the first object + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bathrooms".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let summary = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'summary' field in the first object + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("summary".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let propertySubType = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'propertySubType' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("propertySubType".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let firstVisibleDate = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'firstVisibleDate' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("firstVisibleDate".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let displaySize = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'displaySize' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("displaySize".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let displayAddress = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'displayAddress' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("displayAddress".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let propertyImages = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'propertyImages' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("propertyImages".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let id = id.integer as u32; + let bedrooms = bedrooms.integer as u32; + let bathrooms = bathrooms.integer as u32; + let summary: &str = &summary.iter().collect::(); + let propertySubType: &str = &propertySubType.iter().collect::(); + let firstVisibleDate: &str = &firstVisibleDate.iter().collect::(); + let displaySize: &str = &displaySize.iter().collect::(); + let displayAddress: &str = &displayAddress.iter().collect::(); + // let propertyImages: &str = &propertyImages.iter().collect::(); + + let property = PropertyInfoData { + id, + bedrooms, + bathrooms, + summary: summary.as_bytes().to_vec().try_into().unwrap(), + property_sub_type: propertySubType.as_bytes().to_vec().try_into().unwrap(), + first_visible_date: firstVisibleDate.as_bytes().to_vec().try_into().unwrap(), + display_size: displaySize.as_bytes().to_vec().try_into().unwrap(), + display_address: displayAddress.as_bytes().to_vec().try_into().unwrap(), + // property_images: propertyImages.as_bytes().to_vec().try_into().unwrap(), + }; + + Some(property) + + // Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) + } + + /// Add new price to the list. + // fn add_price(maybe_who: Option, price: u32) { + // log::info!("Adding to the average: {}", price); + // >::mutate(|prices| { + // if prices.try_push(price).is_err() { + // prices[(price % T::MaxPrices::get()) as usize] = price; + // } + // }); + + // let average = Self::average_price() + // .expect("The average is not empty, because it was just mutated; qed"); + // log::info!("Current average price is: {}", average); + // // here we are raising the NewPrice event + // Self::deposit_event(Event::NewPrice { price, maybe_who }); + // } + + /// Calculate current average price. + fn average_price() -> Option { + let prices = Prices::::get(); + if prices.is_empty() { + None + } else { + Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32) + } + } + + fn validate_transaction_parameters(block_number: &BlockNumberFor) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at = NextUnsignedAt::::get(); + if &next_unsigned_at > block_number { + return InvalidTransaction::Stale.into() + } + // Let's make sure to reject transactions from the future. + let current_block = >::block_number(); + if ¤t_block < block_number { + return InvalidTransaction::Future.into() + } + + // We prioritize transactions that are more far away from current average. + // + // Note this doesn't make much sense when building an actual oracle, but this example + // is here mostly to show off offchain workers capabilities, not about building an + // oracle. + // let avg_price = Self::average_price() + // .map(|price| if &price > new_price { price - new_price } else { new_price - price }) + // .unwrap_or(0); + + ValidTransaction::with_tag_prefix("ExampleOffchainWorker") + // We set base priority to 2**20 and hope it's included before any other + // transactions in the pool. Next we tweak the priority depending on how much + // it differs from the current average. (the more it differs the more priority it + // has). + .priority(T::UnsignedPriority::get().saturating_add(100 as _)) + // This transaction does not require anything else to go before into the pool. + // In theory we could require `previous_unsigned_at` transaction to go first, + // but it's not necessary in our case. + //.and_requires() + // We set the `provides` tag to be the same as `next_unsigned_at`. This makes + // sure only one transaction produced after `next_unsigned_at` will ever + // get to the transaction pool and will end up in the block. + // We can still have multiple transactions compete for the same "spot", + // and the one with higher priority will replace other one in the pool. + .and_provides(next_unsigned_at) + // The transaction is only valid for next 5 blocks. After that it's + // going to be revalidated by the pool. + .longevity(5) + // It's fine to propagate that transaction to other peers, which means it can be + // created even by nodes that don't produce blocks. + // Note that sometimes it's better to keep it for yourself (if you are the block + // producer), since for instance in some schemes others may copy your solution and + // claim a reward. + .propagate(true) + .build() } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 83a7c12..032d08c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/substrate-developer-hub/substrate-node-template targets = ["x86_64-unknown-linux-gnu"] [dependencies] +log = { version = '0.4.14', default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } @@ -96,6 +97,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "log/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7670ebd..e7e92fe 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -5,11 +5,12 @@ // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); - +// use crate::pallet_template::Call; +// use frame_system::Call; use pallet_grandpa::AuthorityId as GrandpaId; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, Encode}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, One, Verify}, @@ -20,6 +21,8 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use sp_runtime::SaturatedConversion; +// use codec::Encode; use frame_support::genesis_builder_helper::{build_config, create_default_config}; // A few exports that help ease life for downstream crates. @@ -233,6 +236,7 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub FeeMultiplier: Multiplier = Multiplier::one(); + pub const StringLimit: u32 = 5000; } impl pallet_transaction_payment::Config for Runtime { @@ -250,10 +254,83 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = pallet_sudo::weights::SubstrateWeight; } +// impl pallet_ocw::Config for Runtime { +// type AuthorityId = pallet_template::crypto::TestAuthId; +// type Call = Call; +// type Event = Event; +// } + + +parameter_types! { + pub const UnsignedPriority: u64 = 1 << 20; +} + /// Configure the pallet-template in pallets/template. impl pallet_template::Config for Runtime { + type AuthorityId = pallet_template::crypto::TestAuthId; + type GracePeriod = ConstU32<5>; + type UnsignedInterval = ConstU32<2>; + type UnsignedPriority = UnsignedPriority; + type MaxPrices = ConstU32<64>; type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_template::weights::SubstrateWeight; + type StringLimit = StringLimit; + +} + + +impl frame_system::offchain::CreateSignedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_transaction>( + call: RuntimeCall, + public: ::Signer, + account: AccountId, + nonce: Nonce, + ) -> Option<(RuntimeCall, ::SignaturePayload)> { + let tip = 0; + // take the biggest period possible. + let period = + BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; + let current_block = System::block_number() + .saturated_into::() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); + + let extra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + ); + let raw_payload = SignedPayload::new(call, extra) + .map_err(|e| { + log::warn!("Unable to create signed payload: {:?}", e); + }) + .ok()?; + let signature: MultiSignature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; + let address = account; + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (sp_runtime::MultiAddress::Id(address), signature.into(), extra))) + } +} + +impl frame_system::offchain::SigningTypes for Runtime { + type Public = ::Signer; + type Signature = Signature; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = UncheckedExtrinsic; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -327,6 +404,8 @@ mod benches { } impl_runtime_apis! { + + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION