mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-10-20 18:56:19 +00:00
lightclient: Add support for multi-chain usecase (#1238)
* lightclient: Make `smoldot::chainID` part of the RPC requests Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Make `BackgroundTask` generic over `PlatformRef` and chain Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Construct from raw smoldot and target different chains Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * testing: Update cargo lock for wasm tests Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Reuse `new_from_client` method and removed unused imports Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Reexport smoldot client and RPC objects used in pub interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Adjust `new_from_client` interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Extend background to poll over multiple RPC objects Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Build light client from raw and target different chains Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * artifacts: Add demo chain specs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * artifacts: Move artifacts to dedicated folder Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Use SelectAll to drive all streams Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Fetch initial data from the target chain Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Reexport other smoldot objects Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Target chain with potentially different config Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/rpc: Log chainID for debugging Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/examples: Add smoldot client with parachain example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Propagate chain ID together with rpc responses object Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Multiplex responses by request ID and chain ID Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add raw light client builder Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add cargo feature flag for parachains example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Derive default for internal structure Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Guard reexports by std feature flag Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update subxt/src/client/light_client/mod.rs Co-authored-by: James Wilson <james@jsdw.me> * lightclient: Update the builder pattern and chain targetting Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Fix documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Provide more insightful docs wrt native/wasm panics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Adjust comment location Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Refactor UniqueChainId into the background task Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update lightclient/src/background.rs Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com> * Update subxt/src/client/light_client/builder.rs Co-authored-by: James Wilson <james@jsdw.me> * lightclient: Update docs wrt panics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Update docs wrt to smoldot instance -> client Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Use IntoIter instead of Iterator Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Adjsut docs wrt [`Self::new_from_client`] Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Remove RawRpc from LightClient in favor of chainID Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Reexport everything under smoldot module Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * artifacts: Use stateRootHash instead of genesis.raw Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: James Wilson <james@jsdw.me> Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
11 files changed
+606
-104
No files matched your search
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "Polkadot",
|
||||
"id": "polkadot",
|
||||
"chainType": "Live",
|
||||
"bootNodes": [
|
||||
"/dns/polkadot-connect-0.parity.io/tcp/443/wss/p2p/12D3KooWEPmjoRpDSUuiTjvyNDd8fejZ9eNWH5bE965nyBMDrB4o",
|
||||
"/dns/polkadot-connect-1.parity.io/tcp/443/wss/p2p/12D3KooWLvcA24g6sT9YTaQyinwowMbLF5z7iMLoxZpEiV9pSmNf",
|
||||
"/dns/polkadot-connect-2.parity.io/tcp/443/wss/p2p/12D3KooWDhp18HYzJuVX2jLhtjQgAhT1XWGqah42StoUJpkLvh2o",
|
||||
"/dns/polkadot-connect-3.parity.io/tcp/443/wss/p2p/12D3KooWEsPEadSjLAPyxckqVJkp54aVdPuX3DD6a1FTL2y5cB9x",
|
||||
"/dns/polkadot-connect-4.parity.io/tcp/443/wss/p2p/12D3KooWFfG1SQvcPoUK2N41cx7r52KYXKpRtZxfLZk8xtVzpp4d",
|
||||
"/dns/polkadot-connect-5.parity.io/tcp/443/wss/p2p/12D3KooWDmQPkBvQGg9wjBdFThtWj3QCDVQyHJ1apfWrHvjwbYS8",
|
||||
"/dns/polkadot-connect-6.parity.io/tcp/443/wss/p2p/12D3KooWBKtPpCnVTTzD7fPpCdFsrsYZ5K8fwmsLabb1JBuCycYs",
|
||||
"/dns/polkadot-connect-7.parity.io/tcp/443/wss/p2p/12D3KooWP3BsFY6UaiLjEJ3YbDp6q6SMQgAHB15qKj41DUZQLMqD",
|
||||
"/dns/p2p.0.polkadot.network/tcp/30333/p2p/12D3KooWHsvEicXjWWraktbZ4MQBizuyADQtuEGr3NbDvtm5rFA5",
|
||||
"/dns/p2p.1.polkadot.network/tcp/30333/p2p/12D3KooWQz2q2UWVCiy9cFX1hHYEmhSKQB2hjEZCccScHLGUPjcc",
|
||||
"/dns/p2p.2.polkadot.network/tcp/30333/p2p/12D3KooWNHxjYbDLLbDNZ2tq1kXgif5MSiLTUWJKcDdedKu4KaG8",
|
||||
"/dns/p2p.3.polkadot.network/tcp/30333/p2p/12D3KooWGJQysxrQcSvUWWNw88RkqYvJhH3ZcDpWJ8zrXKhLP5Vr",
|
||||
"/dns/p2p.4.polkadot.network/tcp/30333/p2p/12D3KooWKer8bYqpYjwurVABu13mkELpX2X7mSpEicpjShLeg7D6",
|
||||
"/dns/p2p.5.polkadot.network/tcp/30333/p2p/12D3KooWSRjL9LcEQd5u2fQTbyLxTEHq1tUFgQ6amXSp8Eu7TfKP",
|
||||
"/dns/cc1-0.parity.tech/tcp/30333/p2p/12D3KooWSz8r2WyCdsfWHgPyvD8GKQdJ1UAiRmrcrs8sQB3fe2KU",
|
||||
"/dns/cc1-1.parity.tech/tcp/30333/p2p/12D3KooWFN2mhgpkJsDBuNuE5427AcDrsib8EoqGMZmkxWwx3Md4"
|
||||
],
|
||||
"telemetryEndpoints": [
|
||||
[
|
||||
"wss://telemetry.polkadot.io/submit/",
|
||||
0
|
||||
]
|
||||
],
|
||||
"properties": {
|
||||
"ss58Format": 0,
|
||||
"tokenDecimals": 10,
|
||||
"tokenSymbol": "DOT"
|
||||
},
|
||||
"forkBlocks": null,
|
||||
"badBlocks": null,
|
||||
"consensusEngine": null,
|
||||
"genesis": {
|
||||
"stateRootHash": "0x29d0d972cd27cbc511e9589fcb7a4506d5eb6a9e8df205f00472e5ab354a4e17"
|
||||
},
|
||||
"lightSyncState": {
|
||||
"babeEpochChanges": "0x048edc0df46d8dd7b4d2976d124920bf94ab2bdd9058f837f3a502586a70d22ddbd24ddc0001733eaa1000000000d347aa1000000000049a41a36f474af1af3046324e211e0edba2f8cc50390dff82976981bbd67245db2957dc0001d347aa10000000003351aa10000000000486ce7842747897a71c3dd8462432fe113713580f11146d55ee881318f910e3797f60dc00013351aa1000000000935aaa100000000000000c86ce7842747897a71c3dd8462432fe113713580f11146d55ee881318f910e3797f60dc00019f170000000000003351aa10000000006009000000000000a50466f60202b962c40e58fcf3481f5773dc178b9fe096f81511eaa4c7bad20e612001000000000000009c40155989f6072e82caba245d7db7e40a60f866b403257976b89aba6be2b55b0100000000000000ee3b07c54f0d93945caa98d8deea125865e36dee14644453fb07176d4605bb2c010000000000000072ab87b654d80294c876cbefb534b3344c2f64159b015b89221331ec2145417501000000000000008e43fb54c6d8074ba65ddc6c1b9c54a9c9f0508ec939d5b1746ad2bc42069c600100000000000000e2782e5b1d9962dcb6c006b797af79017be14346a6c2086ff132776ee7e74f53010000000000000020352ba0565811731625a8001d26f5d899e5856dedd0f9c6bc3bec2995704e5b01000000000000004c9886e2e62ee92a6778d6a7928052732044b64fa61969cd8e37f3f7e3406f72010000000000000012f7e35aedc710f8335cc813db106f2a569c6224505416e33c241e625993a86a0100000000000000dceaa05fb95e70e99b9fcfa547a67d067b1ec24b1e795bcd739ff3fdf1c2c9160100000000000000002e496e564f088a075525d2f9155aadd1a7eb017fa36f9e16096e9db7e1693301000000000000006a49a129620f4318c4a99b69c28cbe5f94c7578bbce50bf2627c61590f68df750100000000000000f6dddaaaa53b57763610037753656b591d90fa81efbadcabbeddf7378c23602701000000000000009601c5f103f77e1cb78d4e03f2585320142cbb4fd6c5c37f21edbbb2d343eb560100000000000000466c0e97d043a4b0f4c3ac4849e11f901e9b31b28f8b3e40b7cdb0ba7f77480801000000000000002049288b4dbfb741109778c602f9308daf2e31b3b66e9aa550065e73c6787d7c0100000000000000e67c524e443cda4cd27dfcce70488a5d23a93c8c46a5e83ef5a69eff3a02fa5a0100000000000000ca4fb6a27ee4ba734e8e8caac9ed440170a99efe98af430a14a3eaa4b0d03d600100000000000000400c83c89f541c02338cf113c5a841d810bf3bf17c5c6e78efbd637059005f7001000000000000002e85d3d2c07aaf1c0a5f63f544c34b87cfa90a98c4f6167e86cee0b74a20cb1201000000000000007c5f58a83f8101943c089dba3ab906c07811e853444aa58dbcc986c4095fbe7c01000000000000005494142902284943201b7cf610306e8665eeeec87c014d607175c3f50f76103b01000000000000001822493200c35242a149070784dca5e7012a8a52b61cd218ec7d65e469e8cc560100000000000000dcf6d4dda860d902eb4acb8adff1116c637c12eb9d0633caa8fd0b65a30c8105010000000000000042e377034ce6b2a582bfd66c465252ab10ce790e4f0d3bbe99a42ff86b29965c010000000000000052e281ebf2cb6e1751a844d92574869e5a06e596bc41404de1fe55e99980dc6901000000000000004e2753547f6ec0a896bcbc55b9eeab875e8019acdeb10b979210af63f24c4b740100000000000000822467882ab8bd2cd75152843a948b3e56ae1da6aac9b66ac4cba1448217d8410100000000000000f63e0c859002c868d1e8bf7b36558b7b3f4a033f13850862ad643bc99628f335010000000000000090fd0815e122cc480aaa256b319405ab08f867d414dd59736bc81b7f054c59700100000000000000046c86bcf2260b9e862c5867def0e1a528c32e64ce0cb20d41bba00f30013a690100000000000000d416213c0b30df8f5aadf00642a4777dcea76b12836c51a98abcd133c78899470100000000000000ae831c0a4d22eeff12f679540c229029e321499477451768b8999b1191d6164801000000000000006ec27dfa7ac9d35eb002c55d3e4d56b4df928dd5c44323ac228305741c9e09360100000000000000b89cbc83a386d6f5d4fed93289ee50fdf69d77369ca0c9a8b962554e3fc0d37e0100000000000000e61da4eefdd14434952c58dd1943f7f63185d03321ccc8835e4216badd16fc19010000000000000078f66da37371586d480a8c4dcb2fc772c7c3c185961b5757169cb3b72c12436b0100000000000000a20797f3a8669a909adb22a3aee2844dba82d231cc33f5d9be0e2c533cb82a670100000000000000e297620b731b4958e3c6aa06611c6aa5a45f7e116362d64954a0a330d2a98f33010000000000000088c2166cef8e248c6dc7f801967f6c936b21ecff6f061035d9f95cbaabbcbb0f01000000000000005a7a6d3951924738f27fec8c0baa71a0f518a2d56d8289a1887d3d6805088c220100000000000000049e89e200ca2e92ee67afb644b216f3e99c8b4eaf26c1f0e42038850d0c617901000000000000004077191c8d78ed00c81b74788215db40d498f37252f9966f57e96c9d3f6af31701000000000000000ef6f09a9e310abb5c57dd7050a94d56d766772b80789b997cbb40f3b40f625d0100000000000000b25892ec76bcc88536409e440cb14f9de9589c4efe2b69608fff594885a2fe6501000000000000007cc03ffbfb187c1ea32cf4d25893238aae9073b2016084052a6defac64a7e36c01000000000000003af71b42b712323aa28fd7a2099c4f8b8071e3ed27c8a52ff2cdff5d1a5eb66b0100000000000000a88864a0dc9e292aa7cc8eddaa8dd8b6be2a4bc7e8f290023c6604e5c4ad1a7a0100000000000000dc68569b46a09aebabe8fc4452e293fac635e4a2889739809b32e4e2303e3f71010000000000000034ffee048e511f4187a6f40339650ec60a3d99ac5a1158ae162c5729b59d51740100000000000000be7c58318c49ff328d53a6d67830a8a046a41f5124824d3ce2e72cc9965ef57e0100000000000000a693f218033b3eb885bd2c7793227834d1ff3350c296e49aef3d8d801d5719750100000000000000b06e3ee1ea6420fb73ebd502988420f51b977a83074e1aa5daf0121f4a8b6b1e01000000000000003843abe933fa3ce63f3a5e422e13ec05dc9c0e8f150b93a0fa97f1b193cfba2d01000000000000009cdd57a06bd22d104d539527a19033620becff73dd267f0c2f9f13bc37b0d32c0100000000000000e020b5e946e4c6b0603ceac71b44c330fa7d9cb3e79791ebe1e16bedbb9c71000100000000000000a86c7bdf11794e554439b09f9f294c8e0aLine truncated
|
||||
"babeFinalizedBlockWeight": 3605523,
|
||||
"finalizedBlockHeader": "0x78835356007a3483eb5f099af463786e062e8513d025f5e8b326f2f05e8d040ed685710377e7b4ebd68adb7bbe2d509bf61eafb18fce581eb6a0411d161ceba9cc5d4c621806d6ca96d5ccabe0ec3cd1f047ee61015eb765fbc4ed735aaac976f243fa76080642414245b5010311000000cc48aa10000000004a79e7001b31b145320daed393d6e6f9a619bab04c27742be4477952fb46fc4c1b17fe2fa71fec279dcfbb9f234bfea251d8f715f76db1a0562bbbec80d43006ada682bb30d431003e91fba8dfb656396159aa4251a779b3f72c22c18bd4f501054241424501013cce6589582512ce47682b3b5365d7474caf6a6d75bfeb0dd96fe9d0b44aea308b1d8ffd8ce1ddc2f08a974382a154dea572ca87d333c99c5703c2ef1c7bcd8a",
|
||||
"grandpaAuthoritySet": "0xa5043866d53e02db8ff21ae934a03e06375ba5f3588bd444902794dde526fac2cae00100000000000000fff437ff18629bf1490e5c9b3ec6f1515d46bb9b2aeaa6e39e36611f2479b50d01000000000000006719dee0d6e25f2aabec94019e598524b205b514c1582e72fb9f1be3555300a5010000000000000047f940bc985355be7187c709ded2b689b66d69e61e293a507f1d4f90bdbeceee0100000000000000525c0460e98f5cda7d20e67ea1e27b3ca471760559baa73dcf77b2e163b957180100000000000000f98b21ab797ac19b101f8e446865564901dff29b4e0bbc5badcae7ac88dab7d80100000000000000b61e9b13e849779be8f5378a670d61991875d8e091f359384d7be73d02d50693010000000000000070ba6e5985990ba1b1392713236f1b50df750f10f744cc6eb95fa7c5cfbc687601000000000000005081c119ae99cfaacd677ac4eed05ce7d0d3f9af9e1caa58194564bedff73a480100000000000000b54a0bdc05bf5634a385ad79469726bd507dce50ff5e2e5f6132868fe9ced6af010000000000000057ae0c85ebaf333a6ec3251f577cab910cce072f238d1e50046322b83bbc0dd50100000000000000bdae4bc60b03f8fbc14299f0449301ef7f423a4cd15c6f6a35ea6a44fc5bcb0f01000000000000000139b946b46bee4c61be50c986f2a9ef2e76917644e2ecd842c59d61a94ee612010000000000000076aa25ab781816fdfabc9051149c3db958bc09d2e8f0e10a4d91e0df8c79ec9b010000000000000053352e16d2a27fb3525d5d8750d6ad7f46b303a45e434a409ac727d5cb80432b01000000000000008c54298c6f8704498112ecb24f64f7a28ed2289ec573620b0d1e5a9a44f5fa78010000000000000041ef4a31eb7dc1e01f4630604e1908e644d7cdee3f66a60f98d6d59605326f8b0100000000000000c4ab2451500e321f187140d3aeed09d5cdf67f09d9997b966c86dd74714233db010000000000000021acf715d75065242920f902a3490f2f9f3dbf5bae57a4a6fbc8ed3f805eb8620100000000000000049cb09a86d544249f9e11cf6c348bc0561600343ebee89d1c90bbef3d13e5170100000000000000d04d64a07f144638f20f081de122b88fed0cc8bda57e4810e7afb38036dd8bbf0100000000000000f26945a8a64032a1defa76e720a99649125b55751b6088205e7acab901de670b0100000000000000684768723f0efc2001562c3b2156b987601ae9431f8aaa6f4199649cba487aea01000000000000005ee4dbc4f6c4f07171eda6db4f331180ad4434b34f809071e958910df3252c8c01000000000000009b94bc45bf79bb33f70430c82880456cd7bd5fb3ea131c1b26d493d01c5fe7100100000000000000d7e8ce7ff46c836473acb0c3016feab586b5c4a6b529f7cedef01c6727dbbcf601000000000000006f4855725ebb9eced957bae43ffa7d30657ce7d06b251ce20e2218bcf929e4050100000000000000f2f0f84855c6bce35c42d537fb4022662272203cc4985767ad4ef8ef1bbb716501000000000000008376e5e1a7b6eac21e3e9f9e133f89ed0053b5559376a97e58896653725c502d0100000000000000ff0bf39c82ed573d48585448b0fc19ba3f6203806cb6b4a230848892c097a26f01000000000000009c27510363c46fb84c22765901302fa26af266da5a22099368fce05562ce97eb01000000000000003478e6a5b64e3aaaa6795e6536a12728a20af7a8d44c75f5e981c6d2aee4517701000000000000003b252801bf8a2f4797699a0d95e8fad8c8fb42cf6f6a33f4555bde1c7fefd4490100000000000000c1c1ff5f66fefb5c0849e3b2333411d1f7e9dd4888600e334e7a33623f15fa360100000000000000fe240e80ff8a48a77947b394979cf361017cd906ddaab637d72cf72e729d79bf010000000000000006854c73f5703242d31d2576ac2297c93f0b5dc16ade0097c59d5dc0faa43c5501000000000000004e8f0524e30ac39910b99d0c465e945580c48070ea75214f7c01a920b1a66e4901000000000000008aabd4f6d7ecec7a749c83fc7be33f3b4818cca6f93a4927b4964900df9454e50100000000000000b9186b95c90d2d00a31e7c68066bd37d73408271762604e3608e2f2c983f83090100000000000000f5260c9ada18534d79be08836484429e686985c8241e113307792aa1255b1cb10100000000000000e730007e26ea493e96a65b30d278e92ef85444e61fee3606729a5e699b74f1100100000000000000d4d8cfaa23b3a1a89a7f6a90cdbfdea15c87ad2ca4ae77deb3389406ad3feffe01000000000000002db20e745d850245733e6f6aa93ea9a176044e904160e59995846aac21f1ff610100000000000000dc1427d14e83926d22f6be25d1bd39cbc82a194f5e9373ecae644f156ae9c62e01000000000000005cc3bf681735b048c6d32066de94abae0515d8f401583bf9bb103b4e07ec6e700100000000000000beab86851a829fb4f25d1c1a5d92b4cd97718fd0f2f9ef345b0d3cab98a304aa01000000000000000339088379600f5bd507277eb894aba34d2a8f27ce1964f41e60ef2a4142dc6e010000000000000003d105a30087d96b5f0684f6ded76f826b01dab61e4136e1d851a24f0088b5ed010000000000000020d4e595c50bb9558dad9be6a8784492bbbfc754b9c5fae17edf4f8a84e8b47101000000000000006ba031ff146af5199eeb335a4a9c2fd487685a489e1a154ba1d012e965fdd5ba0100000000000000608f61824d3ae16d443cd0fec15c18590c890d4105e9402cad8743fb423e52b40100000000000000fe95aa161a01b3473ed9dc7f4e2219528d8be86530bcfa7095086ed61e3d7a0301000000000000000ccee96768f9b1d29d5bd9e63afd2be78a90cc89d19653ff1910cb36000b48530100000000000000818a546c630b881c2161588866965649678cda0f4110cccc3533d0f20e5e41200100000000000000088f8736e1cf2ea3d102f0a96ccf51222b8aa1f93d8e42947892f4395cb5477c0100000000000000c3ff25a1743a9df92af4ccd9a7aed5cbc90f64fd538c3df0a9539128f596526701000000000000004e3fb8c16ad624c1852777456805e5352d0221269de66949c95da4076206b35a01000000000000007ad55381f9de00220678c95a19489640fc61331e2a40d927e3d833555575c6fc0100000000000000cb92ffeace78dbafe6fbf275741b4b38657fb81590712aa0bca7877931f6ad39010000000000000074a8906dd2888c9e7edea13ba3c42ad4d833f4a5de43d5ce0d9b12c654ab2e8701000000000000005cd0c7b21d4834c5a41a80f7d421c7d0297e42eb409a524d33aa7557df13adaa01000000000000007ac1f611f90df5fb222924b2c6683062db933d6083922c4a17ce29f21298a7680100000000000000077Line truncated
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "Polkadot Asset Hub",
|
||||
"id": "asset-hub-polkadot",
|
||||
"chainType": "Live",
|
||||
"bootNodes": [
|
||||
"/ip4/34.65.251.121/tcp/30334/p2p/12D3KooWG3GrM6XKMM4gp3cvemdwUvu96ziYoJmqmetLZBXE8bSa",
|
||||
"/ip4/34.65.35.228/tcp/30334/p2p/12D3KooWMRyTLrCEPcAQD6c4EnudL3vVzg9zji3whvsMYPUYevpq",
|
||||
"/dns/polkadot-asset-hub-connect-0.polkadot.io/tcp/30334/p2p/12D3KooWLHqbcQtoBygf7GJgVjVa3TaeLuf7VbicNdooaCmQM2JZ",
|
||||
"/dns/polkadot-asset-hub-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWLHqbcQtoBygf7GJgVjVa3TaeLuf7VbicNdooaCmQM2JZ",
|
||||
"/dns/polkadot-asset-hub-connect-1.polkadot.io/tcp/30334/p2p/12D3KooWNDrKSayoZXGGE2dRSFW2g1iGPq3fTZE2U39ma9yZGKd3",
|
||||
"/dns/polkadot-asset-hub-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWNDrKSayoZXGGE2dRSFW2g1iGPq3fTZE2U39ma9yZGKd3",
|
||||
"/dns/polkadot-asset-hub-connect-2.polkadot.io/tcp/30334/p2p/12D3KooWApa2JW4rbLtgzuK7fjLMupLS9HZheX9cdkQKyu6AnGrP",
|
||||
"/dns/polkadot-asset-hub-connect-2.polkadot.io/tcp/443/wss/p2p/12D3KooWApa2JW4rbLtgzuK7fjLMupLS9HZheX9cdkQKyu6AnGrP",
|
||||
"/dns/polkadot-asset-hub-connect-3.polkadot.io/tcp/30334/p2p/12D3KooWRsVeHqRs2iKmjLiguxp8myL4G2mDAWhtX2jHwyWujseV",
|
||||
"/dns/polkadot-asset-hub-connect-3.polkadot.io/tcp/443/wss/p2p/12D3KooWRsVeHqRs2iKmjLiguxp8myL4G2mDAWhtX2jHwyWujseV",
|
||||
"/dns/boot.stake.plus/tcp/35333/p2p/12D3KooWFrQjYaPZSSLLxEVmoaHFcrF6VoY4awG4KRSLaqy3JCdQ",
|
||||
"/dns/boot.stake.plus/tcp/35334/wss/p2p/12D3KooWFrQjYaPZSSLLxEVmoaHFcrF6VoY4awG4KRSLaqy3JCdQ",
|
||||
"/dns/boot.metaspan.io/tcp/16052/p2p/12D3KooWLwiJuvqQUB4kYaSjLenFKH9dWZhGZ4qi7pSb3sUYU651",
|
||||
"/dns/boot.metaspan.io/tcp/16056/wss/p2p/12D3KooWLwiJuvqQUB4kYaSjLenFKH9dWZhGZ4qi7pSb3sUYU651",
|
||||
"/dns/boot-cr.gatotech.network/tcp/33110/p2p/12D3KooWKgwQfAeDoJARdtxFNNWfbYmcu6s4yUuSifnNoDgzHZgm",
|
||||
"/dns/boot-cr.gatotech.network/tcp/35110/wss/p2p/12D3KooWKgwQfAeDoJARdtxFNNWfbYmcu6s4yUuSifnNoDgzHZgm",
|
||||
"/dns/statemint-bootnode.turboflakes.io/tcp/30315/p2p/12D3KooWL8CyLww3m3pRySQGGYGNJhWDMqko3j5xi67ckP7hDUvo",
|
||||
"/dns/statemint-bootnode.turboflakes.io/tcp/30415/wss/p2p/12D3KooWL8CyLww3m3pRySQGGYGNJhWDMqko3j5xi67ckP7hDUvo",
|
||||
"/dns/boot-node.helikon.io/tcp/10220/p2p/12D3KooW9uybhguhDjVJc3U3kgZC3i8rWmAnSpbnJkmuR7C6ZsRW",
|
||||
"/dns/boot-node.helikon.io/tcp/10222/wss/p2p/12D3KooW9uybhguhDjVJc3U3kgZC3i8rWmAnSpbnJkmuR7C6ZsRW",
|
||||
"/dns/statemint.bootnode.amforc.com/tcp/30341/p2p/12D3KooWByohP9FXn7ao8syS167qJsbFdpa7fY2Y24xbKtt3r7Ls",
|
||||
"/dns/statemint.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWByohP9FXn7ao8syS167qJsbFdpa7fY2Y24xbKtt3r7Ls",
|
||||
"/dns/statemint-boot-ng.dwellir.com/tcp/30344/p2p/12D3KooWEFrNuNk8fPdQS2hf34Gmqi6dGSvrETshGJUrqrvfRDZr",
|
||||
"/dns/statemint-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWEFrNuNk8fPdQS2hf34Gmqi6dGSvrETshGJUrqrvfRDZr"
|
||||
],
|
||||
"telemetryEndpoints": null,
|
||||
"protocolId": null,
|
||||
"properties": {
|
||||
"ss58Format": 0,
|
||||
"tokenDecimals": 10,
|
||||
"tokenSymbol": "DOT"
|
||||
},
|
||||
"relay_chain": "polkadot",
|
||||
"para_id": 1000,
|
||||
"consensusEngine": null,
|
||||
"codeSubstitutes": {},
|
||||
"genesis": {
|
||||
"stateRootHash": "0xc1ef26b567de07159e4ecd415fbbb0340c56a09c4d72c82516d0f3bc2b782c80"
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,16 @@ use futures::stream::StreamExt;
|
||||
use futures_util::future::{self, Either};
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::RawValue;
|
||||
use smoldot_light::platform::PlatformRef;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use super::platform::PlatformType;
|
||||
use crate::client::AddedChain;
|
||||
|
||||
use super::LightClientRpcError;
|
||||
use smoldot_light::ChainId;
|
||||
|
||||
const LOG_TARGET: &str = "light-client-background";
|
||||
const LOG_TARGET: &str = "subxt-light-client-background";
|
||||
|
||||
/// The response of an RPC method.
|
||||
pub type MethodResponse = Result<Box<RawValue>, LightClientRpcError>;
|
||||
@@ -34,6 +36,8 @@ pub enum FromSubxt {
|
||||
params: String,
|
||||
/// Channel used to send back the result.
|
||||
sender: oneshot::Sender<MethodResponse>,
|
||||
/// The ID of the chain used to identify the chain.
|
||||
chain_id: ChainId,
|
||||
},
|
||||
/// The RPC subscription (pub/sub) request.
|
||||
Subscription {
|
||||
@@ -43,24 +47,21 @@ pub enum FromSubxt {
|
||||
params: String,
|
||||
/// Channel used to send back the subscription ID if successful.
|
||||
sub_id: oneshot::Sender<MethodResponse>,
|
||||
/// Channel used to send back the notifcations.
|
||||
/// Channel used to send back the notifications.
|
||||
sender: mpsc::UnboundedSender<Box<RawValue>>,
|
||||
/// The ID of the chain used to identify the chain.
|
||||
chain_id: ChainId,
|
||||
},
|
||||
}
|
||||
|
||||
/// Background task data.
|
||||
pub struct BackgroundTask {
|
||||
pub struct BackgroundTask<TPlatform: PlatformRef, TChain> {
|
||||
/// Smoldot light client implementation that leverages the exposed platform.
|
||||
client: smoldot_light::Client<PlatformType>,
|
||||
/// The ID of the chain used to identify the chain protocol (ie. substrate).
|
||||
///
|
||||
/// Note: A single chain is supported for a client. This aligns with the subxt's
|
||||
/// vision of the Client.
|
||||
chain_id: ChainId,
|
||||
/// Unique ID for RPC calls.
|
||||
request_id: usize,
|
||||
client: smoldot_light::Client<TPlatform, TChain>,
|
||||
/// Generates an unique monotonically increasing ID for each chain.
|
||||
request_id_per_chain: HashMap<smoldot_light::ChainId, usize>,
|
||||
/// Map the request ID of a RPC method to the frontend `Sender`.
|
||||
requests: HashMap<usize, oneshot::Sender<MethodResponse>>,
|
||||
requests: HashMap<(usize, smoldot_light::ChainId), oneshot::Sender<MethodResponse>>,
|
||||
/// Subscription calls first need to make a plain RPC method
|
||||
/// request to obtain the subscription ID.
|
||||
///
|
||||
@@ -68,23 +69,27 @@ pub struct BackgroundTask {
|
||||
/// not be sent back to the user.
|
||||
/// Map the request ID of a RPC method to the frontend `Sender`.
|
||||
id_to_subscription: HashMap<
|
||||
usize,
|
||||
(usize, smoldot_light::ChainId),
|
||||
(
|
||||
oneshot::Sender<MethodResponse>,
|
||||
mpsc::UnboundedSender<Box<RawValue>>,
|
||||
),
|
||||
>,
|
||||
/// Map the subscription ID to the frontend `Sender`.
|
||||
subscriptions: HashMap<usize, mpsc::UnboundedSender<Box<RawValue>>>,
|
||||
///
|
||||
/// The subscription ID is entirely generated by the node (smoldot). Therefore, it is
|
||||
/// possible for two distinct subscriptions of different chains to have the same subscription ID.
|
||||
subscriptions: HashMap<(usize, smoldot_light::ChainId), mpsc::UnboundedSender<Box<RawValue>>>,
|
||||
}
|
||||
|
||||
impl BackgroundTask {
|
||||
impl<TPlatform: PlatformRef, TChain> BackgroundTask<TPlatform, TChain> {
|
||||
/// Constructs a new [`BackgroundTask`].
|
||||
pub fn new(client: smoldot_light::Client<PlatformType>, chain_id: ChainId) -> BackgroundTask {
|
||||
pub fn new(
|
||||
client: smoldot_light::Client<TPlatform, TChain>,
|
||||
) -> BackgroundTask<TPlatform, TChain> {
|
||||
BackgroundTask {
|
||||
client,
|
||||
chain_id,
|
||||
request_id: 1,
|
||||
request_id_per_chain: Default::default(),
|
||||
requests: Default::default(),
|
||||
id_to_subscription: Default::default(),
|
||||
subscriptions: Default::default(),
|
||||
@@ -92,10 +97,11 @@ impl BackgroundTask {
|
||||
}
|
||||
|
||||
/// Fetch and increment the request ID.
|
||||
fn next_id(&mut self) -> usize {
|
||||
let next = self.request_id;
|
||||
self.request_id = self.request_id.wrapping_add(1);
|
||||
next
|
||||
fn next_id(&mut self, chain_id: smoldot_light::ChainId) -> usize {
|
||||
let next = self.request_id_per_chain.entry(chain_id).or_insert(1);
|
||||
let id = *next;
|
||||
*next = next.wrapping_add(1);
|
||||
id
|
||||
}
|
||||
|
||||
/// Handle the registration messages received from the user.
|
||||
@@ -105,27 +111,28 @@ impl BackgroundTask {
|
||||
method,
|
||||
params,
|
||||
sender,
|
||||
chain_id,
|
||||
} => {
|
||||
let id = self.next_id();
|
||||
let id = self.next_id(chain_id);
|
||||
let request = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":"{}", "method":"{}","params":{}}}"#,
|
||||
id, method, params
|
||||
);
|
||||
|
||||
self.requests.insert(id, sender);
|
||||
self.requests.insert((id, chain_id), sender);
|
||||
tracing::trace!(target: LOG_TARGET, "Tracking request id={id} chain={chain_id:?}");
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, "Generated unique id={id} for request={request}");
|
||||
|
||||
let result = self.client.json_rpc_request(request, self.chain_id);
|
||||
let result = self.client.json_rpc_request(request, chain_id);
|
||||
if let Err(err) = result {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send RPC request to lightclient {:?}",
|
||||
err.to_string()
|
||||
);
|
||||
|
||||
let sender = self
|
||||
.requests
|
||||
.remove(&id)
|
||||
.remove(&(id, chain_id))
|
||||
.expect("Channel is inserted above; qed");
|
||||
|
||||
// Send the error back to frontend.
|
||||
@@ -147,20 +154,21 @@ impl BackgroundTask {
|
||||
params,
|
||||
sub_id,
|
||||
sender,
|
||||
chain_id,
|
||||
} => {
|
||||
// For subscriptions we need to make a plain RPC request to the subscription method.
|
||||
// The server will return as a result the subscription ID.
|
||||
let id = self.next_id();
|
||||
let id = self.next_id(chain_id);
|
||||
let request = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":"{}", "method":"{}","params":{}}}"#,
|
||||
id, method, params
|
||||
);
|
||||
|
||||
self.id_to_subscription.insert(id, (sub_id, sender));
|
||||
tracing::trace!(target: LOG_TARGET, "Tracking subscription request id={id} chain={chain_id:?}");
|
||||
self.id_to_subscription
|
||||
.insert((id, chain_id), (sub_id, sender));
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, "Generated unique id={id} for subscription request={request}");
|
||||
|
||||
let result = self.client.json_rpc_request(request, self.chain_id);
|
||||
let result = self.client.json_rpc_request(request, chain_id);
|
||||
if let Err(err) = result {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -169,7 +177,7 @@ impl BackgroundTask {
|
||||
);
|
||||
let (sub_id, _) = self
|
||||
.id_to_subscription
|
||||
.remove(&id)
|
||||
.remove(&(id, chain_id))
|
||||
.expect("Channels are inserted above; qed");
|
||||
|
||||
// Send the error back to frontend.
|
||||
@@ -190,54 +198,57 @@ impl BackgroundTask {
|
||||
}
|
||||
|
||||
/// Parse the response received from the light client and sent it to the appropriate user.
|
||||
fn handle_rpc_response(&mut self, response: String) {
|
||||
tracing::trace!(target: LOG_TARGET, "Received from smoldot response={:?}", response);
|
||||
fn handle_rpc_response(&mut self, chain_id: smoldot_light::ChainId, response: String) {
|
||||
tracing::trace!(target: LOG_TARGET, "Received from smoldot response={response} chain={chain_id:?}");
|
||||
|
||||
match RpcResponse::from_str(&response) {
|
||||
Ok(RpcResponse::Error { id, error }) => {
|
||||
let Ok(id) = id.parse::<usize>() else {
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send error. Id={id} is not a valid number");
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send error. Id={id} chain={chain_id:?} is not a valid number");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(sender) = self.requests.remove(&id) {
|
||||
if let Some(sender) = self.requests.remove(&(id, chain_id)) {
|
||||
if sender
|
||||
.send(Err(LightClientRpcError::Request(error.to_string())))
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send method response to id={id}",
|
||||
"Cannot send method response to id={id} chain={chain_id:?}",
|
||||
);
|
||||
}
|
||||
} else if let Some((sub_id_sender, _)) = self.id_to_subscription.remove(&id) {
|
||||
} else if let Some((sub_id_sender, _)) =
|
||||
self.id_to_subscription.remove(&(id, chain_id))
|
||||
{
|
||||
if sub_id_sender
|
||||
.send(Err(LightClientRpcError::Request(error.to_string())))
|
||||
.is_err()
|
||||
{
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send method response to id {:?}",
|
||||
id
|
||||
"Cannot send method response to id {id} chain={chain_id:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(RpcResponse::Method { id, result }) => {
|
||||
let Ok(id) = id.parse::<usize>() else {
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send response. Id={id} is not a valid number");
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send response. Id={id} chain={chain_id:?} is not a valid number");
|
||||
return;
|
||||
};
|
||||
|
||||
// Send the response back.
|
||||
if let Some(sender) = self.requests.remove(&id) {
|
||||
if let Some(sender) = self.requests.remove(&(id, chain_id)) {
|
||||
if sender.send(Ok(result)).is_err() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send method response to id={id}",
|
||||
"Cannot send method response to id={id} chain={chain_id:?}",
|
||||
);
|
||||
}
|
||||
} else if let Some((sub_id_sender, sender)) = self.id_to_subscription.remove(&id) {
|
||||
} else if let Some((sub_id_sender, sender)) =
|
||||
self.id_to_subscription.remove(&(id, chain_id))
|
||||
{
|
||||
let Ok(sub_id) = result
|
||||
.get()
|
||||
.trim_start_matches('"')
|
||||
@@ -246,41 +257,51 @@ impl BackgroundTask {
|
||||
else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Subscription id={result} is not a valid number",
|
||||
"Subscription id={result} chain={chain_id:?} is not a valid number",
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, "Received subscription id={sub_id}");
|
||||
tracing::trace!(target: LOG_TARGET, "Received subscription id={sub_id} chain={chain_id:?}");
|
||||
|
||||
if sub_id_sender.send(Ok(result)).is_err() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send method response to id={id}",
|
||||
"Cannot send method response to id={id} chain={chain_id:?}",
|
||||
);
|
||||
} else {
|
||||
// Track this subscription ID if send is successful.
|
||||
self.subscriptions.insert(sub_id, sender);
|
||||
self.subscriptions.insert((sub_id, chain_id), sender);
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Response id={id} chain={chain_id:?} is not tracked",
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(RpcResponse::Subscription { method, id, result }) => {
|
||||
let Ok(id) = id.parse::<usize>() else {
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send subscription. Id={id} is not a valid number");
|
||||
tracing::warn!(target: LOG_TARGET, "Cannot send subscription. Id={id} chain={chain_id:?} is not a valid number");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(sender) = self.subscriptions.get_mut(&id) {
|
||||
if let Some(sender) = self.subscriptions.get_mut(&(id, chain_id)) {
|
||||
// Send the current notification response.
|
||||
if sender.send(result).is_err() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Cannot send notification to subscription id={id} method={method}",
|
||||
"Cannot send notification to subscription id={id} chain={chain_id:?} method={method}",
|
||||
);
|
||||
|
||||
// Remove the sender if the subscription dropped the receiver.
|
||||
self.subscriptions.remove(&id);
|
||||
self.subscriptions.remove(&(id, chain_id));
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Subscription response id={id} chain={chain_id:?} is not tracked",
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -295,17 +316,22 @@ impl BackgroundTask {
|
||||
pub async fn start_task(
|
||||
&mut self,
|
||||
from_subxt: mpsc::UnboundedReceiver<FromSubxt>,
|
||||
from_node: smoldot_light::JsonRpcResponses,
|
||||
from_node: Vec<AddedChain>,
|
||||
) {
|
||||
let from_subxt_event = tokio_stream::wrappers::UnboundedReceiverStream::new(from_subxt);
|
||||
let from_node_event = futures_util::stream::unfold(from_node, |mut from_node| async {
|
||||
from_node.next().await.map(|result| (result, from_node))
|
||||
});
|
||||
|
||||
tokio::pin!(from_subxt_event, from_node_event);
|
||||
let from_node = from_node.into_iter().map(|rpc| {
|
||||
Box::pin(futures::stream::unfold(rpc, |mut rpc| async move {
|
||||
let response = rpc.rpc_responses.next().await;
|
||||
Some(((response, rpc.chain_id), rpc))
|
||||
}))
|
||||
});
|
||||
let stream_combinator = futures::stream::select_all(from_node);
|
||||
|
||||
tokio::pin!(from_subxt_event, stream_combinator);
|
||||
|
||||
let mut from_subxt_event_fut = from_subxt_event.next();
|
||||
let mut from_node_event_fut = from_node_event.next();
|
||||
let mut from_node_event_fut = stream_combinator.next();
|
||||
|
||||
loop {
|
||||
match future::select(from_subxt_event_fut, from_node_event_fut).await {
|
||||
@@ -328,6 +354,10 @@ impl BackgroundTask {
|
||||
}
|
||||
// Message received from rpc handler: lightclient response.
|
||||
Either::Right((node_message, previous_fut)) => {
|
||||
let Some((node_message, chain)) = node_message else {
|
||||
tracing::trace!(target: LOG_TARGET, "Smoldot closed all RPC channels");
|
||||
break;
|
||||
};
|
||||
// Smoldot returns `None` if the chain has been removed (which subxt does not remove).
|
||||
let Some(response) = node_message else {
|
||||
tracing::trace!(target: LOG_TARGET, "Smoldot RPC responses channel closed");
|
||||
@@ -335,15 +365,15 @@ impl BackgroundTask {
|
||||
};
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Received smoldot RPC result {:?}",
|
||||
response
|
||||
"Received smoldot RPC chain {:?} result {:?}",
|
||||
chain, response
|
||||
);
|
||||
|
||||
self.handle_rpc_response(response);
|
||||
self.handle_rpc_response(chain, response);
|
||||
|
||||
// Advance backend, save frontend.
|
||||
from_subxt_event_fut = previous_fut;
|
||||
from_node_event_fut = from_node_event.next();
|
||||
from_node_event_fut = stream_combinator.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
-9
@@ -2,6 +2,8 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use super::{
|
||||
background::{BackgroundTask, FromSubxt, MethodResponse},
|
||||
LightClientRpcError,
|
||||
@@ -11,7 +13,29 @@ use tokio::sync::{mpsc, mpsc::error::SendError, oneshot};
|
||||
|
||||
use super::platform::build_platform;
|
||||
|
||||
pub const LOG_TARGET: &str = "light-client";
|
||||
pub const LOG_TARGET: &str = "subxt-light-client";
|
||||
|
||||
/// A raw light-client RPC implementation that can connect to multiple chains.
|
||||
#[derive(Clone)]
|
||||
pub struct RawLightClientRpc {
|
||||
/// Communicate with the backend task that multiplexes the responses
|
||||
/// back to the frontend.
|
||||
to_backend: mpsc::UnboundedSender<FromSubxt>,
|
||||
}
|
||||
|
||||
impl RawLightClientRpc {
|
||||
/// Construct a [`LightClientRpc`] that can communicated with the provided chain.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This uses the same underlying instance created by [`LightClientRpc::new_from_client`].
|
||||
pub fn for_chain(&self, chain_id: smoldot_light::ChainId) -> LightClientRpc {
|
||||
LightClientRpc {
|
||||
to_backend: self.to_backend.clone(),
|
||||
chain_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
@@ -19,6 +43,8 @@ pub struct LightClientRpc {
|
||||
/// Communicate with the backend task that multiplexes the responses
|
||||
/// back to the frontend.
|
||||
to_backend: mpsc::UnboundedSender<FromSubxt>,
|
||||
/// The chain ID to target for requests.
|
||||
chain_id: smoldot_light::ChainId,
|
||||
}
|
||||
|
||||
impl LightClientRpc {
|
||||
@@ -31,14 +57,35 @@ impl LightClientRpc {
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if being called outside of `tokio` runtime context.
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
pub fn new(
|
||||
config: smoldot_light::AddChainConfig<'_, (), impl Iterator<Item = smoldot_light::ChainId>>,
|
||||
config: smoldot_light::AddChainConfig<
|
||||
'_,
|
||||
(),
|
||||
impl IntoIterator<Item = smoldot_light::ChainId>,
|
||||
>,
|
||||
) -> Result<LightClientRpc, LightClientRpcError> {
|
||||
tracing::trace!(target: LOG_TARGET, "Create light client");
|
||||
|
||||
let mut client = smoldot_light::Client::new(build_platform());
|
||||
|
||||
let config = smoldot_light::AddChainConfig {
|
||||
specification: config.specification,
|
||||
json_rpc: config.json_rpc,
|
||||
database_content: config.database_content,
|
||||
potential_relay_chains: config.potential_relay_chains.into_iter(),
|
||||
user_data: config.user_data,
|
||||
};
|
||||
|
||||
let smoldot_light::AddChainSuccess {
|
||||
chain_id,
|
||||
json_rpc_responses,
|
||||
@@ -46,14 +93,48 @@ impl LightClientRpc {
|
||||
.add_chain(config)
|
||||
.map_err(|err| LightClientRpcError::AddChainError(err.to_string()))?;
|
||||
|
||||
let (to_backend, backend) = mpsc::unbounded_channel();
|
||||
|
||||
// `json_rpc_responses` can only be `None` if we had passed `json_rpc: Disabled`.
|
||||
let rpc_responses = json_rpc_responses.expect("Light client RPC configured; qed");
|
||||
|
||||
let raw_client = Self::new_from_client(
|
||||
client,
|
||||
iter::once(AddedChain {
|
||||
chain_id,
|
||||
rpc_responses,
|
||||
}),
|
||||
);
|
||||
Ok(raw_client.for_chain(chain_id))
|
||||
}
|
||||
|
||||
/// Constructs a new [`RawLightClientRpc`] from the raw smoldot client.
|
||||
///
|
||||
/// Receives a list of RPC objects as a result of calling `smoldot_light::Client::add_chain`.
|
||||
/// This [`RawLightClientRpc`] can target different chains using [`RawLightClientRpc::for_chain`] method.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
pub fn new_from_client<TPlat>(
|
||||
client: smoldot_light::Client<TPlat>,
|
||||
chains: impl IntoIterator<Item = AddedChain>,
|
||||
) -> RawLightClientRpc
|
||||
where
|
||||
TPlat: smoldot_light::platform::PlatformRef + Clone,
|
||||
{
|
||||
let (to_backend, backend) = mpsc::unbounded_channel();
|
||||
let chains = chains.into_iter().collect();
|
||||
|
||||
let future = async move {
|
||||
let mut task = BackgroundTask::new(client, chain_id);
|
||||
task.start_task(backend, rpc_responses).await;
|
||||
let mut task = BackgroundTask::new(client);
|
||||
task.start_task(backend, chains).await;
|
||||
};
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
@@ -61,7 +142,12 @@ impl LightClientRpc {
|
||||
#[cfg(feature = "web")]
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
|
||||
Ok(LightClientRpc { to_backend })
|
||||
RawLightClientRpc { to_backend }
|
||||
}
|
||||
|
||||
/// Returns the chain ID of the current light-client.
|
||||
pub fn chain_id(&self) -> smoldot_light::ChainId {
|
||||
self.chain_id
|
||||
}
|
||||
|
||||
/// Submits an RPC method request to the light-client.
|
||||
@@ -79,6 +165,7 @@ impl LightClientRpc {
|
||||
method,
|
||||
params,
|
||||
sender,
|
||||
chain_id: self.chain_id,
|
||||
})?;
|
||||
|
||||
Ok(receiver)
|
||||
@@ -107,8 +194,17 @@ impl LightClientRpc {
|
||||
params,
|
||||
sub_id,
|
||||
sender,
|
||||
chain_id: self.chain_id,
|
||||
})?;
|
||||
|
||||
Ok((sub_id_rx, receiver))
|
||||
}
|
||||
}
|
||||
|
||||
/// The added chain of the light-client.
|
||||
pub struct AddedChain {
|
||||
/// The id of the chain.
|
||||
pub chain_id: smoldot_light::ChainId,
|
||||
/// Producer of RPC responses for the chain.
|
||||
pub rpc_responses: smoldot_light::JsonRpcResponses,
|
||||
}
|
||||
+12
-2
@@ -33,8 +33,18 @@ mod platform;
|
||||
#[allow(unused_imports)]
|
||||
pub use getrandom as _;
|
||||
|
||||
pub use client::LightClientRpc;
|
||||
pub use smoldot_light::{AddChainConfig, AddChainConfigJsonRpc, ChainId};
|
||||
pub use client::{AddedChain, LightClientRpc, RawLightClientRpc};
|
||||
|
||||
/// Re-exports of the smoldot related objects.
|
||||
pub mod smoldot {
|
||||
pub use smoldot_light::{
|
||||
platform::PlatformRef, AddChainConfig, AddChainConfigJsonRpc, ChainId, Client,
|
||||
JsonRpcResponses,
|
||||
};
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub use smoldot_light::platform::default::DefaultPlatform;
|
||||
}
|
||||
|
||||
/// Light client error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@@ -114,3 +114,8 @@ tracing-subscriber = { workspace = true }
|
||||
name = "unstable_light_client_tx_basic"
|
||||
path = "examples/unstable_light_client_tx_basic.rs"
|
||||
required-features = ["unstable-light-client", "jsonrpsee"]
|
||||
|
||||
[[example]]
|
||||
name = "unstable_light_client_parachains"
|
||||
path = "examples/unstable_light_client_parachains.rs"
|
||||
required-features = ["unstable-light-client", "jsonrpsee", "native"]
|
||||
@@ -0,0 +1,101 @@
|
||||
use futures::StreamExt;
|
||||
use std::{iter, num::NonZeroU32};
|
||||
use subxt::{
|
||||
client::{LightClient, RawLightClient},
|
||||
PolkadotConfig,
|
||||
};
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
const POLKADOT_SPEC: &str = include_str!("../../artifacts/demo_chain_specs/polkadot.json");
|
||||
const ASSET_HUB_SPEC: &str =
|
||||
include_str!("../../artifacts/demo_chain_specs/polkadot_asset_hub.json");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The smoldot logs are informative:
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Connecting to a parachain is a multi step process.
|
||||
|
||||
// Step 1. Construct a new smoldot client.
|
||||
let mut client =
|
||||
subxt_lightclient::smoldot::Client::new(subxt_lightclient::smoldot::DefaultPlatform::new(
|
||||
"subxt-example-light-client".into(),
|
||||
"version-0".into(),
|
||||
));
|
||||
|
||||
// Step 2. Connect to the relay chain of the parachain. For this example, the Polkadot relay chain.
|
||||
let polkadot_connection = client
|
||||
.add_chain(subxt_lightclient::smoldot::AddChainConfig {
|
||||
specification: POLKADOT_SPEC,
|
||||
json_rpc: subxt_lightclient::smoldot::AddChainConfigJsonRpc::Enabled {
|
||||
max_pending_requests: NonZeroU32::new(128).unwrap(),
|
||||
max_subscriptions: 1024,
|
||||
},
|
||||
potential_relay_chains: iter::empty(),
|
||||
database_content: "",
|
||||
user_data: (),
|
||||
})
|
||||
.expect("Light client chain added with valid spec; qed");
|
||||
let polkadot_json_rpc_responses = polkadot_connection
|
||||
.json_rpc_responses
|
||||
.expect("Light client configured with json rpc enabled; qed");
|
||||
let polkadot_chain_id = polkadot_connection.chain_id;
|
||||
|
||||
// Step 3. Connect to the parachain. For this example, the Asset hub parachain.
|
||||
let assethub_connection = client
|
||||
.add_chain(subxt_lightclient::smoldot::AddChainConfig {
|
||||
specification: ASSET_HUB_SPEC,
|
||||
json_rpc: subxt_lightclient::smoldot::AddChainConfigJsonRpc::Enabled {
|
||||
max_pending_requests: NonZeroU32::new(128).unwrap(),
|
||||
max_subscriptions: 1024,
|
||||
},
|
||||
// The chain specification of the asset hub parachain mentions that the identifier
|
||||
// of its relay chain is `polkadot`.
|
||||
potential_relay_chains: [polkadot_chain_id].into_iter(),
|
||||
database_content: "",
|
||||
user_data: (),
|
||||
})
|
||||
.expect("Light client chain added with valid spec; qed");
|
||||
let parachain_json_rpc_responses = assethub_connection
|
||||
.json_rpc_responses
|
||||
.expect("Light client configured with json rpc enabled; qed");
|
||||
let parachain_chain_id = assethub_connection.chain_id;
|
||||
|
||||
// Step 4. Turn the smoldot client into a raw client.
|
||||
let raw_light_client = RawLightClient::builder()
|
||||
.add_chain(polkadot_chain_id, polkadot_json_rpc_responses)
|
||||
.add_chain(parachain_chain_id, parachain_json_rpc_responses)
|
||||
.build(client)
|
||||
.await?;
|
||||
|
||||
// Step 5. Obtain a client to target the relay chain and the parachain.
|
||||
let polkadot_api: LightClient<PolkadotConfig> =
|
||||
raw_light_client.for_chain(polkadot_chain_id).await?;
|
||||
let parachain_api: LightClient<PolkadotConfig> =
|
||||
raw_light_client.for_chain(parachain_chain_id).await?;
|
||||
|
||||
// Step 6. Subscribe to the finalized blocks of the chains.
|
||||
let polkadot_sub = polkadot_api
|
||||
.blocks()
|
||||
.subscribe_finalized()
|
||||
.await?
|
||||
.map(|block| ("Polkadot", block));
|
||||
let parachain_sub = parachain_api
|
||||
.blocks()
|
||||
.subscribe_finalized()
|
||||
.await?
|
||||
.map(|block| ("AssetHub", block));
|
||||
let mut stream_combinator = futures::stream::select(polkadot_sub, parachain_sub);
|
||||
|
||||
while let Some((chain, block)) = stream_combinator.next().await {
|
||||
let block = block?;
|
||||
|
||||
println!(" Chain {:?} hash={:?}", chain, block.hash());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
use super::{rpc::LightClientRpc, LightClient, LightClientError};
|
||||
use crate::backend::rpc::RpcClient;
|
||||
use crate::client::RawLightClient;
|
||||
use crate::{config::Config, error::Error, OnlineClient};
|
||||
use std::num::NonZeroU32;
|
||||
use subxt_lightclient::{AddChainConfig, AddChainConfigJsonRpc, ChainId};
|
||||
use subxt_lightclient::{smoldot, AddedChain};
|
||||
|
||||
/// Builder for [`LightClient`].
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -14,7 +15,7 @@ pub struct LightClientBuilder<T: Config> {
|
||||
max_pending_requests: NonZeroU32,
|
||||
max_subscriptions: u32,
|
||||
bootnodes: Option<Vec<serde_json::Value>>,
|
||||
potential_relay_chains: Option<Vec<ChainId>>,
|
||||
potential_relay_chains: Option<Vec<smoldot::ChainId>>,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
/// be wrong to connect to the "Kusama" created by user A.
|
||||
pub fn potential_relay_chains(
|
||||
mut self,
|
||||
potential_relay_chains: impl IntoIterator<Item = ChainId>,
|
||||
potential_relay_chains: impl IntoIterator<Item = smoldot::ChainId>,
|
||||
) -> Self {
|
||||
self.potential_relay_chains = Some(potential_relay_chains.into_iter().collect());
|
||||
self
|
||||
@@ -88,7 +89,16 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if being called outside of `tokio` runtime context.
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
#[cfg(feature = "jsonrpsee")]
|
||||
pub async fn build_from_url<Url: AsRef<str>>(self, url: Url) -> Result<LightClient<T>, Error> {
|
||||
let chain_spec = fetch_url(url.as_ref()).await?;
|
||||
@@ -104,7 +114,7 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
///
|
||||
/// The chain spec must be obtained from a trusted entity.
|
||||
///
|
||||
/// It can be fetched from a trused node with the following command:
|
||||
/// It can be fetched from a trusted node with the following command:
|
||||
/// ```bash
|
||||
/// curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "sync_state_genSyncSpec", "params":[true]}' http://localhost:9944/ | jq .result > res.spec
|
||||
/// ```
|
||||
@@ -116,7 +126,16 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if being called outside of `tokio` runtime context.
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
pub async fn build(self, chain_spec: &str) -> Result<LightClient<T>, Error> {
|
||||
let chain_spec = serde_json::from_str(chain_spec)
|
||||
.map_err(|_| Error::LightClient(LightClientError::InvalidChainSpec))?;
|
||||
@@ -136,9 +155,9 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let config = AddChainConfig {
|
||||
let config = smoldot::AddChainConfig {
|
||||
specification: &chain_spec.to_string(),
|
||||
json_rpc: AddChainConfigJsonRpc::Enabled {
|
||||
json_rpc: smoldot::AddChainConfigJsonRpc::Enabled {
|
||||
max_pending_requests: self.max_pending_requests,
|
||||
max_subscriptions: self.max_subscriptions,
|
||||
},
|
||||
@@ -147,12 +166,71 @@ impl<T: Config> LightClientBuilder<T> {
|
||||
user_data: (),
|
||||
};
|
||||
|
||||
let rpc_client = RpcClient::new(LightClientRpc::new(config)?);
|
||||
let online_client = OnlineClient::<T>::from_rpc_client(rpc_client).await?;
|
||||
Ok(LightClient(online_client))
|
||||
let raw_rpc = LightClientRpc::new(config)?;
|
||||
build_client_from_rpc(raw_rpc).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw builder for [`RawLightClient`].
|
||||
pub struct RawLightClientBuilder {
|
||||
chains: Vec<AddedChain>,
|
||||
}
|
||||
|
||||
impl Default for RawLightClientBuilder {
|
||||
fn default() -> Self {
|
||||
Self { chains: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl RawLightClientBuilder {
|
||||
/// Create a new [`RawLightClientBuilder`].
|
||||
pub fn new() -> RawLightClientBuilder {
|
||||
RawLightClientBuilder::default()
|
||||
}
|
||||
|
||||
/// Adds a new chain to the list of chains synchronized by the light client.
|
||||
pub fn add_chain(
|
||||
mut self,
|
||||
chain_id: smoldot::ChainId,
|
||||
rpc_responses: smoldot::JsonRpcResponses,
|
||||
) -> Self {
|
||||
self.chains.push(AddedChain {
|
||||
chain_id,
|
||||
rpc_responses,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Construct a [`RawLightClient`] from a raw smoldot client.
|
||||
///
|
||||
/// The provided `chain_id` is the chain with which the current instance of light client will interact.
|
||||
/// To target a different chain call the [`LightClient::target_chain`] method.
|
||||
pub async fn build<TPlatform: smoldot::PlatformRef>(
|
||||
self,
|
||||
client: smoldot::Client<TPlatform>,
|
||||
) -> Result<RawLightClient, Error> {
|
||||
// The raw subxt light client that spawns the smoldot background task.
|
||||
let raw_rpc: subxt_lightclient::RawLightClientRpc =
|
||||
subxt_lightclient::LightClientRpc::new_from_client(client, self.chains.into_iter());
|
||||
|
||||
// The crate implementation of `RpcClientT` over the raw subxt light client.
|
||||
let raw_rpc = crate::client::light_client::rpc::RawLightClientRpc::from_inner(raw_rpc);
|
||||
|
||||
Ok(RawLightClient { raw_rpc })
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the light client from a raw rpc client.
|
||||
async fn build_client_from_rpc<T: Config>(
|
||||
raw_rpc: LightClientRpc,
|
||||
) -> Result<LightClient<T>, Error> {
|
||||
let chain_id = raw_rpc.chain_id();
|
||||
let rpc_client = RpcClient::new(raw_rpc);
|
||||
let client = OnlineClient::<T>::from_rpc_client(rpc_client).await?;
|
||||
|
||||
Ok(LightClient { client, chain_id })
|
||||
}
|
||||
|
||||
/// Fetch the chain spec from the URL.
|
||||
#[cfg(feature = "jsonrpsee")]
|
||||
async fn fetch_url(url: impl AsRef<str>) -> Result<serde_json::Value, Error> {
|
||||
|
||||
@@ -8,6 +8,7 @@ mod builder;
|
||||
mod rpc;
|
||||
|
||||
use crate::{
|
||||
backend::rpc::RpcClient,
|
||||
blocks::BlocksClient,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::Config,
|
||||
@@ -19,10 +20,13 @@ use crate::{
|
||||
tx::TxClient,
|
||||
OnlineClient,
|
||||
};
|
||||
pub use builder::LightClientBuilder;
|
||||
pub use builder::{LightClientBuilder, RawLightClientBuilder};
|
||||
use derivative::Derivative;
|
||||
use subxt_lightclient::LightClientRpcError;
|
||||
|
||||
// Re-export smoldot related objects.
|
||||
pub use subxt_lightclient::smoldot;
|
||||
|
||||
/// Light client error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LightClientError {
|
||||
@@ -51,10 +55,57 @@ pub enum LightClientError {
|
||||
Handshake,
|
||||
}
|
||||
|
||||
/// The raw light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
pub struct RawLightClient {
|
||||
raw_rpc: rpc::RawLightClientRpc,
|
||||
}
|
||||
|
||||
impl RawLightClient {
|
||||
/// Construct a [`RawLightClient`] using its builder interface.
|
||||
///
|
||||
/// The raw builder is utilized for constructing light-clients from a low
|
||||
/// level smoldot client.
|
||||
///
|
||||
/// This is especially useful when you want to gain access to the smoldot client.
|
||||
/// For example, you may want to connect to multiple chains and/or parachains while reusing the
|
||||
/// same smoldot client under the hood. Or you may want to configure different values for
|
||||
/// smoldot internal buffers, number of subscriptions and relay chains.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If you are unsure, please use [`LightClient::builder`] instead.
|
||||
pub fn builder() -> RawLightClientBuilder {
|
||||
RawLightClientBuilder::default()
|
||||
}
|
||||
|
||||
/// Target a different chain identified by the provided chain ID for requests.
|
||||
///
|
||||
/// The provided chain ID is provided by the `smoldot_light::Client::add_chain` and it must
|
||||
/// match one of the `smoldot_light::JsonRpcResponses` provided in [`RawLightClientBuilder::add_chain`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This uses the same underlying instance spawned by the builder.
|
||||
pub async fn for_chain<TChainConfig: Config>(
|
||||
&self,
|
||||
chain_id: smoldot::ChainId,
|
||||
) -> Result<LightClient<TChainConfig>, crate::Error> {
|
||||
let raw_rpc = self.raw_rpc.for_chain(chain_id);
|
||||
let rpc_client = RpcClient::new(raw_rpc.clone());
|
||||
let client = OnlineClient::<TChainConfig>::from_rpc_client(rpc_client).await?;
|
||||
|
||||
Ok(LightClient { client, chain_id })
|
||||
}
|
||||
}
|
||||
|
||||
/// The light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct LightClient<T: Config>(OnlineClient<T>);
|
||||
pub struct LightClient<T: Config> {
|
||||
client: OnlineClient<T>,
|
||||
chain_id: smoldot::ChainId,
|
||||
}
|
||||
|
||||
impl<T: Config> LightClient<T> {
|
||||
/// Construct a [`LightClient`] using its builder interface.
|
||||
@@ -68,17 +119,17 @@ impl<T: Config> LightClient<T> {
|
||||
|
||||
/// Return the [`crate::Metadata`] used in this client.
|
||||
fn metadata(&self) -> crate::Metadata {
|
||||
self.0.metadata()
|
||||
self.client.metadata()
|
||||
}
|
||||
|
||||
/// Return the genesis hash.
|
||||
fn genesis_hash(&self) -> <T as Config>::Hash {
|
||||
self.0.genesis_hash()
|
||||
self.client.genesis_hash()
|
||||
}
|
||||
|
||||
/// Return the runtime version.
|
||||
fn runtime_version(&self) -> crate::backend::RuntimeVersion {
|
||||
self.0.runtime_version()
|
||||
self.client.runtime_version()
|
||||
}
|
||||
|
||||
/// Work with transactions.
|
||||
@@ -115,11 +166,16 @@ impl<T: Config> LightClient<T> {
|
||||
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::runtime_api(self)
|
||||
}
|
||||
|
||||
/// Returns the chain ID of the current light-client.
|
||||
pub fn chain_id(&self) -> smoldot::ChainId {
|
||||
self.chain_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OnlineClientT<T> for LightClient<T> {
|
||||
fn backend(&self) -> &dyn crate::backend::Backend<T> {
|
||||
self.0.backend()
|
||||
self.client.backend()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,32 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::LightClientError;
|
||||
use super::{smoldot, LightClientError};
|
||||
use crate::{
|
||||
backend::rpc::{RawRpcFuture, RawRpcSubscription, RpcClientT},
|
||||
error::{Error, RpcError},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use serde_json::value::RawValue;
|
||||
use subxt_lightclient::{AddChainConfig, ChainId, LightClientRpcError};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
pub const LOG_TARGET: &str = "light-client";
|
||||
pub const LOG_TARGET: &str = "subxt-rpc-light-client";
|
||||
|
||||
/// The raw light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
pub struct RawLightClientRpc(subxt_lightclient::RawLightClientRpc);
|
||||
|
||||
impl RawLightClientRpc {
|
||||
/// Constructs a new [`RawLightClientRpc`] from a low level [`subxt_lightclient::RawLightClientRpc`].
|
||||
pub fn from_inner(client: subxt_lightclient::RawLightClientRpc) -> RawLightClientRpc {
|
||||
RawLightClientRpc(client)
|
||||
}
|
||||
|
||||
/// Constructs a new [`LightClientRpc`] that communicates with the provided chain.
|
||||
pub fn for_chain(&self, chain_id: smoldot::ChainId) -> LightClientRpc {
|
||||
LightClientRpc(self.0.for_chain(chain_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// The light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
@@ -28,15 +43,29 @@ impl LightClientRpc {
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if being called outside of `tokio` runtime context.
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
pub fn new(
|
||||
config: AddChainConfig<'_, (), impl Iterator<Item = ChainId>>,
|
||||
config: smoldot::AddChainConfig<'_, (), impl Iterator<Item = smoldot::ChainId>>,
|
||||
) -> Result<LightClientRpc, Error> {
|
||||
let rpc = subxt_lightclient::LightClientRpc::new(config)
|
||||
.map_err(|err| LightClientError::Rpc(err))?;
|
||||
|
||||
Ok(LightClientRpc(rpc))
|
||||
}
|
||||
|
||||
/// Returns the chain ID of the current light-client.
|
||||
pub fn chain_id(&self) -> smoldot::ChainId {
|
||||
self.0.chain_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcClientT for LightClientRpc {
|
||||
@@ -46,6 +75,7 @@ impl RpcClientT for LightClientRpc {
|
||||
params: Option<Box<RawValue>>,
|
||||
) -> RawRpcFuture<'a, Box<RawValue>> {
|
||||
let client = self.clone();
|
||||
let chain_id = self.chain_id();
|
||||
|
||||
Box::pin(async move {
|
||||
let params = match params {
|
||||
@@ -66,7 +96,7 @@ impl RpcClientT for LightClientRpc {
|
||||
.await
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?;
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, "RPC response {:?}", response);
|
||||
tracing::trace!(target: LOG_TARGET, "RPC response={:?} chain={:?}", response, chain_id);
|
||||
|
||||
response.map_err(|err| RpcError::ClientError(Box::new(err)))
|
||||
})
|
||||
@@ -79,13 +109,15 @@ impl RpcClientT for LightClientRpc {
|
||||
_unsub: &'a str,
|
||||
) -> RawRpcFuture<'a, RawRpcSubscription> {
|
||||
let client = self.clone();
|
||||
let chain_id = self.chain_id();
|
||||
|
||||
Box::pin(async move {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Subscribe to {:?} with params {:?}",
|
||||
"Subscribe to {:?} with params {:?} chain={:?}",
|
||||
sub,
|
||||
params
|
||||
params,
|
||||
chain_id,
|
||||
);
|
||||
|
||||
let params = match params {
|
||||
@@ -107,7 +139,7 @@ impl RpcClientT for LightClientRpc {
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?
|
||||
.map_err(|err| {
|
||||
RpcError::ClientError(Box::new(LightClientError::Rpc(
|
||||
LightClientRpcError::Request(err.to_string()),
|
||||
subxt_lightclient::LightClientRpcError::Request(err.to_string()),
|
||||
)))
|
||||
})?;
|
||||
|
||||
@@ -116,7 +148,7 @@ impl RpcClientT for LightClientRpc {
|
||||
.trim_start_matches('"')
|
||||
.trim_end_matches('"')
|
||||
.to_string();
|
||||
tracing::trace!(target: LOG_TARGET, "Received subscription ID: {}", sub_id);
|
||||
tracing::trace!(target: LOG_TARGET, "Received subscription={} chain={:?}", sub_id, chain_id);
|
||||
|
||||
let stream = UnboundedReceiverStream::new(notif);
|
||||
|
||||
|
||||
@@ -20,4 +20,6 @@ pub use online_client::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "unstable-light-client")]
|
||||
pub use light_client::{LightClient, LightClientBuilder, LightClientError};
|
||||
pub use light_client::{
|
||||
LightClient, LightClientBuilder, LightClientError, RawLightClient, RawLightClientBuilder,
|
||||
};
|
||||
Reference in new issue
Block a user