Make subxt-core ready for publishing (#1508)

* Move Extrinsic decoding things to subxt_core and various tidy-ups

* A couple more fixes and fmt

* first pass moving tx logic to subxt_core

* cargo fmt

* fix wasm example

* clippy

* more clippy

* WIP Adding examples and such

* Move storage functionality more fully to subxt_core and nice examples for storage and txs

* Add example for events

* consistify how addresses/payloads are exposed in subxt-core and add runtime API fns

* Add runtime API core example

* fmt

* remove scale-info patch

* Add a little to the top level docs

* swap args around

* clippy

* cargo fmt and fix wasm-example

* doc fixes

* no-std-ise new subxt-core additions

* alloc, not core

* more no-std fixes

* A couple more fixes

* Add back extrinsic decode test
This commit is contained in:
James Wilson authored and GitHub committed 2024-04-15 15:20:11 +01:00
1 parent b527c857ea
commit 1e111ea9db
89 files changed
+4458 -3499

No files matched your search

Generated
+258 -237
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -43,10 +43,10 @@
},
"id": "polkadot",
"lightSyncState": {
"babeEpochChanges": "0x04123ae85881861156a074a07110a5f59142b0b762ac89422d0a601116ee1e8c49721f320101d369001100000000337300110000000004efc8cb869ae5714a4f4f590372f3a9a76d748e084658d023958a3a549d616486cb283201013373001100000000937c00110000000004100d6ae96c2f95f1bcfbedfa0dd48a4b97a6ffee38103c65d55cc8e7d609125f1a32320101937c001100000000f38500110000000000000c100d6ae96c2f95f1bcfbedfa0dd48a4b97a6ffee38103c65d55cc8e7d609125f1a32320101d020000000000000937c0011000000006009000000000000a50432904ec260963195582a1d339f9fbd2d5d76f551160105c922423cad0d94482f010000000000000076441c448aefcf40a72128edc9b44a4f7ef9c267c503e98f5f9c72d5e9e92f000100000000000000526e055ade4ac425e9b5f93ce4394601335d3599a5b742603126d78ce99a4a540100000000000000d0e8ddfdeacd8e163b01ca4da1a4e856312b0de412b492247070f2721f7d803801000000000000008008c1b7061a2402634fc15d3849e2913863beb9f55024bc8cc7fa7d6e397817010000000000000024a51a9bdb67213626d1b52bd8dbc0e9b94ea88ea417aa17b56212d1cea3e1780100000000000000488d520146f681678e9d229f5e29354edf65e521246b28a4175c0502e060b9200100000000000000da663fef002e63d5de9676a1a930abe7b0d8088657d67a3f24076bce08c661490100000000000000a86e2248480b1f6ad903a965344ecef69838aae812ba3c39d3d9eaae22963f2a01000000000000004ceef9b2b8e3b5cd971f7557c3a3ad02c9c7a7bbc7365c2db5622fa74853790d0100000000000000f03555628bc649cebf75046ab9663faa1a5421df323f99c82782b7a3d162ce080100000000000000aca7a86b6fcf25daa7f0767bfe36b284f3100dc1ee4dcc971fbb3244bb57221c0100000000000000ba395fcb33327501109ed40712c7a372d25bf3339edfabe2a1964fb7b5d4d82401000000000000009cb389c209afbebda2b12113e0266b3d30f46e2a91aaf4451c193d640288357101000000000000004c9886e2e62ee92a6778d6a7928052732044b64fa61969cd8e37f3f7e3406f7201000000000000005237e4509f24bc9e8bf9bfd78b594219d86853ae16601f692f10c9b16b88f83b01000000000000006869737a2873891c0e4ef80a69e331f64ffc3663c9f3cdf86b440bb41827a252010000000000000098192016f83ef2fe74dba1ecc6526efc61b79f3177c00dc7c053160391858f70010000000000000078c35105ea4cd9d6ea0ba91b2c19e78f60931bef5918c1bd2e14259229e7c330010000000000000006d2846642c835b0fb1ff1ef908449376aa8e7c0f3352e55da9a1e97e9528b230100000000000000f8a681118ea0bb9dfde2813ce5144eeb5cee881c27883de1c0c759255ec326540100000000000000ac8b2b59f77eb5f3725961767878166a3817747018f18cf48c933e284320b90e010000000000000064838c162c251bc34c0f5a2696183fb518527d4681821dbc3ad53b004a8da7220100000000000000e67c524e443cda4cd27dfcce70488a5d23a93c8c46a5e83ef5a69eff3a02fa5a0100000000000000a42289d68c963358f3c450a850242bb53121e73e17f0e2ea2b93327446bea33c01000000000000006c50859d9dde209012eb3868c1a3a6e80ae8fa528ace9df68e92dbbae60368200100000000000000a8e470f2339e3ed0da456780dd9207e9c6a02459238ec3057a6ade01fd6cd60a01000000000000005c7f05d77f853f3eb8d4989fce36d0808b6d2b881e26f13c01e939b5d489a57d01000000000000002addf696d5a42b0a3dada689731e00c991db936909df869ac6600662abc35310010000000000000072ed7192625403acef94aa9189f9f179deb89e60478816ca96c79b742f757e750100000000000000b4ffe3c5959ff37c11c6f19d91926cb97aa4bf0f604919e1753bb55664d061680100000000000000bc4c9b5425f9a2aa09e3239332c702dedbfffa2b9f4cc1b978b307fc1fa1337c0100000000000000e07d5b19d98a6f8ef5c964b507fcd88f500a2a0a9afd5cffe9d4e3f74d83b27f010000000000000024532b17fc5fb786660c52aeb03150799165dd05e88ce359189189a4b771f51e0100000000000000b0033313d23127061d7f052a965bfeead348e6fe54f9915474ed52b843382e790100000000000000be18787b5177e58664cd893fdaa20a3ca1bc9f1acbbb15036b046ae49c17c15b010000000000000072a85a9dcc0af833b5a21a51ecbc4a9855c2bef8848e080ed70ac48cd1efdd5f0100000000000000c49cfd152ce3ac1fa142b3b6e720cb173eb7f43c82f021418253a070036f870c01000000000000006ec27dfa7ac9d35eb002c55d3e4d56b4df928dd5c44323ac228305741c9e09360100000000000000125becf853d8dc03bc97114ca0c751f6045bd7ef5580e58d32e3b47d7ac5b60301000000000000009237113cf97fee5fafbeb82de593f6125e9c498bd59f0369625f46b326c7d9170100000000000000722d7d57085c250af633f2d461874d59aebc8021eb1112ec8805db5ddea53e5a0100000000000000acfd0e585d89c54d57bdbcfcbef574217ce0997c82fab720b2d4e12ce61bd3610100000000000000562fd7529c92f9370dd6d7ce60d06ea4a826cbefa238231993ecfc9e7725ca240100000000000000a20797f3a8669a909adb22a3aee2844dba82d231cc33f5d9be0e2c533cb82a6701000000000000006a1274b6748bf822ba80c9a9416abd3cf591b7aee6adabb8359b7dbed8ebb3770100000000000000dc1274e0689c7a3eac31b41ce352719529e65c01f26999f74ce17c9905fce87401000000000000009c9bc1074cf465c0dd39f74bfdf16a67f81339ba2ad117a0c7030b4f0678771701000000000000005c0494412a296a92296160ac510697d97288753829a5e9d02eafb5f101e60f510100000000000000d8b38c77823eb1912bf8b743cca6c655ea1844ee468fbd78b394bf222bf8bc2001000000000000007251d6c376064f07f27d2532b05c091bd0978067b078c9f8d9984fd4216bdd4b0100000000000000487c6aad1cc26893dc7e498916a4c23d303f098c04b0a8b0c8242fbb7e73a16e010000000000000088f9a16886165d00612032cb1b36669ad62b7325536f499191e711134c71b6070100000000000000d6152835cb59040a40a5a4ee3a4d481937b7ee9d4a5649abeac6c535d9e177080100000000000000bc4b95226de4e10f91e0254cfe4541232252deaa347f567df124aba81bac4d3f01000000000000001eb833e31087d6baffee4e7f1d9a8e3b7013ea392c81f74313a354ba82e92f6b01000000000000009c8c7c8c91df0ed53864f69017d530b5ebdaLine truncated
"babeFinalizedBlockWeight": 5011401,
"finalizedBlockHeader": "0xe054f8f24f0d2997d51756fce6cc7ecb7731a656dc50abb672766ecf0ec6d20fa2e6c804fd14c44bcfc4f7a1f66608af6d08e9b52feff6130ad0eb519fbefad408e6919a7755b0321b2c6b9d3ebd65f57347dd9abf9d1213988a24eea6c41c1063fdd8c90c0642414245b5010311000000cd7a0011000000003cd64eeb4d432862e7ed6446ded0b2e42934b6349bebc98e7e3179938e5d2350696bea1b896f6cd45e2f05baddb61aed7ba6b4edecc5399f8049f909db29820f7c8b859264a59c98872a9716e83af46db366cca16d8a2e1ed64e49488095b30d044245454684030752c33f306b93f0ac407f15fc50c711a0a540d37531e0720ac1eb5a0ea53fa605424142450101b2984693bdc6dc781fcbb1114cd597c7ab6827faebe7d71c9dc2da43f376a416428caad6394a6b745f695c85b06698683c7277d633f85405068f7028feb54681",
"grandpaAuthoritySet": "0xa5046f706506065685b322054d22e8a1f23ca9df75c32a88dda5214ad58b553b4cca01000000000000008a239af78d4659897af698b5670533fa6d215864be8c41e3a2fc4309f9f83dcb0100000000000000e2b9e72d9202e99526fa626d9a6651dfd7c1daec8fc6ba1130af96f7d21a42da010000000000000056b838dd2005e499be47ceef086df4ca9c5fbc1f81968391c31af062a8a8bef301000000000000004a3fc0fe1020c7f460c7bd23d0b657c03368552b2f0a12d80a1c406fd066e0db01000000000000009b992b5e9b99eb2cd3b2b648d6db81ed722a2feb4938d998b58dcb5d159fadfa0100000000000000aee9de3938173700b3e3f4fbf2b200ae296173a0854157f40de2598dd4cad8f10100000000000000262562429feb60edcc7f1db025cc39e6c7b113e26e884338b3f844168e6f6f420100000000000000e3606b27e4146ffe1437860746968e0a026071120cc09d88494126a86d7281090100000000000000b9284bdf22aa83317140b91288555c900d208634f50d07ce8b772d149364085101000000000000005a40b78008713bef1a35714bd32e0ec4c8905dd0728aeb4943280efe766936080100000000000000e8d7333e457fa740db924866152f31c3bfa6124c0367b0878a61aff2d5d6e27e0100000000000000426b2be4596a759a15028d84e08c5c56440e6cede68d088f47c671cc3387c3e6010000000000000070ba6e5985990ba1b1392713236f1b50df750f10f744cc6eb95fa7c5cfbc68760100000000000000116768c765c7ce5a5e895a5342d1f56f1a118901d8d36fe584bab325506f3b280100000000000000e511d441d6d3c822cb276bb2a14b1c8071d9fe1995802383fb5dcf8a92edcfc2010000000000000003e385d1bbcaeed57930ff625035cd65781e3fe9de1d350d783ba261e678c0ac0100000000000000be2235b9d9c9164f494dd688000fb569a37d5c47912ef99b9ebda9318d13345e01000000000000000e0ac68072ac35b8f19f98d13385b6eb75cf6fd4d513d9a2abfe9711b19883bb01000000000000007818639057900f8fb58e3aa8180f6108c251884a9fcb8041ca645cf612bc1eda0100000000000000a8dbfc6cb88ac105e25b9dc7f11e883631824647cd4d0c18014bb6239627b2900100000000000000ed7a7ea18c2d3b3effd9e8d7031592bc99517ca321c1faf7370f7d6d0fdebc47010000000000000041ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b0100000000000000890f5c296681b8c23038b3d36a491ca9e0e7e809e6455b2bcd28a13700f81abb010000000000000008cc1d6c743da905511a39a6f0a68809a6bc32f8ac25e53bb2d6aca4f493fc9b0100000000000000acd71d4269b3ba2a9de822d494c4d841708e1519c08de41d7fc6153eaf48da8101000000000000007c1024f3b93e54cb1cdde0d2f685e2e16cadabe59e407ebc45eabb07efebca6101000000000000000f42866aa6927f1c5916a8a1a5a5cba2d88e1fff957d80df701617d93ee6407801000000000000005cba2c6f569da1edc3778308da406f266aa53140381dbb3f14f22909ba6e30040100000000000000dac2e90de824b109043f70818d53eabe05e79d518bda504e951d7f4056b0b2720100000000000000652f455eb3d30486151c716a4031bc02bd00254ef3e8290fd29f946d958a46b60100000000000000589fc35b132ef9d7ed9a6787a203e5a3a37f8cd6c775f47ac5e589d55d381a640100000000000000a34b3f8e84aa86be3b47260f67720030795fc96379e5b31cc6c87c2f4d1cc3e001000000000000007a99eb5f9dd892ab541176abfcb3a9b87e184c9f5737f0c1e69b90f62c522aef0100000000000000dccafad8bba5affbc80095564afc9376881907073a060979fabbb962bef08d5601000000000000003bf5b70cb5789c7ac4f6a4c97e6fabfe7a58fe9fed1d79f39c2aabc2c5816ece0100000000000000aa3be8112cdf851411fe9e00fe9ef0a6b58217326ba7c1ab7ed9ce8a34620b250100000000000000c1c1ff5f66fefb5c0849e3b2333411d1f7e9dd4888600e334e7a33623f15fa3601000000000000001e6ce7e1c936b8872d22b1d24f0d5f4eb0ce13d6cda46bbc9b1b93812cbe674c0100000000000000594f51d31b1ae51a8c8ec48f5ab2428607440dd9c8f690ea377e99444529acb501000000000000002f40f2660559a4472aa7d2b5dac22c8925b9baeaec9fa2463cef2a77eb9d3a1b0100000000000000fa2c21f1a03638d4cb35202102240c2b85f5667924d5838cc503721117049da601000000000000008c461493dcd314f4b384169f3741954d8cde37bc8ad988149e40ddf18b4b95e101000000000000008aabd4f6d7ecec7a749c83fc7be33f3b4818cca6f93a4927b4964900df9454e50100000000000000fcc0d954c7d519851e0094cd9a015b4789022a37091e3a04791d0731bd85f0d301000000000000003948bfd09bbbc2fe6ac5dfe53146e32df50828eecf7b2b18cdcff8dc385cbd750100000000000000a2a4af2a11ae7ba812fcc32845583d0354a0f2ffb1271a53c9a976a0f47fff4401000000000000002385953f1ac49461b9c243159d9668b26e7322e19fff7bc18d90634f0f767ce8010000000000000058e304af91397247f704ce95d60845629aa59a50fba1c13896bb2300708db47201000000000000008663371a5899d5e7a8bc99b9a6ce24c8c3f5f1149c1e70accf9560405c1a5bf201000000000000003d28789c6d574f3aa6d14360471901c5bf8255d072c2209fa22ac773bd32e5bf010000000000000041763df8284ae8248ee0f649982ffc7d4f613e07c7bb83cd554aca5dcf8f56250100000000000000cc0c6a91c7971438b621cd9a3bd37a67f52ec4093336585d1e2c73416195e28a0100000000000000de52bfa88c4a76bb22fbebb0f078dfcc5a327034e0044e8f8034f2a96e7adfef0100000000000000bf3fd3d4065b306638662c3e789d830a63d9b343cef5f863d346058387844b58010000000000000005197187ac7eb8d0a3cfadd32f1a7fb662e7e4a16ea718af3f1e4437b865205b01000000000000007d09497b4d65162d28d4940f8eaf8342798e7794ec0f5028d163fea6156cb4f30100000000000000c84074bb67af84cdf84b9df1dadc1188cd75709a8d11bba65f2835d76f38c4730100000000000000aaecb94cc62ac90656976daa8955353e0ff22fcabd55715903be2f958988bc070100000000000000b98c920666586f392d129d480e78a965764ac82de45e691589b3aab7831a7f020100000000000000cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad39010000000000000034a18d21097f479eecbe51b91c522a798582caafc787134bab6888854742406e010000000000000027791Line truncated
"babeEpochChanges": "0x0480df609c40171d0f62924a7d83db3a5c52c2f78dbe0dab0497b8786e659872e8daff340101734e031100000000d35703110000000004877d77dc713f5db844a860efb4ea0372343b3deca76cc7fa0827780217a6c4d33609350101d3570311000000003361031100000000046204e22a5bed51dced77343ac746411fa965babf73ef76b57e185eebd64f32af8d123501013361031100000000936a03110000000000000c6204e22a5bed51dced77343ac746411fa965babf73ef76b57e185eebd64f32af8d123501011f2100000000000033610311000000006009000000000000a50432904ec260963195582a1d339f9fbd2d5d76f551160105c922423cad0d94482f010000000000000076441c448aefcf40a72128edc9b44a4f7ef9c267c503e98f5f9c72d5e9e92f000100000000000000526e055ade4ac425e9b5f93ce4394601335d3599a5b742603126d78ce99a4a540100000000000000d0e8ddfdeacd8e163b01ca4da1a4e856312b0de412b492247070f2721f7d803801000000000000008008c1b7061a2402634fc15d3849e2913863beb9f55024bc8cc7fa7d6e397817010000000000000024a51a9bdb67213626d1b52bd8dbc0e9b94ea88ea417aa17b56212d1cea3e1780100000000000000e033368bc2edee96d72e43448824a642d53939c6c190693971b05ccd1054464a01000000000000004ceef9b2b8e3b5cd971f7557c3a3ad02c9c7a7bbc7365c2db5622fa74853790d0100000000000000aca7a86b6fcf25daa7f0767bfe36b284f3100dc1ee4dcc971fbb3244bb57221c01000000000000004ea4871b3ccdd1276ee604eb0e06aa4a62536dfe8fb053042a8f36caddb94c510100000000000000ba395fcb33327501109ed40712c7a372d25bf3339edfabe2a1964fb7b5d4d82401000000000000009cb389c209afbebda2b12113e0266b3d30f46e2a91aaf4451c193d640288357101000000000000004c9886e2e62ee92a6778d6a7928052732044b64fa61969cd8e37f3f7e3406f7201000000000000005237e4509f24bc9e8bf9bfd78b594219d86853ae16601f692f10c9b16b88f83b01000000000000006869737a2873891c0e4ef80a69e331f64ffc3663c9f3cdf86b440bb41827a252010000000000000098192016f83ef2fe74dba1ecc6526efc61b79f3177c00dc7c053160391858f700100000000000000a6f10752adf745ad55866031836194ad027ed8d6710d9853ef62982bcf45fd0e010000000000000078c35105ea4cd9d6ea0ba91b2c19e78f60931bef5918c1bd2e14259229e7c330010000000000000006d2846642c835b0fb1ff1ef908449376aa8e7c0f3352e55da9a1e97e9528b230100000000000000f8a681118ea0bb9dfde2813ce5144eeb5cee881c27883de1c0c759255ec326540100000000000000ac8b2b59f77eb5f3725961767878166a3817747018f18cf48c933e284320b90e0100000000000000e67c524e443cda4cd27dfcce70488a5d23a93c8c46a5e83ef5a69eff3a02fa5a0100000000000000a42289d68c963358f3c450a850242bb53121e73e17f0e2ea2b93327446bea33c0100000000000000caa53cf3e8ff5604d82bac45868bf3e25115f7e8f4d1cdfada68f8487d03e91b01000000000000000219b9856c2d50592c3821d7df9329795c9f6bc1e8d62b76072a4289617e5a060100000000000000e4ba5ffce05c90e6a5eea52b15922b6b3a78d13ca98f53fdbe08e5c24ce3c8410100000000000000a8e470f2339e3ed0da456780dd9207e9c6a02459238ec3057a6ade01fd6cd60a01000000000000005c7f05d77f853f3eb8d4989fce36d0808b6d2b881e26f13c01e939b5d489a57d01000000000000002addf696d5a42b0a3dada689731e00c991db936909df869ac6600662abc35310010000000000000072ed7192625403acef94aa9189f9f179deb89e60478816ca96c79b742f757e750100000000000000b4ffe3c5959ff37c11c6f19d91926cb97aa4bf0f604919e1753bb55664d061680100000000000000bc4c9b5425f9a2aa09e3239332c702dedbfffa2b9f4cc1b978b307fc1fa1337c0100000000000000e07d5b19d98a6f8ef5c964b507fcd88f500a2a0a9afd5cffe9d4e3f74d83b27f01000000000000001ee57665488b5ad6f7358157b73009a4ec4b14b4a03c976487e188f54bff7d640100000000000000be18787b5177e58664cd893fdaa20a3ca1bc9f1acbbb15036b046ae49c17c15b01000000000000004c50abd9279d48439dc8f93aaf679e47981f25372b63d838786dcaf7a3f8d0740100000000000000c49cfd152ce3ac1fa142b3b6e720cb173eb7f43c82f021418253a070036f870c01000000000000006ec27dfa7ac9d35eb002c55d3e4d56b4df928dd5c44323ac228305741c9e093601000000000000002803ef26679fce0495aa4066e053c4fa9cda5bfb54e24497fcb1bcef2902cb400100000000000000125becf853d8dc03bc97114ca0c751f6045bd7ef5580e58d32e3b47d7ac5b60301000000000000009237113cf97fee5fafbeb82de593f6125e9c498bd59f0369625f46b326c7d9170100000000000000722d7d57085c250af633f2d461874d59aebc8021eb1112ec8805db5ddea53e5a0100000000000000562fd7529c92f9370dd6d7ce60d06ea4a826cbefa238231993ecfc9e7725ca240100000000000000a20797f3a8669a909adb22a3aee2844dba82d231cc33f5d9be0e2c533cb82a6701000000000000006a1274b6748bf822ba80c9a9416abd3cf591b7aee6adabb8359b7dbed8ebb3770100000000000000dc1274e0689c7a3eac31b41ce352719529e65c01f26999f74ce17c9905fce87401000000000000009c9bc1074cf465c0dd39f74bfdf16a67f81339ba2ad117a0c7030b4f06787717010000000000000090708d1a49ef0acfea32147a109d08d9aa15ee975b5237d56ca9b8e324108d0f0100000000000000d8b38c77823eb1912bf8b743cca6c655ea1844ee468fbd78b394bf222bf8bc2001000000000000007251d6c376064f07f27d2532b05c091bd0978067b078c9f8d9984fd4216bdd4b0100000000000000487c6aad1cc26893dc7e498916a4c23d303f098c04b0a8b0c8242fbb7e73a16e0100000000000000d6152835cb59040a40a5a4ee3a4d481937b7ee9d4a5649abeac6c535d9e177080100000000000000bc4b95226de4e10f91e0254cfe4541232252deaa347f567df124aba81bac4d3f01000000000000001eb833e31087d6baffee4e7f1d9a8e3b7013ea392c81f74313a354ba82e92f6b01000000000000009c8c7c8c91df0ed53864f69017d530b5ebda3ab4bde3c67027e70893213d533a010000000000000018ec239eec5457ba11a41518e7c5f98266d1bdbd67602641d04706f95dba5c560100000000000000ee0c4369d7e5dca53a68ef223db7c858f2a9Line truncated
"babeFinalizedBlockWeight": 5057865,
"finalizedBlockHeader": "0x5a979217b503aa78e90ebcae20f6f220a4bc4ed6d776942fb850bcda517989e6a64bd4040e16472342dec8607f270f2d6ce99ceacb891aaddceaa3d7eace37129bca3338e2f6d0766dc5fcfc489eaa7a5d8284183494f34caa74847fed5c4fd8e05cbf290c0642414245b50103c9000000305803110000000098f2bd8c49c25f3a3ef98d184934741cd144aaf6d1ff9dd46479f449786a3d2941fb3a6b91133bd27200e607d83ce69787200ef74cf2708e876705e0657a9408debff3ab5bba7ae0c88832de6beee0f14de74abdf86218035889d763234fd00c04424545468403eb7277261742e27bca09f32dba2deb3ad262a859449d9bea27887340ba819a9205424142450101eaa77bb7093126995fc6905e2a89ee5d68de063bfae58eaaee9044deb42bef2372c3964800c13860b60bccf420d69bdbb6d1ea9cfdc58bfb9ea3ba0881236783",
"grandpaAuthoritySet": "0xa5046f706506065685b322054d22e8a1f23ca9df75c32a88dda5214ad58b553b4cca01000000000000008a239af78d4659897af698b5670533fa6d215864be8c41e3a2fc4309f9f83dcb0100000000000000e2b9e72d9202e99526fa626d9a6651dfd7c1daec8fc6ba1130af96f7d21a42da010000000000000056b838dd2005e499be47ceef086df4ca9c5fbc1f81968391c31af062a8a8bef301000000000000004a3fc0fe1020c7f460c7bd23d0b657c03368552b2f0a12d80a1c406fd066e0db01000000000000009b992b5e9b99eb2cd3b2b648d6db81ed722a2feb4938d998b58dcb5d159fadfa0100000000000000a8b2ff688a9b1709f0371b49fe31d4d5a362d3fa11f64fcdc2941df9351edebe01000000000000003255f477781804204f24ddfd080346b0e75c7c5a92b37cec1fbbe79fa93407b001000000000000005a40b78008713bef1a35714bd32e0ec4c8905dd0728aeb4943280efe766936080100000000000000907fc1295e137738f8862d34490ad0746b80926e236e05ef8b958084f1d91a850100000000000000e8d7333e457fa740db924866152f31c3bfa6124c0367b0878a61aff2d5d6e27e0100000000000000426b2be4596a759a15028d84e08c5c56440e6cede68d088f47c671cc3387c3e6010000000000000070ba6e5985990ba1b1392713236f1b50df750f10f744cc6eb95fa7c5cfbc68760100000000000000e511d441d6d3c822cb276bb2a14b1c8071d9fe1995802383fb5dcf8a92edcfc2010000000000000003e385d1bbcaeed57930ff625035cd65781e3fe9de1d350d783ba261e678c0ac0100000000000000be2235b9d9c9164f494dd688000fb569a37d5c47912ef99b9ebda9318d13345e010000000000000049c0902ee37b569482da5474a15458b5dee16102fbc09b45878bb05f4a717acf01000000000000000e0ac68072ac35b8f19f98d13385b6eb75cf6fd4d513d9a2abfe9711b19883bb01000000000000007818639057900f8fb58e3aa8180f6108c251884a9fcb8041ca645cf612bc1eda0100000000000000a8dbfc6cb88ac105e25b9dc7f11e883631824647cd4d0c18014bb6239627b2900100000000000000ed7a7ea18c2d3b3effd9e8d7031592bc99517ca321c1faf7370f7d6d0fdebc47010000000000000041ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b0100000000000000890f5c296681b8c23038b3d36a491ca9e0e7e809e6455b2bcd28a13700f81abb010000000000000098df914cfd089eee8993fbfbd1522d6a31700c98bdc3a31cf69014a0ac123ecf010000000000000045ee96016a01c74778a6589fb2934ee9d83fd7a9e36509c4ef794d3d4f309bc60100000000000000f7e159462ec9dc0e54c6f6b8a83c4b551ee91613cad4208bbd563f16418328710100000000000000acd71d4269b3ba2a9de822d494c4d841708e1519c08de41d7fc6153eaf48da8101000000000000007c1024f3b93e54cb1cdde0d2f685e2e16cadabe59e407ebc45eabb07efebca6101000000000000000f42866aa6927f1c5916a8a1a5a5cba2d88e1fff957d80df701617d93ee6407801000000000000005cba2c6f569da1edc3778308da406f266aa53140381dbb3f14f22909ba6e30040100000000000000dac2e90de824b109043f70818d53eabe05e79d518bda504e951d7f4056b0b2720100000000000000652f455eb3d30486151c716a4031bc02bd00254ef3e8290fd29f946d958a46b60100000000000000589fc35b132ef9d7ed9a6787a203e5a3a37f8cd6c775f47ac5e589d55d381a64010000000000000044d68d3dbde0fbc3d667b7a004882caa9413b54785d820dc3654aff3a5b8e57d0100000000000000dccafad8bba5affbc80095564afc9376881907073a060979fabbb962bef08d56010000000000000078774f4f6130efaa55b5a06d1ef94ca420739dff79cbc24074bba841053e0aa20100000000000000aa3be8112cdf851411fe9e00fe9ef0a6b58217326ba7c1ab7ed9ce8a34620b250100000000000000c1c1ff5f66fefb5c0849e3b2333411d1f7e9dd4888600e334e7a33623f15fa360100000000000000f23b0362de32f952573d64c1f52ac4b8db0a7e15e7ad49a0c81d8f15b1f2ca8e0100000000000000594f51d31b1ae51a8c8ec48f5ab2428607440dd9c8f690ea377e99444529acb501000000000000002f40f2660559a4472aa7d2b5dac22c8925b9baeaec9fa2463cef2a77eb9d3a1b0100000000000000fa2c21f1a03638d4cb35202102240c2b85f5667924d5838cc503721117049da601000000000000008c461493dcd314f4b384169f3741954d8cde37bc8ad988149e40ddf18b4b95e101000000000000008aabd4f6d7ecec7a749c83fc7be33f3b4818cca6f93a4927b4964900df9454e50100000000000000fcc0d954c7d519851e0094cd9a015b4789022a37091e3a04791d0731bd85f0d301000000000000003948bfd09bbbc2fe6ac5dfe53146e32df50828eecf7b2b18cdcff8dc385cbd750100000000000000a2a4af2a11ae7ba812fcc32845583d0354a0f2ffb1271a53c9a976a0f47fff440100000000000000214ce2bd2912889eca3c69f45a3677989d66e20c3b26a52bbd378a4a71f5aa05010000000000000058e304af91397247f704ce95d60845629aa59a50fba1c13896bb2300708db47201000000000000008663371a5899d5e7a8bc99b9a6ce24c8c3f5f1149c1e70accf9560405c1a5bf201000000000000003d28789c6d574f3aa6d14360471901c5bf8255d072c2209fa22ac773bd32e5bf010000000000000099f574f1c57e766376a6976ceb3f44ea0420be7426d5c55b016a91e34091a0980100000000000000cc0c6a91c7971438b621cd9a3bd37a67f52ec4093336585d1e2c73416195e28a0100000000000000de52bfa88c4a76bb22fbebb0f078dfcc5a327034e0044e8f8034f2a96e7adfef0100000000000000bf3fd3d4065b306638662c3e789d830a63d9b343cef5f863d346058387844b58010000000000000005197187ac7eb8d0a3cfadd32f1a7fb662e7e4a16ea718af3f1e4437b865205b01000000000000007d09497b4d65162d28d4940f8eaf8342798e7794ec0f5028d163fea6156cb4f30100000000000000a5aa8fc244c0d6036f96ca0585214448d94a124157dad6a09b585d1638c2362d01000000000000007c077f9fbba618995d7df802a578934d36a5c0bec16dfcbaa7bce3879b4c44ce0100000000000000b98c920666586f392d129d480e78a965764ac82de45e691589b3aab7831a7f020100000000000000cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad39010000000000000034a18d21097f479eecbe51b91c522a798582caafc787134bab6888854742406e010000000000000027791Line truncated
},
"name": "Polkadot",
"properties": {
Binary file not shown.
Binary file not shown.
+5 -2
View File
@@ -45,7 +45,7 @@ pub fn explore_calls(
Usage:
subxt explore pallet {pallet_name} calls <CALL>
explore a specific call of this pallet
{calls}
"}
};
@@ -154,7 +154,10 @@ fn mocked_offline_client(metadata: Metadata) -> OfflineClient<SubstrateConfig> {
H256::from_str("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")
.expect("Valid hash; qed");
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
let runtime_version = subxt::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
OfflineClient::<SubstrateConfig>::new(genesis_hash, runtime_version, metadata)
}
+2 -2
View File
@@ -92,8 +92,8 @@ pub fn generate_calls(
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> #crate_path::tx::Payload<types::#struct_name> {
#crate_path::tx::Payload::new_static(
) -> #crate_path::tx::payload::Payload<types::#struct_name> {
#crate_path::tx::payload::Payload::new_static(
#pallet_name,
#call_name,
types::#struct_name { #( #call_args, )* },
+2 -2
View File
@@ -68,8 +68,8 @@ pub fn generate_constants(
Ok(quote! {
#docs
pub fn #fn_name(&self) -> #crate_path::constants::Address<#return_ty> {
#crate_path::constants::Address::new_static(
pub fn #fn_name(&self) -> #crate_path::constants::address::Address<#return_ty> {
#crate_path::constants::address::Address::new_static(
#pallet_name,
#constant_name,
[#(#constant_hash,)*]
+2 -2
View File
@@ -70,8 +70,8 @@ fn generate_custom_value_fn(
};
Some(quote!(
pub fn #fn_name_ident(&self) -> #crate_path::custom_values::StaticAddress<#return_ty, #decodable> {
#crate_path::custom_values::StaticAddress::new_static(#name, [#(#custom_value_hash,)*])
pub fn #fn_name_ident(&self) -> #crate_path::custom_values::address::StaticAddress<#return_ty, #decodable> {
#crate_path::custom_values::address::StaticAddress::new_static(#name, [#(#custom_value_hash,)*])
}
))
}
+2 -2
View File
@@ -135,8 +135,8 @@ fn generate_runtime_api(
let method = quote!(
#docs
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, types::#method_name::output::Output> {
#crate_path::runtime_api::Payload::new_static(
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::payload::Payload<types::#struct_name, types::#method_name::output::Output> {
#crate_path::runtime_api::payload::Payload::new_static(
#trait_name_str,
#method_name_str,
types::#struct_name { #( #param_names, )* },
+9
View File
@@ -224,7 +224,16 @@ impl CodegenBuilder {
}
/// Set the path to the `subxt` crate. By default, we expect it to be at `::subxt::ext::subxt_core`.
///
/// # Panics
///
/// Panics if the path provided is not an absolute path.
pub fn set_subxt_crate_path(&mut self, crate_path: syn::Path) {
if absolute_path(crate_path.clone()).is_err() {
// Throw an error here, because otherwise we end up with a harder to comprehend error when
// substitute types don't begin with an absolute path.
panic!("The provided crate path must be an absolute path, ie prefixed with '::' or 'crate'");
}
self.crate_path = crate_path;
}
+4
View File
@@ -67,11 +67,15 @@ sp-runtime = { workspace = true, optional = true }
tracing = { workspace = true, default-features = false }
[dev-dependencies]
assert_matches = { workspace = true }
bitvec = { workspace = true }
codec = { workspace = true, features = ["derive", "bit-vec"] }
subxt-macro = { workspace = true }
subxt-signer = { workspace = true, features = ["sr25519", "subxt"] }
sp-core = { workspace = true }
sp-keyring = { workspace = true }
sp-runtime = { workspace = true }
hex = { workspace = true }
[package.metadata.docs.rs]
@@ -0,0 +1,169 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::config::signed_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::config::SignedExtension;
use crate::dynamic::Value;
use crate::{config::Config, error::Error, Metadata};
use scale_decode::DecodeAsType;
/// The signed extensions of an extrinsic.
#[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtensions<'a, T: Config> {
bytes: &'a [u8],
metadata: &'a Metadata,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
pub(crate) fn new(bytes: &'a [u8], metadata: &'a Metadata) -> Self {
Self {
bytes,
metadata,
_marker: core::marker::PhantomData,
}
}
/// Returns an iterator over each of the signed extension details of the extrinsic.
/// If the decoding of any signed extension fails, an error item is yielded and the iterator stops.
pub fn iter(&self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<T>, Error>> {
let signed_extension_types = self.metadata.extrinsic().signed_extensions();
let num_signed_extensions = signed_extension_types.len();
let bytes = self.bytes;
let mut index = 0;
let mut byte_start_idx = 0;
let metadata = &self.metadata;
core::iter::from_fn(move || {
if index == num_signed_extensions {
return None;
}
let extension = &signed_extension_types[index];
let ty_id = extension.extra_ty();
let cursor = &mut &bytes[byte_start_idx..];
if let Err(err) = scale_decode::visitor::decode_with_visitor(
cursor,
&ty_id,
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(|e| Error::Decode(e.into()))
{
index = num_signed_extensions; // (such that None is returned in next iteration)
return Some(Err(err));
}
let byte_end_idx = bytes.len() - cursor.len();
let bytes = &bytes[byte_start_idx..byte_end_idx];
byte_start_idx = byte_end_idx;
index += 1;
Some(Ok(ExtrinsicSignedExtension {
bytes,
ty_id,
identifier: extension.identifier(),
metadata,
_marker: core::marker::PhantomData,
}))
})
}
/// Searches through all signed extensions to find a specific one.
/// If the Signed Extension is not found `Ok(None)` is returned.
/// If the Signed Extension is found but decoding failed `Err(_)` is returned.
pub fn find<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
for ext in self.iter() {
// If we encounter an error while iterating, we won't get any more results
// back, so just return that error as we won't find the signed ext anyway.
let ext = ext?;
match ext.as_signed_extension::<S>() {
// We found a match; return it:
Ok(Some(e)) => return Ok(Some(e)),
// No error, but no match either; next!
Ok(None) => continue,
// Error? return it
Err(e) => return Err(e),
}
}
Ok(None)
}
/// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment
/// signed extension, depending on which is present.
///
/// Returns `None` if `tip` was not found or decoding failed.
pub fn tip(&self) -> Option<u128> {
// Note: the overhead of iterating multiple time should be negligible.
self.find::<ChargeTransactionPayment>()
.ok()
.flatten()
.map(|e| e.tip())
.or_else(|| {
self.find::<ChargeAssetTxPayment<T>>()
.ok()
.flatten()
.map(|e| e.tip())
})
}
/// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension.
///
/// Returns `None` if `nonce` was not found or decoding failed.
pub fn nonce(&self) -> Option<u64> {
self.find::<CheckNonce>().ok()?
}
}
/// A single signed extension
#[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtension<'a, T: Config> {
bytes: &'a [u8],
ty_id: u32,
identifier: &'a str,
metadata: &'a Metadata,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
/// The bytes representing this signed extension.
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
/// The name of the signed extension.
pub fn name(&self) -> &'a str {
self.identifier
}
/// The type id of the signed extension.
pub fn type_id(&self) -> u32 {
self.ty_id
}
/// Signed Extension as a [`scale_value::Value`]
pub fn value(&self) -> Result<Value<u32>, Error> {
let value = scale_value::scale::decode_as_type(
&mut &self.bytes[..],
&self.ty_id,
self.metadata.types(),
)?;
Ok(value)
}
/// Decodes the bytes of this Signed Extension into its associated `Decoded` type.
/// Returns `Ok(None)` if the data we have doesn't match the Signed Extension we're asking to
/// decode with.
pub fn as_signed_extension<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return Ok(None);
}
self.as_type::<S::Decoded>().map(Some)
}
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], &self.ty_id, self.metadata.types())?;
Ok(value)
}
}
+709
View File
@@ -0,0 +1,709 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::blocks::extrinsic_signed_extensions::ExtrinsicSignedExtensions;
use crate::utils::strip_compact_prefix;
use crate::{
config::Config,
error::{BlockError, Error, MetadataError},
Metadata,
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::Decode;
use scale_decode::DecodeAsType;
use subxt_metadata::PalletMetadata;
pub use crate::blocks::StaticExtrinsic;
/// The body of a block.
pub struct Extrinsics<T: Config> {
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
ids: ExtrinsicPartTypeIds,
_marker: core::marker::PhantomData<T>,
}
impl<T: Config> Extrinsics<T> {
/// Instantiate a new [`Extrinsics`] object, given a vector containing
/// each extrinsic hash (in the form of bytes) and some metadata that
/// we'll use to decode them.
pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, BlockError> {
let ids = ExtrinsicPartTypeIds::new(&metadata)?;
Ok(Self {
extrinsics,
metadata,
ids,
_marker: core::marker::PhantomData,
})
}
/// The number of extrinsics.
pub fn len(&self) -> usize {
self.extrinsics.len()
}
/// Are there no extrinsics in this block?
// Note: mainly here to satisfy clippy.
pub fn is_empty(&self) -> bool {
self.extrinsics.is_empty()
}
/// Returns an iterator over the extrinsics in the block body.
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
// use of it with our `FilterExtrinsic` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<ExtrinsicDetails<T>, Error>> + Send + Sync + 'static {
let extrinsics = self.extrinsics.clone();
let num_extrinsics = self.extrinsics.len();
let metadata = self.metadata.clone();
let ids = self.ids;
let mut index = 0;
core::iter::from_fn(move || {
if index == num_extrinsics {
None
} else {
match ExtrinsicDetails::decode_from(
index as u32,
&extrinsics[index],
metadata.clone(),
ids,
) {
Ok(extrinsic_details) => {
index += 1;
Some(Ok(extrinsic_details))
}
Err(e) => {
index = num_extrinsics;
Some(Err(e))
}
}
}
})
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `E` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(
&self,
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> + '_ {
self.iter().filter_map(|res| match res {
Err(err) => Some(Err(err)),
Ok(details) => match details.as_extrinsic::<E>() {
// Failed to decode extrinsic:
Err(err) => Some(Err(err)),
// Extrinsic for a different pallet / different call (skip):
Ok(None) => None,
Ok(Some(value)) => Some(Ok(FoundExtrinsic { details, value })),
},
})
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the first extrinsic found which decodes to the provided `E` type.
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
self.find::<E>().next().transpose()
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
self.find::<E>().last().transpose()
}
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
/// A single extrinsic in a block.
pub struct ExtrinsicDetails<T: Config> {
/// The index of the extrinsic in the block.
index: u32,
/// Extrinsic bytes.
bytes: Arc<[u8]>,
/// Some if the extrinsic payload is signed.
signed_details: Option<SignedExtrinsicDetails>,
/// The start index in the `bytes` from which the call is encoded.
call_start_idx: usize,
/// The pallet index.
pallet_index: u8,
/// The variant index.
variant_index: u8,
/// Subxt metadata to fetch the extrinsic metadata.
metadata: Metadata,
_marker: core::marker::PhantomData<T>,
}
/// Details only available in signed extrinsics.
pub struct SignedExtrinsicDetails {
/// start index of the range in `bytes` of `ExtrinsicDetails` that encodes the address.
address_start_idx: usize,
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. Equivalent to signature_start_idx.
address_end_idx: usize,
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. Equivalent to extra_start_idx.
signature_end_idx: usize,
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature.
extra_end_idx: usize,
}
impl<T> ExtrinsicDetails<T>
where
T: Config,
{
// Attempt to dynamically decode a single extrinsic from the given input.
#[doc(hidden)]
pub fn decode_from(
index: u32,
extrinsic_bytes: &[u8],
metadata: Metadata,
ids: ExtrinsicPartTypeIds,
) -> Result<ExtrinsicDetails<T>, Error> {
const SIGNATURE_MASK: u8 = 0b1000_0000;
const VERSION_MASK: u8 = 0b0111_1111;
const LATEST_EXTRINSIC_VERSION: u8 = 4;
// removing the compact encoded prefix:
let bytes: Arc<[u8]> = strip_compact_prefix(extrinsic_bytes)?.1.into();
// Extrinsic are encoded in memory in the following way:
// - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
// - signature: [unknown TBD with metadata].
// - extrinsic data
let first_byte: u8 = Decode::decode(&mut &bytes[..])?;
let version = first_byte & VERSION_MASK;
if version != LATEST_EXTRINSIC_VERSION {
return Err(BlockError::UnsupportedVersion(version).into());
}
let is_signed = first_byte & SIGNATURE_MASK != 0;
// Skip over the first byte which denotes the version and signing.
let cursor = &mut &bytes[1..];
let signed_details = is_signed
.then(|| -> Result<SignedExtrinsicDetails, Error> {
let address_start_idx = bytes.len() - cursor.len();
// Skip over the address, signature and extra fields.
scale_decode::visitor::decode_with_visitor(
cursor,
&ids.address,
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
let address_end_idx = bytes.len() - cursor.len();
scale_decode::visitor::decode_with_visitor(
cursor,
&ids.signature,
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
let signature_end_idx = bytes.len() - cursor.len();
scale_decode::visitor::decode_with_visitor(
cursor,
&ids.extra,
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
let extra_end_idx = bytes.len() - cursor.len();
Ok(SignedExtrinsicDetails {
address_start_idx,
address_end_idx,
signature_end_idx,
extra_end_idx,
})
})
.transpose()?;
let call_start_idx = bytes.len() - cursor.len();
// Decode the pallet index, then the call variant.
let cursor = &mut &bytes[call_start_idx..];
let pallet_index: u8 = Decode::decode(cursor)?;
let variant_index: u8 = Decode::decode(cursor)?;
Ok(ExtrinsicDetails {
index,
bytes,
signed_details,
call_start_idx,
pallet_index,
variant_index,
metadata,
_marker: core::marker::PhantomData,
})
}
/// Is the extrinsic signed?
pub fn is_signed(&self) -> bool {
self.signed_details.is_some()
}
/// The index of the extrinsic in the block.
pub fn index(&self) -> u32 {
self.index
}
/// Return _all_ of the bytes representing this extrinsic, which include, in order:
/// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
/// - SignatureType (if the payload is signed)
/// - Address
/// - Signature
/// - Extra fields
/// - Extrinsic call bytes
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Return only the bytes representing this extrinsic call:
/// - First byte is the pallet index
/// - Second byte is the variant (call) index
/// - Followed by field bytes.
///
/// # Note
///
/// Please use [`Self::bytes`] if you want to get all extrinsic bytes.
pub fn call_bytes(&self) -> &[u8] {
&self.bytes[self.call_start_idx..]
}
/// Return the bytes representing the fields stored in this extrinsic.
///
/// # Note
///
/// This is a subset of [`Self::call_bytes`] that does not include the
/// first two bytes that denote the pallet index and the variant index.
pub fn field_bytes(&self) -> &[u8] {
// Note: this cannot panic because we checked the extrinsic bytes
// to contain at least two bytes.
&self.call_bytes()[2..]
}
/// Return only the bytes of the address that signed this extrinsic.
///
/// # Note
///
/// Returns `None` if the extrinsic is not signed.
pub fn address_bytes(&self) -> Option<&[u8]> {
self.signed_details
.as_ref()
.map(|e| &self.bytes[e.address_start_idx..e.address_end_idx])
}
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
pub fn signature_bytes(&self) -> Option<&[u8]> {
self.signed_details
.as_ref()
.map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx])
}
/// Returns the signed extension `extra` bytes of the extrinsic.
/// Each signed extension has an `extra` type (May be zero-sized).
/// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions.
/// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed.
///
/// Note: Returns `None` if the extrinsic is not signed.
pub fn signed_extensions_bytes(&self) -> Option<&[u8]> {
self.signed_details
.as_ref()
.map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx])
}
/// Returns `None` if the extrinsic is not signed.
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
let signed = self.signed_details.as_ref()?;
let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx];
Some(ExtrinsicSignedExtensions::new(extra_bytes, &self.metadata))
}
/// The index of the pallet that the extrinsic originated from.
pub fn pallet_index(&self) -> u8 {
self.pallet_index
}
/// The index of the extrinsic variant that the extrinsic originated from.
pub fn variant_index(&self) -> u8 {
self.variant_index
}
/// The name of the pallet from whence the extrinsic originated.
pub fn pallet_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.pallet.name())
}
/// The name of the call (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> Result<&str, Error> {
Ok(&self.extrinsic_metadata()?.variant.name)
}
/// Fetch the metadata for this extrinsic.
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails, Error> {
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
let variant = pallet
.call_variant_by_index(self.variant_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
Ok(ExtrinsicMetadataDetails { pallet, variant })
}
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the extrinsic.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
let bytes = &mut self.field_bytes();
let extrinsic_metadata = self.extrinsic_metadata()?;
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
Ok(decoded)
}
/// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
let extrinsic_metadata = self.extrinsic_metadata()?;
if extrinsic_metadata.pallet.name() == E::PALLET
&& extrinsic_metadata.variant.name == E::CALL
{
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
/// type for this is exposed via static codegen as a root level `Call` type.
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
let decoded = E::decode_as_type(
&mut &self.call_bytes()[..],
&self.metadata.outer_enums().call_enum_ty(),
self.metadata.types(),
)?;
Ok(decoded)
}
}
/// A Static Extrinsic found in a block coupled with it's details.
pub struct FoundExtrinsic<T: Config, E> {
/// Details for the extrinsic.
pub details: ExtrinsicDetails<T>,
/// The decoded extrinsic value.
pub value: E,
}
/// Details for the given extrinsic plucked from the metadata.
pub struct ExtrinsicMetadataDetails<'a> {
/// Metadata for the pallet that the extrinsic belongs to.
pub pallet: PalletMetadata<'a>,
/// Metadata for the variant which describes the pallet extrinsics.
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the substrate-based chain.
#[doc(hidden)]
#[derive(Debug, Copy, Clone)]
pub struct ExtrinsicPartTypeIds {
/// The address (source) of the extrinsic.
address: u32,
/// The extrinsic call type.
// Note: the call type can be used to skip over the extrinsic bytes to check
// they are in line with our metadata. This operation is currently postponed.
_call: u32,
/// The signature of the extrinsic.
signature: u32,
/// The extra parameters of the extrinsic.
extra: u32,
}
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
fn new(metadata: &Metadata) -> Result<Self, BlockError> {
Ok(ExtrinsicPartTypeIds {
address: metadata.extrinsic().address_ty(),
_call: metadata.extrinsic().call_ty(),
signature: metadata.extrinsic().signature_ty(),
extra: metadata.extrinsic().extra_ty(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::SubstrateConfig;
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_metadata::v15::{CustomMetadata, OuterEnums};
use frame_metadata::{
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
RuntimeMetadataPrefixed,
};
use scale_info::{meta_type, TypeInfo};
use scale_value::Value;
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Address, Call, Signature, Extra> {
pub signature: Option<(Address, Signature, Extra)>,
pub function: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum RuntimeCall {
Test(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum Pallet {
#[allow(unused)]
#[codec(index = 2)]
TestCall {
value: u128,
signed: bool,
name: String,
},
}
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
struct TestCallExtrinsic {
value: u128,
signed: bool,
name: String,
}
impl StaticExtrinsic for TestCallExtrinsic {
const PALLET: &'static str = "Test";
const CALL: &'static str = "TestCall";
}
/// Build fake metadata consisting the types needed to represent an extrinsic.
fn metadata() -> Metadata {
let pallets = vec![PalletMetadata {
name: "Test",
storage: None,
calls: Some(PalletCallMetadata {
ty: meta_type::<Pallet>(),
}),
event: None,
constants: vec![],
error: None,
index: 0,
docs: vec![],
}];
let extrinsic = ExtrinsicMetadata {
version: 4,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let meta = RuntimeMetadataV15::new(
pallets,
extrinsic,
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<RuntimeCall>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::from(metadata)
}
#[test]
fn extrinsic_metadata_consistency() {
let metadata = metadata();
// Except our metadata to contain the registered types.
let pallet = metadata.pallet_by_index(0).expect("pallet exists");
let extrinsic = pallet
.call_variant_by_index(2)
.expect("metadata contains the RuntimeCall enum with this pallet");
assert_eq!(pallet.name(), "Test");
assert_eq!(&extrinsic.name, "TestCall");
}
#[test]
fn insufficient_extrinsic_bytes() {
let metadata = metadata();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
// Decode with empty bytes.
let result = ExtrinsicDetails::<SubstrateConfig>::decode_from(0, &[], metadata, ids);
assert_matches!(result.err(), Some(crate::Error::Codec(_)));
}
#[test]
fn unsupported_version_extrinsic() {
let metadata = metadata();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
// Decode with invalid version.
let result =
ExtrinsicDetails::<SubstrateConfig>::decode_from(0, &vec![3u8].encode(), metadata, ids);
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::UnsupportedVersion(3)
))
);
}
#[test]
fn statically_decode_extrinsic() {
let metadata = metadata();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
let tx = crate::dynamic::tx(
"Test",
"TestCall",
vec![
Value::u128(10),
Value::bool(true),
Value::string("SomeValue"),
],
);
let tx_encoded = crate::tx::create_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");
// Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
// The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
let extrinsic = ExtrinsicDetails::<SubstrateConfig>::decode_from(
1,
tx_encoded.encoded(),
metadata,
ids,
)
.expect("Valid extrinsic");
assert!(!extrinsic.is_signed());
assert_eq!(extrinsic.index(), 1);
assert_eq!(extrinsic.pallet_index(), 0);
assert_eq!(
extrinsic
.pallet_name()
.expect("Valid metadata contains pallet name"),
"Test"
);
assert_eq!(extrinsic.variant_index(), 2);
assert_eq!(
extrinsic
.variant_name()
.expect("Valid metadata contains variant name"),
"TestCall"
);
// Decode the extrinsic to the root enum.
let decoded_extrinsic = extrinsic
.as_root_extrinsic::<RuntimeCall>()
.expect("can decode extrinsic to root enum");
assert_eq!(
decoded_extrinsic,
RuntimeCall::Test(Pallet::TestCall {
value: 10,
signed: true,
name: "SomeValue".into(),
})
);
// Decode the extrinsic to the extrinsic variant.
let decoded_extrinsic = extrinsic
.as_extrinsic::<TestCallExtrinsic>()
.expect("can decode extrinsic to extrinsic variant")
.expect("value cannot be None");
assert_eq!(
decoded_extrinsic,
TestCallExtrinsic {
value: 10,
signed: true,
name: "SomeValue".into(),
}
);
}
}
+90 -16
View File
@@ -1,19 +1,93 @@
use scale_decode::DecodeAsFields;
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
/// Generated API structures that represent an extrinsic implement this trait.
///
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
/// form of the `Extrinsic` from the metadata.
pub trait StaticExtrinsic: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Call name.
const CALL: &'static str;
//! Decode and iterate over the extrinsics in block bodies.
//!
//! Use the [`decode_from`] function as an entry point to decoding extrinsics, and then
//! have a look at [`Extrinsics`] and [`ExtrinsicDetails`] to see which methods are available
//! to work with the extrinsics.
//!
//! # Example
//!
//! ```rust
//! extern crate alloc;
//!
//! use subxt_macro::subxt;
//! use subxt_core::blocks;
//! use subxt_core::metadata;
//! use subxt_core::config::PolkadotConfig;
//! use alloc::vec;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'd like to use to help us decode extrinsics:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some extrinsics we'd like to decode:
//! let ext_bytes = vec![
//! hex::decode("280402000bf18367a38e01").unwrap(),
//! hex::decode("c10184008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801f4de97941fcc3f95c761cd58d480bb41ce64836850f51b6fcc7542e809eb0a346fe95eb1b72de542273d4f1b00b636eb025e2b0e98cc498a095e7ce48f3d4f82b501040000001848656c6c6f21").unwrap(),
//! hex::decode("5102840090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2201ac0c06f55cf3461067bbe48da16efbb50dfad555e2821ce20d37b2e42d6dcb439acd40f742b12ef00f8889944060b04373dc4d34a1992042fd269e8ec1e64a848502000004000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2217000010632d5ec76b05").unwrap()
//! ];
//!
//! // Given some chain config and metadata, we know how to decode the bytes.
//! let exts = blocks::decode_from::<PolkadotConfig>(ext_bytes, metadata).unwrap();
//!
//! // We'll see 3 extrinsics:
//! assert_eq!(exts.len(), 3);
//!
//! // We can iterate over them and decode various details out of them.
//! for ext in exts.iter() {
//! let ext = ext.unwrap();
//! println!("Pallet: {}", ext.pallet_name().unwrap());
//! println!("Call: {}", ext.variant_name().unwrap());
//! }
//!
//! # let ext_details: Vec<_> = exts.iter()
//! # .map(|ext| {
//! # let ext = ext.unwrap();
//! # let pallet = ext.pallet_name().unwrap().to_string();
//! # let call = ext.variant_name().unwrap().to_string();
//! # (pallet, call)
//! # })
//! # .collect();
//! #
//! # assert_eq!(ext_details, vec![
//! # ("Timestamp".to_owned(), "set".to_owned()),
//! # ("System".to_owned(), "remark".to_owned()),
//! # ("Balances".to_owned(), "transfer_allow_death".to_owned()),
//! # ]);
//! ```
/// Returns true if the given pallet and call names match this extrinsic.
fn is_extrinsic(pallet: &str, call: &str) -> bool {
Self::PALLET == pallet && Self::CALL == call
}
mod extrinsic_signed_extensions;
mod extrinsics;
mod static_extrinsic;
use crate::config::Config;
use crate::error::BlockError;
use crate::Metadata;
use alloc::vec::Vec;
pub use extrinsic_signed_extensions::{ExtrinsicSignedExtension, ExtrinsicSignedExtensions};
pub use extrinsics::{
ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic, SignedExtrinsicDetails,
};
pub use static_extrinsic::StaticExtrinsic;
/// Instantiate a new [`Extrinsics`] object, given a vector containing each extrinsic hash (in the
/// form of bytes) and some metadata that we'll use to decode them.
///
/// This is a shortcut for [`Extrinsics::decode_from`].
pub fn decode_from<T: Config>(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Extrinsics<T>, BlockError> {
Extrinsics::decode_from(extrinsics, metadata)
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use scale_decode::DecodeAsFields;
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
/// Generated API structures that represent an extrinsic implement this trait.
///
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
/// form of the `Extrinsic` from the metadata.
pub trait StaticExtrinsic: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Call name.
const CALL: &'static str;
/// Returns true if the given pallet and call names match this extrinsic.
fn is_extrinsic(pallet: &str, call: &str) -> bool {
Self::PALLET == pallet && Self::CALL == call
}
}
+16 -48
View File
@@ -1,61 +1,31 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A couple of client types that we use elsewhere.
use crate::{config::Config, metadata::Metadata};
use derive_where::derive_where;
/// Each client should be able to provide access to the following fields
/// - runtime version
/// - genesis hash
/// - metadata
/// This provides access to some relevant client state in signed extensions,
/// and is just a combination of some of the available properties.
#[derive_where(Clone, Debug)]
pub struct ClientState<C: Config> {
genesis_hash: C::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<C: Config> ClientState<C> {
pub fn new(genesis_hash: C::Hash, runtime_version: RuntimeVersion, metadata: Metadata) -> Self {
Self {
genesis_hash,
runtime_version,
metadata,
}
}
pub fn metadata(&self) -> Metadata {
self.metadata.clone()
}
pub fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version
}
pub fn genesis_hash(&self) -> C::Hash {
self.genesis_hash
}
/// Genesis hash.
pub genesis_hash: C::Hash,
/// Runtime version.
pub runtime_version: RuntimeVersion,
/// Metadata.
pub metadata: Metadata,
}
/// Runtime version information needed to submit transactions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeVersion {
spec_version: u32,
transaction_version: u32,
}
impl RuntimeVersion {
pub fn new(spec_version: u32, transaction_version: u32) -> Self {
RuntimeVersion {
spec_version,
transaction_version,
}
}
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub fn spec_version(&self) -> u32 {
self.spec_version
}
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change. If this
/// number changes, then `spec_version` must change, also.
///
@@ -65,7 +35,5 @@ impl RuntimeVersion {
/// dispatchable/module changing its index.
///
/// It need *not* change when a new module is added or when a dispatchable is added.
pub fn transaction_version(&self) -> u32 {
self.transaction_version
}
pub transaction_version: u32,
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+7 -9
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -47,7 +47,7 @@ impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckSpecVersion(client.runtime_version().spec_version()))
Ok(CheckSpecVersion(client.runtime_version.spec_version))
}
}
@@ -109,9 +109,7 @@ impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckTxVersion(
client.runtime_version().transaction_version(),
))
Ok(CheckTxVersion(client.runtime_version.transaction_version))
}
}
@@ -135,7 +133,7 @@ impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckGenesis(client.genesis_hash()))
Ok(CheckGenesis(client.genesis_hash))
}
}
@@ -209,12 +207,12 @@ impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
let check_mortality = if let Some(params) = params.0 {
CheckMortality {
era: params.era,
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash()),
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash),
}
} else {
CheckMortality {
era: Era::Immortal,
checkpoint: client.genesis_hash(),
checkpoint: client.genesis_hash,
}
};
Ok(check_mortality)
@@ -402,7 +400,7 @@ macro_rules! impl_tuples {
client: &ClientState<T>,
params: Self::Params,
) -> Result<Self, ExtrinsicParamsError> {
let metadata = client.metadata();
let metadata = &client.metadata;
let types = metadata.types();
// For each signed extension in the tuple, find the matching index in the metadata, if
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -1,7 +1,9 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Construct addresses to access constants with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
@@ -10,7 +12,7 @@ use derive_where::derive_where;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
pub trait AddressT {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
@@ -78,7 +80,7 @@ impl<ReturnTy> Address<ReturnTy> {
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
impl<ReturnTy: DecodeWithMetadata> AddressT for Address<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
+50 -21
View File
@@ -1,29 +1,56 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing constants.
//! Access constants from metadata.
//!
//! Use [`get`] to retrieve a constant from some metadata, or [`validate`] to check that a static
//! constant address lines up with the value seen in the metadata.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::constants;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'd like to access constants in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // We can use a static address to obtain some constant:
//! let address = polkadot::constants().balances().existential_deposit();
//!
//! // This validates that the address given is in line with the metadata
//! // we're trying to access the constant in:
//! constants::validate(&address, &metadata).expect("is valid");
//!
//! // This acquires the constant (and internally also validates it):
//! let ed = constants::get(&address, &metadata).expect("can decode constant");
//!
//! assert_eq!(ed, 33_333_333);
//! ```
mod constant_address;
pub use constant_address::{dynamic, Address, ConstantAddress, DynamicAddress};
pub mod address;
use address::AddressT;
use alloc::borrow::ToOwned;
use crate::{
error::MetadataError,
metadata::{DecodeWithMetadata, MetadataExt},
Error, Metadata,
};
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
/// Run validation logic against some constant address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or constant in question do not exist at all).
pub fn validate_constant<Address: ConstantAddress>(
metadata: &subxt_metadata::Metadata,
address: &Address,
) -> Result<(), Error> {
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the constant value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Address: AddressT>(address: &Address, metadata: &Metadata) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name_err(address.pallet_name())?
@@ -38,12 +65,14 @@ pub fn validate_constant<Address: ConstantAddress>(
Ok(())
}
pub fn get_constant<Address: ConstantAddress>(
metadata: &Metadata,
/// Fetch a constant out of the metadata given a constant address. If the `address` has been
/// statically generated, this will validate that the constant shape is as expected, too.
pub fn get<Address: AddressT>(
address: &Address,
metadata: &Metadata,
) -> Result<Address::Target, Error> {
// 1. Validate constant shape if hash given:
validate_constant(metadata, address)?;
validate(address, metadata)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
@@ -1,14 +1,20 @@
use derive_where::derive_where;
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Construct addresses to access custom values with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use crate::utils::Yes;
use derive_where::derive_where;
/// This represents the address of a custom value in the metadata.
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch
/// custom values from the metadata.
/// The trait is implemented by [str] for dynamic loopup and [StaticAddress] for static queries.
pub trait CustomValueAddress {
/// Use this with [`AddressT::IsDecodable`].
pub use crate::utils::Yes;
/// This represents the address of a custom value in in the metadata.
/// Anything that implements it can be used to fetch custom values from the metadata.
/// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries.
pub trait AddressT {
/// The type of the custom value.
type Target: DecodeWithMetadata;
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
@@ -24,7 +30,7 @@ pub trait CustomValueAddress {
}
}
impl CustomValueAddress for str {
impl AddressT for str {
type Target = DecodedValueThunk;
type IsDecodable = Yes;
@@ -62,7 +68,7 @@ impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
}
}
impl<R: DecodeWithMetadata, Y> CustomValueAddress for StaticAddress<R, Y> {
impl<R: DecodeWithMetadata, Y> AddressT for StaticAddress<R, Y> {
type Target = R;
type IsDecodable = Y;
+45 -23
View File
@@ -1,27 +1,48 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing custom types
//! Access custom values from metadata.
//!
//! Use [`get`] to retrieve a custom value from some metadata, or [`validate`] to check that a
//! static custom value address lines up with the value seen in the metadata.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::custom_values;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'd like to access custom values in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // At the moment, we don't expect to see any custom values in the metadata
//! // for Polkadot, so this will return an error:
//! let err = custom_values::get("Foo", &metadata);
//! ```
mod custom_value_address;
pub mod address;
use crate::utils::Yes;
pub use custom_value_address::{CustomValueAddress, StaticAddress};
use crate::{
error::MetadataError,
metadata::{DecodeWithMetadata, MetadataExt},
Error, Metadata,
};
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
use address::AddressT;
use alloc::vec::Vec;
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate_custom_value<Address: CustomValueAddress + ?Sized>(
metadata: &Metadata,
pub fn validate<Address: AddressT + ?Sized>(
address: &Address,
metadata: &Metadata,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let custom = metadata.custom();
@@ -41,12 +62,12 @@ pub fn validate_custom_value<Address: CustomValueAddress + ?Sized>(
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn get_custom_value<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
metadata: &Metadata,
pub fn get<Address: AddressT<IsDecodable = Yes> + ?Sized>(
address: &Address,
metadata: &Metadata,
) -> Result<Address::Target, Error> {
// 1. Validate custom value shape if hash given:
validate_custom_value(metadata, address)?;
validate(address, metadata)?;
// 2. Attempt to decode custom value:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
@@ -59,12 +80,12 @@ pub fn get_custom_value<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn get_custom_value_bytes<Address: CustomValueAddress + ?Sized>(
metadata: &Metadata,
pub fn get_bytes<Address: AddressT + ?Sized>(
address: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
// 1. Validate custom value shape if hash given:
validate_custom_value(metadata, address)?;
validate(address, metadata)?;
// 2. Return the underlying bytes:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
@@ -73,6 +94,8 @@ pub fn get_custom_value_bytes<Address: CustomValueAddress + ?Sized>(
#[cfg(test)]
mod tests {
use super::*;
use alloc::collections::BTreeMap;
use codec::Encode;
use scale_decode::DecodeAsType;
@@ -83,8 +106,7 @@ mod tests {
use alloc::string::String;
use alloc::vec;
use crate::custom_values::get_custom_value;
use crate::Metadata;
use crate::custom_values;
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
pub struct Person {
@@ -135,15 +157,15 @@ mod tests {
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::new(metadata)
Metadata::from(metadata)
}
#[test]
fn test_decoding() {
let metadata = mock_metadata();
assert!(get_custom_value(&metadata, "Invalid Address").is_err());
let person_decoded_value_thunk = get_custom_value(&metadata, "Mr. Robot").unwrap();
assert!(custom_values::get("Invalid Address", &metadata).is_err());
let person_decoded_value_thunk = custom_values::get("Mr. Robot", &metadata).unwrap();
let person: Person = person_decoded_value_thunk.as_type().unwrap();
assert_eq!(
person,
+5 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -17,16 +17,16 @@ pub use scale_value::{At, Value};
pub type DecodedValue = scale_value::Value<u32>;
// Submit dynamic transactions.
pub use crate::tx::dynamic as tx;
pub use crate::tx::payload::dynamic as tx;
// Lookup constants dynamically.
pub use crate::constants::dynamic as constant;
pub use crate::constants::address::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::dynamic as storage;
pub use crate::storage::address::dynamic as storage;
// Execute runtime API function call dynamically.
pub use crate::runtime_api::dynamic as runtime_api_call;
pub use crate::runtime_api::payload::dynamic as runtime_api_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
+33 -1
View File
@@ -1,15 +1,24 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! The errors that can be emitted in this crate.
use alloc::boxed::Box;
use alloc::string::String;
use derive_more::{Display, From};
use subxt_metadata::StorageHasher;
/// The error emitted when something goes wrong.
#[derive(Debug, Display, From)]
pub enum Error {
/// Codec error.
#[display(fmt = "Scale codec error: {_0}")]
Codec(codec::Error),
/// Metadata error.
#[display(fmt = "Metadata Error: {_0}")]
Metadata(MetadataError),
/// Storage address error.
#[display(fmt = "Storage Error: {_0}")]
StorageAddress(StorageAddressError),
/// Error decoding to a [`crate::dynamic::Value`].
@@ -21,16 +30,39 @@ pub enum Error {
/// Error constructing the appropriate extrinsic params.
#[display(fmt = "Extrinsic params error: {_0}")]
ExtrinsicParams(ExtrinsicParamsError),
/// Block body error.
#[display(fmt = "Error working with block body: {_0}")]
Block(BlockError),
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl From<scale_decode::visitor::DecodeError> for Error {
fn from(value: scale_decode::visitor::DecodeError) -> Self {
Error::Decode(value.into())
}
}
/// Block error
#[derive(Clone, Debug, Display, Eq, PartialEq)]
pub enum BlockError {
/// Extrinsic type ID cannot be resolved with the provided metadata.
#[display(
fmt = "Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata"
)]
MissingType,
/// Unsupported signature.
#[display(fmt = "Unsupported extrinsic version, only version 4 is supported currently")]
/// The extrinsic has an unsupported version.
UnsupportedVersion(u8),
/// Decoding error.
#[display(fmt = "Cannot decode extrinsic: {_0}")]
DecodingError(codec::Error),
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl std::error::Error for BlockError {}
/// Something went wrong trying to access details in the metadata.
#[derive(Clone, Debug, PartialEq, Display)]
+62 -16
View File
@@ -1,3 +1,43 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Decode and work with events.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::config::PolkadotConfig;
//! use subxt_core::events;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../artifacts/polkadot_metadata_full.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some bytes representing events (located in System.Events storage):
//! let event_bytes = hex::decode("1c00000000000000a2e9b53d5517020000000100000000000310c96d901d0102000000020000000408d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a030000000000000000000000000000020000000402d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48102700000000000000000000000000000000020000000407be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25fbeea5a030000000000000000000000000000020000002100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a03000000000000000000000000000000000000000000000000000000000000020000000000426df03e00000000").unwrap();
//!
//! // We can decode these bytes like so:
//! let evs = events::decode_from::<PolkadotConfig>(event_bytes, metadata);
//!
//! // And then do things like iterate over them and inspect details:
//! for ev in evs.iter() {
//! let ev = ev.unwrap();
//! println!("Index: {}", ev.index());
//! println!("Name: {}.{}", ev.pallet_name(), ev.variant_name());
//! println!("Fields: {:?}", ev.field_values().unwrap());
//! }
//! ```
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
@@ -7,6 +47,13 @@ use subxt_metadata::PalletMetadata;
use crate::{error::MetadataError, Config, Error, Metadata};
/// Create a new [`Events`] instance from the given bytes.
///
/// This is a shortcut for [`Events::decode_from`].
pub fn decode_from<T: Config>(event_bytes: Vec<u8>, metadata: Metadata) -> Events<T> {
Events::decode_from(event_bytes, metadata)
}
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
@@ -52,7 +99,7 @@ impl<T: Config> core::fmt::Debug for Events<T> {
impl<T: Config> Events<T> {
/// Create a new [`Events`] instance from the given bytes.
pub fn decode_from(metadata: Metadata, event_bytes: Vec<u8>) -> Self {
pub fn decode_from(event_bytes: Vec<u8>, metadata: Metadata) -> Self {
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
@@ -85,6 +132,11 @@ impl<T: Config> Events<T> {
self.num_events == 0
}
/// Return the bytes representing all of the events.
pub fn bytes(&self) -> &[u8] {
&self.event_bytes
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
@@ -276,12 +328,12 @@ impl<T: Config> EventDetails<T> {
/// The name of the pallet from whence the Event originated.
pub fn pallet_name(&self) -> &str {
self.event_metadata().pallet().name()
self.event_metadata().pallet.name()
}
/// The name of the event (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> &str {
&self.event_metadata().variant().name
&self.event_metadata().variant.name
}
/// Fetch details from the metadata for this event.
@@ -370,17 +422,10 @@ impl<T: Config> EventDetails<T> {
/// Details for the given event plucked from the metadata.
pub struct EventMetadataDetails<'a> {
pallet: PalletMetadata<'a>,
variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
impl<'a> EventMetadataDetails<'a> {
pub fn pallet(&self) -> PalletMetadata<'a> {
self.pallet
}
pub fn variant(&self) -> &'a scale_info::Variant<scale_info::form::PortableForm> {
self.variant
}
/// Metadata for the pallet that the event belongs to.
pub pallet: PalletMetadata<'a>,
/// Metadata for the variant which describes the pallet events.
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// Event related test utilities used outside this module.
@@ -504,8 +549,9 @@ pub(crate) mod test_utils {
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::new(runtime_metadata.try_into().unwrap())
Metadata::from(metadata)
}
/// Build an `Events` object for test purposes, based on the details provided,
@@ -532,7 +578,7 @@ pub(crate) mod test_utils {
// Prepend compact encoded length to event bytes:
let mut all_event_bytes = Compact(num_events).encode();
all_event_bytes.extend(event_bytes);
Events::decode_from(metadata, all_event_bytes)
Events::decode_from(all_event_bytes, metadata)
}
}
+21 -5
View File
@@ -2,13 +2,30 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Subxt-core
//! # subxt-core
//!
//! A `#[no_std]` compatible subset of the functionality provided in the `subxt` crate. This
//! contains the core logic for encoding and decoding things, but nothing related to networking.
//!
//! Here's an overview of the main things exposed here:
//!
//! - [`blocks`]: decode and explore block bodies.
//! - [`constants`]: access and validate the constant addresses in some metadata.
//! - [`custom_values`]: access and validate the custom value addresses in some metadata.
//! - [`metadata`]: decode bytes into the metadata used throughout this library.
//! - [`storage`]: construct storage request payloads and decode the results you'd get back.
//! - [`tx`]: construct and sign transactions (extrinsics).
//! - [`runtime_api`]: construct runtime API request payloads and decode the results you'd get back.
//! - [`events`]: decode and explore events.
//!
//! `#[no_std]` compatible core crate for subxt.
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub extern crate alloc;
#[macro_use]
mod macros;
pub mod blocks;
pub mod client;
pub mod config;
@@ -27,13 +44,12 @@ pub use config::Config;
pub use error::Error;
pub use metadata::Metadata;
#[macro_use]
mod macros;
/// Re-exports of some of the key external crates.
pub mod ext {
pub use codec;
pub use scale_decode;
pub use scale_encode;
pub use scale_value;
cfg_substrate_compat! {
pub use sp_runtime;
+4
View File
@@ -1,3 +1,7 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
macro_rules! cfg_feature {
($feature:literal, $($item:item)*) => {
$(
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+15 -71
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -21,12 +21,6 @@ impl core::ops::Deref for Metadata {
}
impl Metadata {
pub fn new(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
}
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
&self,
@@ -53,11 +47,23 @@ impl Metadata {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
/// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found.
pub fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError> {
self.custom()
.get(name)
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
}
}
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata::new(md)
Metadata {
inner: Arc::new(md),
}
}
}
@@ -70,68 +76,6 @@ impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::new)
}
}
/// Some extension methods on [`subxt_metadata::Metadata`] that return Errors instead of Options.
pub trait MetadataExt {
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError>;
fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError>;
}
impl MetadataExt for subxt_metadata::Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_name(name)
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_index(index)
.ok_or(MetadataError::PalletIndexNotFound(index))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError> {
self.custom()
.get(name)
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
subxt_metadata::Metadata::decode(input).map(Metadata::from)
}
}
+26 -6
View File
@@ -1,14 +1,34 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing the metadata obtained from a node.
//! A [`Metadata`] type, which is used through this crate.
//!
//! This can be decoded from the bytes handed back from a node when asking for metadata.
//!
//! # Examples
//!
//! ```rust
//! use subxt_core::metadata;
//!
//! // We need to fetch the bytes from somewhere, and then we can decode them:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! ```
mod decode_encode_traits;
mod metadata_type;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::{Metadata, MetadataExt};
use codec::Decode;
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
/// Attempt to decode some bytes into [`Metadata`], returning an error
/// if decoding fails.
///
/// This is a shortcut for importing [`codec::Decode`] and using the
/// implementation of that on [`Metadata`].
pub fn decode_from(bytes: &[u8]) -> Result<Metadata, codec::Error> {
Metadata::decode(&mut &*bytes)
}
+86 -166
View File
@@ -1,185 +1,105 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::borrow::Cow;
//! Encode runtime API payloads, decode the associated values returned from them, and validate
//! static runtime API payloads.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::runtime_api;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let payload = polkadot::apis().metadata().metadata_versions();
//!
//! // We can validate that the payload is compatible with the given metadata.
//! runtime_api::validate(&payload, &metadata).unwrap();
//!
//! // Encode the payload name and arguments to hand to a node:
//! let _call_name = runtime_api::call_name(&payload);
//! let _call_args = runtime_api::call_args(&payload, &metadata).unwrap();
//!
//! // If we were to obtain a value back from the node, we could
//! // then decode it using the same payload and metadata like so:
//! let value_bytes = hex::decode("080e0000000f000000").unwrap();
//! let value = runtime_api::decode_value(&mut &*value_bytes, &payload, &metadata).unwrap();
//!
//! println!("Available metadata versions: {value:?}");
//! ```
pub mod payload;
use crate::error::{Error, MetadataError};
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use payload::PayloadT;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::Error;
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the runtime API in question do not exist at all)
pub fn validate<Payload: PayloadT>(payload: &Payload, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
return Ok(());
};
use crate::metadata::{DecodeWithMetadata, Metadata};
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait RuntimeApiPayload {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if static_hash != runtime_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct Payload<ArgsData, ReturnTy> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
/// Return the name of the runtime API call from the payload.
pub fn call_name<Payload: PayloadT>(payload: &Payload) -> String {
format!("{}_{}", payload.trait_name(), payload.method_name())
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(&input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
/// Return the encoded call args given a runtime API payload.
pub fn call_args<Payload: PayloadT>(
payload: &Payload,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
payload.encode_args(metadata)
}
/// A dynamic runtime API payload.
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
/// Decode the value bytes at the location given by the provided runtime API payload.
pub fn decode_value<Payload: PayloadT>(
bytes: &mut &[u8],
payload: &Payload,
metadata: &Metadata,
) -> Result<Payload::ReturnType, Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(payload.trait_name())?
.method_by_name(payload.method_name())
.ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
) -> Self {
Payload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
}
}
let val = <Payload::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
api_method.output_ty(),
metadata,
)?;
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload {
Payload::new(trait_name, method_name, args_data.into())
Ok(val)
}
+188
View File
@@ -0,0 +1,188 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains the trait and types used to represent
//! runtime API calls that can be made.
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::Error;
use crate::metadata::{DecodeWithMetadata, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait PayloadT {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`PayloadT::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct Payload<ArgsData, ReturnTy> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> PayloadT
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(&input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A dynamic runtime API payload.
pub type DynamicPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
) -> Self {
Payload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicPayload`].
pub fn dynamic(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(trait_name, method_name, args_data.into())
}
@@ -1,7 +1,9 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Construct addresses to access storage entries with.
use crate::{
dynamic::DecodedValueThunk,
error::{Error, MetadataError},
@@ -14,11 +16,12 @@ use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use super::{storage_key::StorageHashers, StorageKey};
// Re-export types used here:
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageHashersIter, StorageKey};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
pub trait AddressT {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The keys type used to construct this address.
@@ -120,11 +123,11 @@ where
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
super::get_address_root_bytes(self)
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> AddressT
for Address<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
+133 -13
View File
@@ -1,22 +1,142 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing and working with storage items.
//! Encode storage keys, decode storage values, and validate static storage addresses.
//!
//! # Example
//!
//! ```rust
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::storage;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let account = dev::alice().public_key().into();
//! let address = polkadot::storage().system().account(&account);
//!
//! // We can validate that the address is compatible with the given metadata.
//! storage::validate(&address, &metadata).unwrap();
//!
//! // Encode the address to bytes. These can be sent to a node to query the value.
//! storage::get_address_bytes(&address, &metadata).unwrap();
//!
//! // If we were to obtain a value back from the node at that address, we could
//! // then decode it using the same address and metadata like so:
//! let value_bytes = hex::decode("00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080").unwrap();
//! let value = storage::decode_value(&mut &*value_bytes, &address, &metadata).unwrap();
//!
//! println!("Alice's account info: {value:?}");
//! ```
mod storage_address;
mod storage_key;
pub mod utils;
mod utils;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageKey};
pub mod address;
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
use address::AddressT;
use alloc::vec::Vec;
// This isn't a part of the public API, but expose here because it's useful in Subxt.
#[doc(hidden)]
pub use utils::lookup_storage_entry_details;
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the storage value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Address: AddressT>(address: &Address, metadata: &Metadata) -> Result<(), Error> {
let Some(hash) = address.validation_hash() else {
return Ok(());
};
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
pub use storage_key::StorageKey;
/// Given a storage address and some metadata, this encodes the address into bytes which can be
/// handed to a node to retrieve the corresponding value.
pub fn get_address_bytes<Address: AddressT>(
address: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
address.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
/// Given a storage address and some metadata, this encodes the root of the address (ie the pallet
/// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns
/// the bytes needed to iterate over all of the entries within it.
pub fn get_address_root_bytes<Address: AddressT>(address: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
bytes
}
/// Given some storage value that we've retrieved from a node, the address used to retrieve it, and
/// metadata from the node, this function attempts to decode the bytes into the target value specified
/// by the address.
pub fn decode_value<Address: AddressT>(
bytes: &mut &[u8],
address: &Address,
metadata: &Metadata,
) -> Result<Address::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let val = Address::Target::decode_with_metadata(bytes, value_ty_id, metadata)?;
Ok(val)
}
/// Return the default value at a given storage address if one is available, or an error otherwise.
pub fn default_value<Address: AddressT>(
address: &Address,
metadata: &Metadata,
) -> Result<Address::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let default_bytes = entry_metadata.default_bytes();
let val = Address::Target::decode_with_metadata(&mut &*default_bytes, value_ty_id, metadata)?;
Ok(val)
}
+4
View File
@@ -1,3 +1,7 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::utils::hash_bytes;
use crate::{
error::{Error, MetadataError, StorageAddressError},
+11 -73
View File
@@ -1,50 +1,25 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`StorageAddress`] trait, but
//! these utility methods complement the [`AddressT`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, MetadataExt};
use alloc::vec::Vec;
use subxt_metadata::PalletMetadata;
use subxt_metadata::{StorageEntryMetadata, StorageHasher};
use super::StorageAddress;
use crate::{error::Error, metadata::Metadata};
use super::address::AddressT;
use crate::error::{Error, MetadataError};
use crate::metadata::Metadata;
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher};
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// Return the root of a given [`AddressT`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
pub fn write_storage_address_root_bytes<Address: AddressT>(addr: &Address, out: &mut Vec<u8>) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
@@ -65,10 +40,10 @@ pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
}
/// Return details about the given storage entry.
pub fn lookup_entry_details<'a>(
pub fn lookup_storage_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a subxt_metadata::Metadata,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
@@ -79,40 +54,3 @@ pub fn lookup_entry_details<'a>(
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// Validate a storage address against the metadata.
pub fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Given some bytes, a pallet and storage name, decode the response.
pub fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let return_ty = storage_metadata.entry_type().value_ty();
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}
+274 -146
View File
@@ -1,180 +1,308 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::error::MetadataError;
use crate::metadata::Metadata;
use crate::Error;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{Composite, Value, ValueDef, Variant};
//! Construct and sign transactions.
//!
//! # Example
//!
//! ```rust
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::config::PolkadotConfig;
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
//! use subxt_core::tx;
//! use subxt_core::utils::H256;
//! use subxt_core::metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Gather some other information about the chain that we'll need to construct valid extrinsics:
//! let state = tx::ClientState::<PolkadotConfig> {
//! metadata: {
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! metadata::decode_from(&metadata_bytes[..]).unwrap()
//! },
//! genesis_hash: {
//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
//! let bytes = hex::decode(h).unwrap();
//! H256::from_slice(&bytes)
//! },
//! runtime_version: tx::RuntimeVersion {
//! spec_version: 9370,
//! transaction_version: 20,
//! }
//! };
//!
//! // Now we can build a balance transfer extrinsic.
//! let dest = dev::bob().public_key().into();
//! let call = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
//! let params = Params::new().tip(1_000).nonce(0).build();
//!
//! // We can validate that this lines up with the given metadata:
//! tx::validate(&call, &state.metadata).unwrap();
//!
//! // We can build a signed transaction:
//! let signed_call = tx::create_signed(&call, &state, &dev::alice(), params).unwrap();
//!
//! // And log it:
//! println!("Tx: 0x{}", hex::encode(signed_call.encoded()));
//! ```
pub mod payload;
pub mod signer;
pub use signer::Signer;
/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// Encode call data to the provided output.
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher};
use crate::error::{Error, MetadataError};
use crate::metadata::Metadata;
use crate::utils::Encoded;
use alloc::borrow::{Cow, ToOwned};
use alloc::vec::Vec;
use codec::{Compact, Encode};
use payload::PayloadT;
use signer::Signer as SignerT;
use sp_crypto_hashing::blake2_256;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
// Expose these here since we expect them in some calls below.
pub use crate::client::{ClientState, RuntimeVersion};
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
pub fn validate<Call: PayloadT>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
if let Some(details) = call.validation_details() {
let expected_hash = metadata
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
}
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call: PayloadT>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
call.encode_call_data_to(metadata, &mut bytes)?;
Ok(bytes)
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Payload<CallData> {
pallet_name: Cow<'static, str>,
call_name: Cow<'static, str>,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<T: Config, Call: PayloadT>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, metadata)?;
// 2. Encode extrinsic
let extrinsic = {
let mut encoded_inner = Vec::new();
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data_to(metadata, &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
Ok(Transaction::from_bytes(extrinsic))
}
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
/// Create a partial extrinsic.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_partial_signed<T: Config, Call: PayloadT>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T>, Error> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = call_data(call, &client_state.metadata)?;
// 3. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialTransaction {
call_data,
additional_and_extra_params,
})
}
/// Creates a signed extrinsic without submitting it.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_signed<T, Call, Signer>(
call: &Call,
client_state: &ClientState<T>,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<Transaction<T>, Error>
where
T: Config,
Call: PayloadT,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = create_partial_signed(call, client_state, params)?;
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
}
/// This represents a partially constructed transaction that needs signing before it is ready
/// to submit. Use [`PartialTransaction::signer_payload()`] to return the payload that needs signing,
/// [`PartialTransaction::sign()`] to sign the transaction using a [`SignerT`] impl, or
/// [`PartialTransaction::sign_with_address_and_signature()`] to apply an existing signature and address
/// to the transaction.
pub struct PartialTransaction<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
}
impl<T: Config> PartialTransaction<T> {
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases when compared to
// [`PartialExtrinsic::signer_payload()`].
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params.encode_extra_to(&mut bytes);
self.additional_and_extra_params
.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
} else {
f(Cow::Owned(bytes))
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
/// Return the signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the call data.
pub fn call_data(&self) -> &CallData {
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
/// Returns the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
/// Convert this [`PartialTransaction`] into a [`Transaction`], ready to submit.
/// The provided `signer` is responsible for providing the "from" address for the transaction,
/// as well as providing a signature to attach to it.
pub fn sign<Signer>(&self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
// Now, use the signature and "from" address to build the extrinsic.
self.sign_with_address_and_signature(&signer.address(), &signature)
}
/// Returns the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}
}
impl Payload<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
/// Convert this [`PartialTransaction`] into a [`Transaction`], ready to submit.
/// An address, and something representing a signature that can be SCALE encoded, are both
/// needed in order to construct it. If you have a `Signer` to hand, you can use
/// [`PartialTransaction::sign()`] instead.
pub fn sign_with_address_and_signature(
&self,
address: &T::Address,
signature: &T::Signature,
) -> Transaction<T> {
// Encode the extrinsic (into the format expected by protocol version 4)
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
address.encode_to(&mut encoded_inner);
// the signature
signature.encode_to(&mut encoded_inner);
// attach custom extra params
self.additional_and_extra_params
.encode_extra_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
Value::unnamed_variant(self.pallet_name, [call])
// Return an extrinsic ready to be submitted.
Transaction::from_bytes(extrinsic)
}
}
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.map(|f| scale_encode::Field::new(&f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)
.expect("The fields are valid types from the metadata, qed;");
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
/// This represents a signed transaction that's ready to be submitted.
/// Use [`Transaction::encoded()`] or [`Transaction::into_encoded()`] to
/// get the bytes for it, or [`Transaction::hash()`] to get the hash.
pub struct Transaction<T> {
encoded: Encoded,
marker: core::marker::PhantomData<T>,
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
impl<T: Config> Transaction<T> {
/// Create a [`Transaction`] from some already-signed and prepared
/// extrinsic bytes,
pub fn from_bytes(tx_bytes: Vec<u8>) -> Self {
Self {
encoded: Encoded(tx_bytes),
marker: core::marker::PhantomData,
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
T::Hasher::hash_of(&self.encoded)
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
/// Consumes this [`Transaction`] and returns the SCALE encoded
/// extrinsic bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.encoded.0
}
}
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::error::MetadataError;
use crate::metadata::Metadata;
use crate::Error;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{Composite, Value, ValueDef, Variant};
/// This represents a transaction payload that can be submitted
/// to a node.
pub trait PayloadT {
/// Encode call data to the provided output.
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`PayloadT::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
}
}
/// Details required to validate the shape of a transaction payload against some metadata.
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Payload<CallData> {
pallet_name: Cow<'static, str>,
call_name: Cow<'static, str>,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the call data.
pub fn call_data(&self) -> &CallData {
&self.call_data
}
/// Returns the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Returns the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}
}
impl Payload<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
};
Value::unnamed_variant(self.pallet_name, [call])
}
}
impl<CallData: EncodeAsFields> PayloadT for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.map(|f| scale_encode::Field::new(&f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)
.expect("The fields are valid types from the metadata, qed;");
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+3 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -24,14 +24,11 @@ pub use account_id::AccountId32;
pub use era::Era;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
pub use primitive_types::{H160, H256, H512};
pub use static_type::Static;
pub use unchecked_extrinsic::UncheckedExtrinsic;
pub use wrapper_opaque::WrapperKeepOpaque;
// Used in codegen
#[doc(hidden)]
pub use primitive_types::{H160, H256, H512};
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
@@ -74,7 +71,7 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// A unit marker struct signalling that some property is true
/// A unit marker struct.
pub struct Yes;
/// A quick helper to encode some bytes to hex.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
+334 -221
View File
File diff suppressed because it is too large. Load diff
+48 -33
View File
@@ -3,11 +3,11 @@ use futures::FutureExt;
use subxt::{OnlineClient, PolkadotConfig};
use subxt::ext::codec::{Decode, Encode};
use subxt::tx::SubmittableExtrinsic;
use subxt::tx::TxPayload;
use subxt::utils::{AccountId32, MultiSignature};
use subxt::config::DefaultExtrinsicParamsBuilder;
use subxt::ext::codec::{Decode, Encode};
use subxt::tx::PayloadT as _;
use subxt::tx::SubmittableExtrinsic;
use subxt::utils::{AccountId32, MultiSignature};
use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account};
use web_sys::HtmlInputElement;
@@ -137,41 +137,54 @@ impl Component for SigningExamplesComponent {
let api = self.online_client.as_ref().unwrap().clone();
ctx.link()
.send_future(
async move {
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
return Message::Error(anyhow!("Fetching account nonce failed"));
};
ctx.link().send_future(async move {
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
return Message::Error(anyhow!("Fetching account nonce failed"));
};
let Ok(call_data) = api.tx().call_data(&remark_call) else {
return Message::Error(anyhow!("could not encode call data"));
};
let Ok(call_data) = api.tx().call_data(&remark_call) else {
return Message::Error(anyhow!("could not encode call data"));
};
let Ok(signature) = extension_signature_for_extrinsic(&call_data, &api, account_nonce, account_source, account_address).await else {
return Message::Error(anyhow!("Signing via extension failed"));
};
let Ok(signature) = extension_signature_for_extrinsic(
&call_data,
&api,
account_nonce,
account_source,
account_address,
)
.await
else {
return Message::Error(anyhow!("Signing via extension failed"));
};
let Ok(multi_signature) = MultiSignature::decode(&mut &signature[..]) else {
return Message::Error(anyhow!("MultiSignature Decoding"));
};
let Ok(multi_signature) = MultiSignature::decode(&mut &signature[..])
else {
return Message::Error(anyhow!("MultiSignature Decoding"));
};
let params = DefaultExtrinsicParamsBuilder::new().nonce(account_nonce).build();
let Ok(partial_signed) = api.tx().create_partial_signed_offline(&remark_call, params) else {
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
};
let params = DefaultExtrinsicParamsBuilder::new()
.nonce(account_nonce)
.build();
let Ok(partial_signed) =
api.tx().create_partial_signed_offline(&remark_call, params)
else {
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
};
// Apply the signature
let signed_extrinsic = partial_signed.sign_with_address_and_signature(&account_id.into(), &multi_signature);
// Apply the signature
let signed_extrinsic = partial_signed
.sign_with_address_and_signature(&account_id.into(), &multi_signature);
// check the TX validity (to debug in the js console if the extrinsic would work)
let dry_res = signed_extrinsic.validate().await;
web_sys::console::log_1(&format!("Validation Result: {:?}", dry_res).into());
// return the signature and signed extrinsic
Message::ReceivedSignature(multi_signature, signed_extrinsic)
}
// check the TX validity (to debug in the js console if the extrinsic would work)
let dry_res = signed_extrinsic.validate().await;
web_sys::console::log_1(
&format!("Validation Result: {:?}", dry_res).into(),
);
// return the signature and signed extrinsic
Message::ReceivedSignature(multi_signature, signed_extrinsic)
});
}
}
Message::ReceivedSignature(signature, signed_extrinsic) => {
@@ -192,7 +205,9 @@ impl Component for SigningExamplesComponent {
..
} = &mut self.stage
{
let SubmittingStage::Initial { signed_extrinsic } = std::mem::replace(submitting_stage, SubmittingStage::Submitting) else {
let SubmittingStage::Initial { signed_extrinsic } =
std::mem::replace(submitting_stage, SubmittingStage::Submitting)
else {
panic!("unreachable")
};
+2 -2
View File
@@ -130,8 +130,8 @@ pub async fn extension_signature_for_extrinsic(
) -> Result<Vec<u8>, anyhow::Error> {
let genesis_hash = encode_then_hex(&api.genesis_hash());
// These numbers aren't SCALE encoded; their bytes are just converted to hex:
let spec_version = to_hex(&api.runtime_version().spec_version().to_be_bytes());
let transaction_version = to_hex(&api.runtime_version().transaction_version().to_be_bytes());
let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes());
let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes());
let nonce = to_hex(&account_nonce.to_be_bytes());
// If you construct a mortal transaction, then this block hash needs to correspond
// to the block number passed to `Era::mortal()`.
+1 -1
View File
@@ -283,7 +283,7 @@ mod subxt_compat {
use super::*;
use subxt_core::config::Config;
use subxt_core::tx::Signer as SignerT;
use subxt_core::tx::signer::Signer as SignerT;
use subxt_core::utils::{AccountId32, MultiAddress, MultiSignature};
impl From<Signature> for MultiSignature {
+1 -1
View File
@@ -259,7 +259,7 @@ mod subxt_compat {
use super::*;
use subxt_core::{
tx::Signer as SignerT,
tx::signer::Signer as SignerT,
utils::{AccountId32, MultiAddress, MultiSignature},
Config,
};
+4 -1
View File
@@ -16,7 +16,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};
// 2. A runtime version (system_version constant on a Substrate node has these):
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
let runtime_version = subxt::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
// 3. Metadata (I'll load it from the downloaded metadata, but you can use
// `subxt metadata > file.scale` to download it):
+1 -1
View File
@@ -62,7 +62,7 @@ impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
// Gather together all of the params we will need to encode:
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(Self {
genesis_hash: client.genesis_hash(),
genesis_hash: client.genesis_hash,
tip: params.tip,
foo: params.foo,
})
+3 -2
View File
@@ -22,9 +22,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.fetch_raw(subxt_core::storage::get_address_bytes(&storage_query, &api.metadata()).unwrap())
.await?;
println!("Alice has free balance: {}", result.unwrap().data.free);
let v = hex::encode(result.unwrap());
println!("Alice: {v}");
Ok(())
}
+10 -6
View File
@@ -181,16 +181,20 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
let details = self.methods.state_get_runtime_version(None).await?;
Ok(RuntimeVersion::new(
details.spec_version,
details.transaction_version,
))
Ok(RuntimeVersion {
spec_version: details.spec_version,
transaction_version: details.transaction_version,
})
}
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
let sub = self.methods.state_subscribe_runtime_version().await?;
let sub =
sub.map(|r| r.map(|v| RuntimeVersion::new(v.spec_version, v.transaction_version)));
let sub = sub.map(|r| {
r.map(|v| RuntimeVersion {
spec_version: v.spec_version,
transaction_version: v.transaction_version,
})
});
Ok(StreamOf(Box::pin(sub)))
}
+4 -1
View File
@@ -416,7 +416,10 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
RuntimeEvent::Valid(ev) => ev,
};
let runtime_version = RuntimeVersion::new(runtime_details.spec.spec_version, runtime_details.spec.transaction_version);
let runtime_version = RuntimeVersion {
spec_version: runtime_details.spec.spec_version,
transaction_version: runtime_details.spec.transaction_version
};
std::future::ready(Some(Ok(runtime_version)))
});
+2 -4
View File
@@ -4,7 +4,7 @@
use crate::{
backend::BlockRef,
blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics},
blocks::Extrinsics,
client::{OfflineClientT, OnlineClientT},
config::{Config, Header},
error::{BlockError, DecodeError, Error},
@@ -79,7 +79,6 @@ where
/// Fetch and return the extrinsics in the block body.
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
let ids = ExtrinsicPartTypeIds::new(&self.client.metadata())?;
let block_hash = self.header.hash();
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
return Err(BlockError::not_found(block_hash).into());
@@ -89,9 +88,8 @@ where
self.client.clone(),
extrinsics,
self.cached_events.clone(),
ids,
block_hash,
))
)?)
}
/// Work with storage.
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -15,7 +15,7 @@ pub use block_types::Block;
pub use blocks_client::BlocksClient;
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions,
Extrinsics, StaticExtrinsic,
Extrinsics, FoundExtrinsic, StaticExtrinsic,
};
// We get account nonce info in tx_client, too, so re-use the logic:
+1 -1
View File
@@ -40,7 +40,7 @@
//! );
//! ```
//!
//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which
//! All valid runtime calls implement [`crate::runtime_api::PayloadT`], a trait which
//! describes how to encode the runtime call arguments and what return type to decode from the
//! response.
//!
+2 -2
View File
@@ -66,7 +66,7 @@
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
//! a given `u8`, `bool` and `u16` value.
//!
//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing
//! All valid storage queries implement [`crate::storage::AddressT`]. As well as describing
//! how to build a valid storage query, this trait also has some associated types that determine the
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
//! over storage entries using it).
@@ -124,7 +124,7 @@
//!
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
//! obtained from a [`crate::storage::StorageAddress`] by using
//! obtained from a [`crate::storage::AddressT`] by using
//! [`crate::storage::StorageClient::address_bytes()`] or
//! [`crate::storage::StorageClient::address_root_bytes()`].
//!
+1 -1
View File
@@ -53,7 +53,7 @@
//! represents any type of data that can be SCALE encoded or decoded. It can be serialized,
//! deserialized and parsed from/to strings.
//!
//! A valid transaction payload is just something that implements the [`crate::tx::TxPayload`] trait;
//! A valid transaction payload is just something that implements the [`crate::tx::PayloadT`] trait;
//! you can implement this trait on your own custom types if the built-in ones are not suitable for
//! your needs.
//!
+16 -19
View File
@@ -17,13 +17,20 @@ use subxt_core::client::{ClientState, RuntimeVersion};
pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
/// Return the provided [`Metadata`].
fn metadata(&self) -> Metadata;
/// Return the provided genesis hash.
fn genesis_hash(&self) -> T::Hash;
/// Return the provided [`RuntimeVersion`].
fn runtime_version(&self) -> RuntimeVersion;
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
fn client_state(&self) -> ClientState<T> {
ClientState::new(self.genesis_hash(), self.runtime_version(), self.metadata())
ClientState {
genesis_hash: self.genesis_hash(),
runtime_version: self.runtime_version(),
metadata: self.metadata(),
}
}
/// Work with transactions.
@@ -77,37 +84,30 @@ impl<T: Config> OfflineClient<T> {
runtime_version: RuntimeVersion,
metadata: impl Into<Metadata>,
) -> OfflineClient<T> {
let metadata = metadata.into();
OfflineClient {
inner: Arc::new(ClientState::new(
inner: Arc::new(ClientState {
genesis_hash,
runtime_version,
metadata.into(),
)),
metadata,
}),
}
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
self.inner.genesis_hash()
self.inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
self.inner.runtime_version()
self.inner.runtime_version
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
self.inner.metadata()
}
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
pub fn client_state(&self) -> ClientState<T> {
ClientState::new(
self.inner.genesis_hash(),
self.inner.runtime_version(),
self.inner.metadata(),
)
self.inner.metadata.clone()
}
// Just a copy of the most important trait methods so that people
@@ -149,9 +149,6 @@ impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
fn metadata(&self) -> Metadata {
self.metadata()
}
fn client_state(&self) -> ClientState<T> {
self.client_state()
}
}
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
+9 -14
View File
@@ -228,7 +228,7 @@ impl<T: Config> OnlineClient<T> {
/// let mut update_stream = updater.runtime_updates().await.unwrap();
///
/// while let Some(Ok(update)) = update_stream.next().await {
/// let version = update.runtime_version().spec_version();
/// let version = update.runtime_version().spec_version;
///
/// match updater.apply_update(update) {
/// Ok(()) => {
@@ -286,16 +286,6 @@ impl<T: Config> OnlineClient<T> {
inner.runtime_version
}
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
pub fn client_state(&self) -> ClientState<T> {
let inner = self.inner.read().expect("shouldn't be poisoned");
ClientState::new(
inner.genesis_hash,
inner.runtime_version,
inner.metadata.clone(),
)
}
/// Change the [`RuntimeVersion`] used in this client.
///
/// # Warning
@@ -371,9 +361,14 @@ impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
// This is provided by default, but we can optimise here and only lock once:
fn client_state(&self) -> ClientState<T> {
self.client_state()
let inner = self.inner.read().expect("shouldn't be poisoned");
ClientState {
genesis_hash: inner.genesis_hash,
runtime_version: inner.runtime_version,
metadata: inner.metadata.clone(),
}
}
}
@@ -551,7 +546,7 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
// We are waiting for the chain to have the same spec version
// as sent out via the runtime subscription.
if spec_version == runtime_version.spec_version() {
if spec_version == runtime_version.spec_version {
break block_ref;
}
};
+5 -8
View File
@@ -2,9 +2,9 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::ConstantAddress;
use crate::{client::OfflineClientT, error::Error, Config};
use derive_where::derive_where;
use subxt_core::constants::address::AddressT;
/// A client for accessing constants.
#[derive_where(Clone; Client)]
@@ -28,19 +28,16 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or constant in question do not exist at all).
pub fn validate<Address: ConstantAddress>(&self, address: &Address) -> Result<(), Error> {
pub fn validate<Address: AddressT>(&self, address: &Address) -> Result<(), Error> {
let metadata = self.client.metadata();
subxt_core::constants::validate_constant(&metadata, address).map_err(Error::from)
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
}
/// Access the constant at the address given, returning the type defined by this address.
/// This is probably used with addresses given from static codegen, although you can manually
/// construct your own, too.
pub fn at<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<Address::Target, Error> {
pub fn at<Address: AddressT>(&self, address: &Address) -> Result<Address::Target, Error> {
let metadata = self.client.metadata();
subxt_core::constants::get_constant(&metadata, address).map_err(Error::from)
subxt_core::constants::get(address, &metadata).map_err(Error::from)
}
}
+1 -1
View File
@@ -7,4 +7,4 @@
mod constants_client;
pub use constants_client::ConstantsClient;
pub use subxt_core::constants::{dynamic, Address, ConstantAddress, DynamicAddress};
pub use subxt_core::constants::address::{dynamic, Address, AddressT, DynamicAddress};
+12 -15
View File
@@ -2,10 +2,7 @@ use crate::client::OfflineClientT;
use crate::{Config, Error};
use derive_where::derive_where;
use subxt_core::custom_values::{
get_custom_value, get_custom_value_bytes, validate_custom_value, CustomValueAddress,
};
use subxt_core::utils::Yes;
use subxt_core::custom_values::address::{AddressT, Yes};
/// A client for accessing custom values stored in the metadata.
#[derive_where(Clone; Client)]
@@ -27,29 +24,26 @@ impl<T, Client> CustomValuesClient<T, Client> {
impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn at<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
pub fn at<Address: AddressT<IsDecodable = Yes> + ?Sized>(
&self,
address: &Address,
) -> Result<Address::Target, Error> {
get_custom_value(&self.client.metadata(), address).map_err(Into::into)
subxt_core::custom_values::get(address, &self.client.metadata()).map_err(Into::into)
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn bytes_at<Address: CustomValueAddress + ?Sized>(
pub fn bytes_at<Address: AddressT + ?Sized>(
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
get_custom_value_bytes(&self.client.metadata(), address).map_err(Into::into)
subxt_core::custom_values::get_bytes(address, &self.client.metadata()).map_err(Into::into)
}
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate<Address: CustomValueAddress + ?Sized>(
&self,
address: &Address,
) -> Result<(), Error> {
validate_custom_value(&self.client.metadata(), address).map_err(Into::into)
pub fn validate<Address: AddressT + ?Sized>(&self, address: &Address) -> Result<(), Error> {
subxt_core::custom_values::validate(address, &self.client.metadata()).map_err(Into::into)
}
}
@@ -113,14 +107,17 @@ mod tests {
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::new(metadata)
Metadata::from(metadata)
}
#[test]
fn test_decoding() {
let client = OfflineClient::<SubstrateConfig>::new(
Default::default(),
RuntimeVersion::new(0, 0),
RuntimeVersion {
spec_version: 0,
transaction_version: 0,
},
mock_metadata(),
);
let custom_value_client = CustomValuesClient::new(client);
+1 -1
View File
@@ -7,4 +7,4 @@
mod custom_values_client;
pub use custom_values_client::CustomValuesClient;
pub use subxt_core::custom_values::{CustomValueAddress, StaticAddress};
pub use subxt_core::custom_values::address::{AddressT, StaticAddress, Yes};
+21 -8
View File
@@ -6,6 +6,8 @@
mod dispatch_error;
use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError};
crate::macros::cfg_unstable_light_client! {
pub use subxt_lightclient::LightClientError;
}
@@ -80,15 +82,16 @@ pub enum Error {
Other(String),
}
impl From<subxt_core::Error> for Error {
fn from(value: subxt_core::Error) -> Self {
impl From<CoreError> for Error {
fn from(value: CoreError) -> Self {
match value {
subxt_core::Error::Codec(e) => Error::Codec(e),
subxt_core::Error::Metadata(e) => Error::Metadata(e),
subxt_core::Error::StorageAddress(e) => Error::StorageAddress(e),
subxt_core::Error::Decode(e) => Error::Decode(e),
subxt_core::Error::Encode(e) => Error::Encode(e),
subxt_core::Error::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Codec(e) => Error::Codec(e),
CoreError::Metadata(e) => Error::Metadata(e),
CoreError::StorageAddress(e) => Error::StorageAddress(e),
CoreError::Decode(e) => Error::Decode(e),
CoreError::Encode(e) => Error::Encode(e),
CoreError::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Block(e) => Error::Block(e.into()),
}
}
}
@@ -175,6 +178,16 @@ pub enum BlockError {
DecodingError(codec::Error),
}
impl From<CoreBlockError> for BlockError {
fn from(value: CoreBlockError) -> Self {
match value {
CoreBlockError::MissingType => BlockError::MissingType,
CoreBlockError::UnsupportedVersion(n) => BlockError::UnsupportedVersion(n),
CoreBlockError::DecodingError(e) => BlockError::DecodingError(e),
}
}
}
impl BlockError {
/// Produce an error that a block with the given hash cannot be found.
pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError {
+1 -1
View File
@@ -64,7 +64,7 @@ where
};
let event_bytes = get_event_bytes(client.backend(), block_ref.hash()).await?;
Ok(Events::decode_from(client.metadata(), event_bytes))
Ok(Events::decode_from(event_bytes, client.metadata()))
}
}
}
+1 -1
View File
@@ -24,5 +24,5 @@ where
C: OnlineClientT<T>,
{
let event_bytes = events_client::get_event_bytes(client.backend(), block_hash).await?;
Ok(Events::<T>::decode_from(metadata, event_bytes))
Ok(Events::<T>::decode_from(event_bytes, metadata))
}
+1 -1
View File
@@ -71,7 +71,7 @@ pub mod config {
/// Types representing the metadata obtained from a node.
pub mod metadata {
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata, MetadataExt};
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
}
+1 -1
View File
@@ -9,4 +9,4 @@ mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_types::RuntimeApi;
pub use subxt_core::runtime_api::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
pub use subxt_core::runtime_api::payload::{dynamic, DynamicPayload, Payload, PayloadT};
+18 -30
View File
@@ -2,19 +2,17 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::PayloadT;
use crate::{
backend::{BackendExt, BlockRef},
client::OnlineClientT,
error::{Error, MetadataError},
metadata::DecodeWithMetadata,
error::Error,
Config,
};
use codec::Decode;
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
use super::RuntimeApiPayload;
/// Execute runtime API calls.
#[derive_where(Clone; Client)]
pub struct RuntimeApi<T: Config, Client> {
@@ -39,6 +37,14 @@ where
T: Config,
Client: OnlineClientT<T>,
{
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the runtime API in question do not exist at all)
pub fn validate<Call: PayloadT>(&self, payload: &Call) -> Result<(), Error> {
subxt_core::runtime_api::validate(payload, &self.client.metadata()).map_err(Into::into)
}
/// Execute a raw runtime API call.
pub fn call_raw<'a, Res: Decode>(
&self,
@@ -59,7 +65,7 @@ where
}
/// Execute a runtime API call.
pub fn call<Call: RuntimeApiPayload>(
pub fn call<Call: PayloadT>(
&self,
payload: Call,
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
@@ -70,39 +76,21 @@ where
async move {
let metadata = client.metadata();
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
let api_method = api_trait
.method_by_name(payload.method_name())
.ok_or_else(|| {
MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned())
})?;
// Validate the runtime API payload hash against the compile hash from codegen.
if let Some(static_hash) = payload.validation_hash() {
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if static_hash != runtime_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
subxt_core::runtime_api::validate(&payload, &metadata)?;
// Encode the arguments of the runtime call.
// For static payloads (codegen) this is pass-through, bytes are not altered.
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
let params = payload.encode_args(&metadata)?;
let call_name = format!("{}_{}", payload.trait_name(), payload.method_name());
let call_name = subxt_core::runtime_api::call_name(&payload);
let call_args = subxt_core::runtime_api::call_args(&payload, &metadata)?;
// Make the call.
let bytes = client
.backend()
.call(&call_name, Some(params.as_slice()), block_hash)
.call(&call_name, Some(call_args.as_slice()), block_hash)
.await?;
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
api_method.output_ty(),
&metadata,
)?;
// Decode the response.
let value = subxt_core::runtime_api::decode_value(&mut &*bytes, &payload, &metadata)?;
Ok(value)
}
}
+3 -15
View File
@@ -8,19 +8,7 @@ mod storage_client;
mod storage_type;
pub use storage_client::StorageClient;
pub use storage_type::{Storage, StorageKeyValuePair};
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use subxt_core::storage::address::{
dynamic, Address, DynamicAddress, StaticStorageKey, StorageAddress, StorageKey,
};
}
pub use subxt_core::storage::StorageKey;
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use address::{dynamic, Address, DynamicAddress, StorageAddress};
pub use subxt_core::storage::address::{
dynamic, Address, AddressT, DynamicAddress, StaticStorageKey, StorageKey,
};
+9 -17
View File
@@ -2,10 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{
storage_type::{validate_storage_address, Storage},
StorageAddress,
};
use super::storage_type::Storage;
use crate::{
backend::BlockRef,
client::{OfflineClientT, OnlineClientT},
@@ -14,6 +11,7 @@ use crate::{
};
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
use subxt_core::storage::address::AddressT;
/// Query the runtime storage.
#[derive_where(Clone; Client)]
@@ -41,29 +39,23 @@ where
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or storage entry in question do not exist at all).
pub fn validate<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
let metadata = self.client.metadata();
let pallet_metadata = metadata.pallet_by_name_err(address.pallet_name())?;
validate_storage_address(address, pallet_metadata)
pub fn validate<Address: AddressT>(&self, address: &Address) -> Result<(), Error> {
subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve the entries at the root of the associated address.
pub fn address_root_bytes<Address: StorageAddress>(&self, address: &Address) -> Vec<u8> {
subxt_core::storage::utils::storage_address_root_bytes(address)
pub fn address_root_bytes<Address: AddressT>(&self, address: &Address) -> Vec<u8> {
subxt_core::storage::get_address_root_bytes(address)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve an entry. This fails if [`StorageAddress::append_entry_bytes`] does; in the built-in
/// to retrieve an entry. This fails if [`AddressT::append_entry_bytes`] does; in the built-in
/// implementation this would be if the pallet and storage entry being asked for is not available on the
/// node you're communicating with, or if the metadata is missing some type information (which should not
/// happen).
pub fn address_bytes<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
subxt_core::storage::utils::storage_address_bytes(address, &self.client.metadata())
.map_err(Into::into)
pub fn address_bytes<Address: AddressT>(&self, address: &Address) -> Result<Vec<u8>, Error> {
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
}
}
+18 -94
View File
@@ -2,23 +2,19 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use subxt_core::storage::address::{StorageAddress, StorageHashers, StorageKey};
use subxt_core::utils::Yes;
use crate::{
backend::{BackendExt, BlockRef},
client::OnlineClientT,
error::{Error, MetadataError, StorageAddressError},
metadata::{DecodeWithMetadata, Metadata},
metadata::DecodeWithMetadata,
Config,
};
use codec::Decode;
use derive_where::derive_where;
use futures::StreamExt;
use std::{future::Future, marker::PhantomData};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
use subxt_core::storage::address::{AddressT, StorageHashers, StorageKey};
use subxt_core::utils::Yes;
/// This is returned from a couple of storage functions.
pub use crate::backend::StreamOfResults;
@@ -119,26 +115,22 @@ where
address: &'address Address,
) -> impl Future<Output = Result<Option<Address::Target>, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes> + 'address,
Address: AddressT<IsFetchable = Yes> + 'address,
{
let client = self.clone();
async move {
let metadata = client.client.metadata();
let (pallet, entry) =
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
validate_storage_address(address, pallet)?;
subxt_core::storage::validate(address, &metadata)?;
// Look up the return type ID to enable DecodeWithMetadata:
let lookup_bytes =
subxt_core::storage::utils::storage_address_bytes(address, &metadata)?;
let lookup_bytes = subxt_core::storage::get_address_bytes(address, &metadata)?;
if let Some(data) = client.fetch_raw(lookup_bytes).await? {
let val =
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
let val = subxt_core::storage::decode_value(&mut &*data, address, &metadata)?;
Ok(Some(val))
} else {
Ok(None)
@@ -152,24 +144,16 @@ where
address: &'address Address,
) -> impl Future<Output = Result<Address::Target, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
Address: AddressT<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
{
let client = self.clone();
async move {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
// Metadata validation happens via .fetch():
if let Some(data) = client.fetch(address).await? {
Ok(data)
} else {
let metadata = client.client.metadata();
let (_pallet_metadata, storage_entry) =
lookup_entry_details(pallet_name, entry_name, &metadata)?;
let return_ty_id = return_type_from_storage_entry_type(storage_entry.entry_type());
let bytes = &mut storage_entry.default_bytes();
let val = Address::Target::decode_with_metadata(bytes, return_ty_id, &metadata)?;
let val = subxt_core::storage::default_value(address, &metadata)?;
Ok(val)
}
}
@@ -211,21 +195,24 @@ where
address: Address,
) -> impl Future<Output = Result<StreamOfResults<StorageKeyValuePair<Address>>, Error>> + 'static
where
Address: StorageAddress<IsIterable = Yes> + 'static,
Address: AddressT<IsIterable = Yes> + 'static,
Address::Keys: 'static + Sized,
{
let client = self.client.clone();
let block_ref = self.block_ref.clone();
async move {
let metadata = client.metadata();
let (pallet, entry) =
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
let (_pallet, entry) = subxt_core::storage::lookup_storage_entry_details(
address.pallet_name(),
address.entry_name(),
&metadata,
)?;
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
validate_storage_address(&address, pallet)?;
subxt_core::storage::validate(&address, &metadata)?;
// Look up the return type for flexible decoding. Do this once here to avoid
// potentially doing it every iteration if we used `decode_storage_with_metadata`
@@ -236,8 +223,7 @@ where
let hashers = StorageHashers::new(entry, metadata.types())?;
// The address bytes of this entry:
let address_bytes =
subxt_core::storage::utils::storage_address_bytes(&address, &metadata)?;
let address_bytes = subxt_core::storage::get_address_bytes(&address, &metadata)?;
let s = client
.backend()
.storage_fetch_descendant_values(address_bytes, block_ref.hash())
@@ -327,7 +313,7 @@ fn strip_storage_address_root_bytes(address_bytes: &mut &[u8]) -> Result<(), Sto
/// A pair of keys and values together with all the bytes that make up the storage address.
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct StorageKeyValuePair<T: StorageAddress> {
pub struct StorageKeyValuePair<T: AddressT> {
/// The bytes that make up the address of the storage entry.
pub key_bytes: Vec<u8>,
/// The keys that can be used to construct the address of this storage entry.
@@ -335,65 +321,3 @@ pub struct StorageKeyValuePair<T: StorageAddress> {
/// The value of the storage entry.
pub value: T::Target,
}
/// Validate a storage address against the metadata.
pub(crate) fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Return details about the given storage entry.
fn lookup_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Fetch the return type out of a [`StorageEntryType`].
fn return_type_from_storage_entry_type(entry: &StorageEntryType) -> u32 {
match entry {
StorageEntryType::Plain(ty) => *ty,
StorageEntryType::Map { value_ty, .. } => *value_ty,
}
}
/// Given some bytes, a pallet and storage name, decode the response.
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let ty = storage_metadata.entry_type();
let return_ty = return_type_from_storage_entry_type(ty);
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}
-60
View File
@@ -1,60 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`StorageAddress`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use subxt_metadata::StorageHasher;
use super::StorageAddress;
use crate::{error::Error, metadata::Metadata};
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_crypto_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_crypto_hashing::twox_64(input));
bytes.extend(input);
}
}
}
+7 -12
View File
@@ -14,21 +14,16 @@ use crate::macros::cfg_substrate_compat;
mod tx_client;
mod tx_progress;
pub use subxt_core::tx as tx_payload;
pub use subxt_core::tx::signer;
// The PairSigner impl currently relies on Substrate bits and pieces, so make it an optional
// feature if we want to avoid needing sp_core and sp_runtime.
cfg_substrate_compat! {
pub use signer::PairSigner;
pub use subxt_core::tx::signer::PairSigner;
}
pub use self::{
signer::Signer,
tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
},
tx_payload::{dynamic, DynamicPayload, Payload, TxPayload},
tx_progress::{TxInBlock, TxProgress, TxStatus},
pub use subxt_core::tx::payload::{dynamic, DynamicPayload, Payload, PayloadT};
pub use subxt_core::tx::signer::{self, Signer};
pub use tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
};
pub use tx_progress::{TxInBlock, TxProgress, TxStatus};
+56 -153
View File
@@ -2,22 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::borrow::Cow;
use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header, RefineParams,
RefineParamsData,
},
error::{BlockError, Error, MetadataError},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
config::{Config, ExtrinsicParams, Header, RefineParams, RefineParamsData},
error::{BlockError, Error},
tx::{PayloadT, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use sp_crypto_hashing::blake2_256;
/// A client for working with transactions.
#[derive_where(Clone; Client)]
@@ -43,65 +37,30 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// the pallet or call in question do not exist at all).
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
where
Call: TxPayload,
Call: PayloadT,
{
if let Some(details) = call.validation_details() {
let expected_hash = self
.client
.metadata()
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
subxt_core::tx::validate(call, &self.client.metadata()).map_err(Into::into)
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data_to(&metadata, &mut bytes)?;
Ok(bytes)
subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
}
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Encode extrinsic
let extrinsic = {
let mut encoded_inner = Vec::new();
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data_to(&self.client.metadata(), &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
Ok(SubmittableExtrinsic::from_bytes(
self.client.clone(),
extrinsic,
))
subxt_core::tx::create_unsigned(call, &self.client.metadata())
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
/// Create a partial extrinsic.
@@ -114,25 +73,14 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = self.call_data(call)?;
// 3. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(&self.client.client_state(), params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialExtrinsic {
client: self.client.clone(),
call_data,
additional_and_extra_params,
})
subxt_core::tx::create_partial_signed(call, &self.client.client_state(), params)
.map(|tx| PartialExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
/// Creates a signed extrinsic without submitting it.
@@ -146,19 +94,15 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = self.create_partial_signed_offline(call, params)?;
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
subxt_core::tx::create_signed(call, &self.client.client_state(), signer, params)
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
}
@@ -205,7 +149,7 @@ where
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// Refine the params by adding account nonce and latest block information:
self.refine_params(account_id, &mut params).await?;
@@ -221,7 +165,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
@@ -249,7 +193,7 @@ where
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
{
@@ -268,7 +212,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params)
@@ -293,7 +237,7 @@ where
signer: &Signer,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
{
@@ -315,7 +259,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params)
@@ -328,8 +272,7 @@ where
/// This payload contains the information needed to produce an extrinsic.
pub struct PartialExtrinsic<T: Config, C> {
client: C,
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
inner: subxt_core::tx::PartialTransaction<T>,
}
impl<T, C> PartialExtrinsic<T, C>
@@ -337,34 +280,16 @@ where
T: Config,
C: OfflineClientT<T>,
{
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases when compared to
// [`PartialExtrinsic::signer_payload()`].
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params.encode_extra_to(&mut bytes);
self.additional_and_extra_params
.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
} else {
f(Cow::Owned(bytes))
}
}
/// Return the signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
self.inner.signer_payload()
}
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
self.inner.call_data()
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
@@ -374,10 +299,10 @@ where
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
// Now, use the signature and "from" address to build the extrinsic.
self.sign_with_address_and_signature(&signer.address(), &signature)
SubmittableExtrinsic {
client: self.client.clone(),
inner: self.inner.sign(signer),
}
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
@@ -389,40 +314,19 @@ where
address: &T::Address,
signature: &T::Signature,
) -> SubmittableExtrinsic<T, C> {
// Encode the extrinsic (into the format expected by protocol version 4)
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
address.encode_to(&mut encoded_inner);
// the signature
signature.encode_to(&mut encoded_inner);
// attach custom extra params
self.additional_and_extra_params
.encode_extra_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Return an extrinsic ready to be submitted.
SubmittableExtrinsic::from_bytes(self.client.clone(), extrinsic)
SubmittableExtrinsic {
client: self.client.clone(),
inner: self
.inner
.sign_with_address_and_signature(address, signature),
}
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SubmittableExtrinsic<T, C> {
client: C,
encoded: Encoded,
marker: std::marker::PhantomData<T>,
inner: subxt_core::tx::Transaction<T>,
}
impl<T, C> SubmittableExtrinsic<T, C>
@@ -440,25 +344,24 @@ where
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
Self {
client,
encoded: Encoded(tx_bytes),
marker: std::marker::PhantomData,
inner: subxt_core::tx::Transaction::from_bytes(tx_bytes),
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
T::Hasher::hash_of(&self.encoded)
self.inner.hash()
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
self.inner.encoded()
}
/// Consumes [`SubmittableExtrinsic`] and returns the SCALE encoded
/// extrinsic bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.encoded.0
self.inner.into_encoded()
}
}
@@ -479,7 +382,7 @@ where
let sub = self
.client
.backend()
.submit_transaction(&self.encoded.0)
.submit_transaction(self.encoded())
.await?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
@@ -495,7 +398,7 @@ where
let mut sub = self
.client
.backend()
.submit_transaction(&self.encoded.0)
.submit_transaction(self.encoded())
.await?;
// If we get a bad status or error back straight away then error, else return the hash.
@@ -543,7 +446,7 @@ where
let block_hash = at.into().hash();
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
let mut params = Vec::with_capacity(8 + self.encoded.0.len() + 8);
let mut params = Vec::with_capacity(8 + self.encoded().len() + 8);
2u8.encode_to(&mut params);
params.extend(self.encoded().iter());
block_hash.encode_to(&mut params);
@@ -52,8 +52,8 @@ async fn chainhead_unstable_follow() {
FollowEvent::Initialized(init) => {
assert_eq!(init.finalized_block_hashes, vec![finalized_block_hash]);
if let Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec })) = init.finalized_block_runtime {
assert_eq!(spec.spec_version, runtime_version.spec_version());
assert_eq!(spec.transaction_version, runtime_version.transaction_version());
assert_eq!(spec.spec_version, runtime_version.spec_version);
assert_eq!(spec.transaction_version, runtime_version.transaction_version);
} else {
panic!("runtime details not provided with init event, got {:?}", init.finalized_block_runtime);
}
@@ -69,7 +69,7 @@ async fn chainhead_unstable_body() {
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
let event = blocks.next().await.unwrap().unwrap();
let hash = match event {
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
_ => panic!("Unexpected event"),
};
let sub_id = blocks.subscription_id().unwrap();
@@ -98,7 +98,7 @@ async fn chainhead_unstable_header() {
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
let event = blocks.next().await.unwrap().unwrap();
let hash = match event {
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
_ => panic!("Unexpected event"),
};
let sub_id = blocks.subscription_id().unwrap();
@@ -126,7 +126,7 @@ async fn chainhead_unstable_storage() {
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
let event = blocks.next().await.unwrap().unwrap();
let hash = match event {
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
_ => panic!("Unexpected event"),
};
let sub_id = blocks.subscription_id().unwrap();
@@ -171,7 +171,7 @@ async fn chainhead_unstable_call() {
let mut blocks = rpc.chainhead_unstable_follow(true).await.unwrap();
let event = blocks.next().await.unwrap().unwrap();
let hash = match event {
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
_ => panic!("Unexpected event"),
};
let sub_id = blocks.subscription_id().unwrap();
@@ -208,7 +208,7 @@ async fn chainhead_unstable_unpin() {
let mut blocks = rpc.chainhead_unstable_follow(true).await.unwrap();
let event = blocks.next().await.unwrap().unwrap();
let hash = match event {
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
_ => panic!("Unexpected event"),
};
let sub_id = blocks.subscription_id().unwrap();
@@ -414,7 +414,7 @@ async fn transaction_unstable_stop() {
.unwrap()
.expect("Server is not overloaded by 1 tx; qed");
let _ = rpc.transaction_unstable_stop(&operation_id).await.unwrap();
rpc.transaction_unstable_stop(&operation_id).await.unwrap();
// Cannot stop it twice.
let _err = rpc
.transaction_unstable_stop(&operation_id)
File diff suppressed because it is too large. Load diff
@@ -35,8 +35,11 @@ fn construct_offline_client() -> OfflineClient<PolkadotConfig> {
let bytes = hex::decode(h).unwrap();
H256::from_slice(&bytes)
};
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
let runtime_version = subxt::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
let metadata = {
let bytes = std::fs::read("../../../../artifacts/metadata_with_custom_values.scale").unwrap();
Metadata::decode(&mut &*bytes).unwrap()