-
Notifications
You must be signed in to change notification settings - Fork 245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lightclient: Add support for multi-chain usecase #1238
Changes from all commits
4cdfe7b
962aeff
3fd6929
220bff3
b50e53d
4655a65
f91b10e
0616aa9
c2dba83
1876b42
6d945f5
b16c5f3
7ef743d
659ea8b
8abe5aa
d873f94
d095a51
c70f655
e2b9def
acaf279
e164b85
ce56fd4
7abaf88
1bafe50
93e1260
0efba8f
bcbde26
d9bd705
c0be204
79dfbd1
43a1c77
f7ef096
2f8dd47
0ee52e6
da4ee7e
deb648e
2f82e91
46a6adc
15811fc
56ca927
0cfe8d3
25581e4
84bf673
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to write a wrapper for this API? It's quite annoying to do this unwrap because the RPC client should always be enabled in smoldot? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I was thinking of creating a bit of a higher level API as a followup to this. Ideally we could provide:
let api = LightClient::<PolkadotConfig>::builder()
.bootnodes([
"/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp",
])
.build_from_url("ws://127.0.0.1:9944")
.await?;
/// Naming tbd, suggesting that's somewhere in the middle between super high level and super low level API.
let client: MediumLightClient = MediumLightClient::builder()
.add_chain(
AddChianBuilder::with_identifier( "polkadot" )
.with_spec( include_str!( "../assets/polkadot_chain_spec.json")
)
.add_chain(
AddChainBuilder::with_identifier( "asset-hub")
.relay_chain( "polkadot")
.from_url("ws://127.0.0.1:9944")
)
.build().await;
let polkadot_api = client.for_chain( "polkadot"); Something like that, but I didn't put a proper thought into the API. We still need a way to identify the chains while adding them to the client. And another approach here, would be to have a Another thing worth exploring would be extending the Still believe having the ability for users to turn a raw light client into something that subxt can use under the scene would cover the most complex use-cases :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, I'm generally for the idea too of having:
But yeah it does need some thought and experimentation to find the ideal interface for this stuff I think; we shouldn't rush into it :) |
||
.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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, so this a little complicated to first call "add_chain" and then after call "for_chain". I don't follow why we can't hide that from this API and just provide "connect_to_chain(chain_id, rpc_stream)" to avoid having to functions to call. Perhaps you need pass in the client as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The It might be possible to introduce another subxt frontend -> subxt backend to propagate this to the smoldot client from the background task and add the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine for now but perhaps worth writing up an issue to look into. My point is initializing and using the API is a bit complicated but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep that's a good point, raised #1253 to handle it later, thanks! |
||
.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> = | ||
lexnv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found the naming of this a bit odd,
I would prefer
connect_to_chain(id: ChainId)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another question is it possible to connect the same chain more than once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, its possible to use the same chain ID to create multiple instances of the light client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have tested this locally with the following patch, because I was also curious if there's anything else to be done here:
This will subscribe to all the blocks of the asset-hub and indeed we get blocks a bit earlier than our
parachain_sub
streamThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I like
for_chain
better thanconnect_to_chain
because I think it's already connected, so all we are doing I believe is "selecting" a chain from those that are available to work with. (maybeseelct_chain
would also work for that reason; I'm easy either way)