-
Notifications
You must be signed in to change notification settings - Fork 20
Add support for smoldot #1214
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
base: main
Are you sure you want to change the base?
Add support for smoldot #1214
Conversation
This ensures faster sync at startup
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.
LGTM, nice one!
|
I noticed issues while finally entering in snapshot / signed phase in the election on WAH / PAH. It seems that with smoldot we have issues with block pinning with the ChainHead API. The miner fetches snapshot pages over multiple blocks and what happens is that we see a 2026-01-08T19:36:56.725125Z DEBUG polkadot-staking-miner: Phase
transition: Off → Snapshot(31) - stopping era pruning
2026-01-08T19:36:56.725292Z DEBUG polkadot-staking-miner: Exiting Off
phase, stopping pruning session
2026-01-08T19:36:56.917957Z TRACE polkadot-staking-miner: Processing
block=11146499 round=65, phase=Snapshot(31)
2026-01-08T19:36:56.918007Z TRACE polkadot-staking-miner: Sent block
#11146499 to miner
2026-01-08T19:36:56.918042Z TRACE polkadot-staking-miner: Processing
block #11146499 (round 65, phase Snapshot(31))
2026-01-08T19:37:09.700129Z TRACE polkadot-staking-miner: Target snapshot
with len 1256, hash:
Ok(0xc69b5de2c4f547313942e02866a326d6349eded79c1e234ab8699732236ffe67)
2026-01-08T19:37:10.040487Z TRACE polkadot-staking-miner: Processing
block=11146500 round=65, phase=Snapshot(30)
2026-01-08T19:37:10.040511Z TRACE polkadot-staking-miner: Sent block
#11146500 to miner
2026-01-08T19:37:10.040535Z TRACE polkadot-staking-miner: Processing
block #11146500 (round 65, phase Snapshot(30))
2026-01-08T19:37:13.576398Z TRACE polkadot-staking-miner: Processing
block=11146501 round=65, phase=Snapshot(29)
2026-01-08T19:37:13.576445Z TRACE polkadot-staking-miner: Sent block
#11146501 to miner
2026-01-08T19:37:17.017627Z WARN polkadot-staking-miner: Block
processing failed, continuing: Subxt(Rpc(ClientError(User(UserError {
code: -32801, message: "unknown or unpinned block", data: None }))))
2026-01-08T19:37:17.017670Z TRACE polkadot-staking-miner: Processing
block #11146501 (round 65, phase Snapshot(29))
2026-01-08T19:37:17.823748Z TRACE polkadot-staking-miner: Processing
block=11146502 round=65, phase=Snapshot(28)
2026-01-08T19:37:17.823783Z TRACE polkadot-staking-miner: Sent block
#11146502 to miner
2026-01-08T19:37:21.301938Z WARN polkadot-staking-miner: Block
processing failed, continuing: Subxt(Rpc(ClientError(User(UserError {
code: -32801, message: "unknown or unpinned block", data: None }))))
2026-01-08T19:37:21.301969Z TRACE polkadot-staking-miner: Processing
block #11146502 (round 65, phase Snapshot(28))
2026-01-08T19:37:23.016445Z TRACE polkadot-staking-miner: Processing
block=11146503 round=65, phase=Snapshot(27)
2026-01-08T19:37:23.016539Z TRACE polkadot-staking-miner: Sent block
#11146503 to miner
2026-01-08T19:37:30.673796Z TRACE polkadot-staking-miner: Voter snapshot
page=29 len=704, hash=Err(Subxt(Rpc(ClientError(User(UserError { code:
-32801, message: "unknown or unpinned block", data: None })))))
2026-01-08T19:37:30.673910Z WARN polkadot-staking-miner: Block
processing failed, continuing: Subxt(Rpc(ClientError(User(UserError {
code: -32801, message: "unknown or unpinned block", data: None }))))
2026-01-08T19:37:30.673924Z TRACE polkadot-staking-miner: Processing
block #11146503 (round 65, phase Snapshot(27))
2026-01-08T19:37:30.721856Z TRACE polkadot-staking-miner: Processing
block=11146504 round=65, phase=Snapshot(26)
2026-01-08T19:37:30.721889Z TRACE polkadot-staking-miner: Sent block
#11146504 to miner
2026-01-08T19:37:33.691744Z WARN polkadot-staking-miner: Block
processing failed, continuing: Subxt(Rpc(ClientError(User(UserError {
code: -32801, message: "unknown or unpinned block", data: None }))))
2026-01-08T19:37:33.691785Z TRACE polkadot-staking-miner: Processing
block #11146504 (round 65, phase Snapshot(26)) |
I think this code is potentially problematic pub async fn storage_at_head(api: &Client) -> Result<Storage, Error> {
let hash = get_latest_finalized_head(api.chain_api()).await?;
storage_at(Some(hash), api.chain_api()).await
}we just extract the hash and we drop the underlying BlockRef pointer handling the pinning mechanism pub struct BlockRef<H> {
hash: H,
_pointer: Option<Arc<dyn BlockRefT>>, // if none we can unpin!!!
}so we create a storage item with just hash with block ref with pointer None. What we could do instead: pub async fn storage_at_head(api: &Client) -> Result<Storage, Error> {
// Single call that preserves the BlockRef with its pinning _pointer
api.chain_api().storage().at_latest().await
}What happens: at_latest() internally calls latest_finalized_block_ref() and passes the entire BlockRef (including _pointer) to the storage client. The block stays pinned for the lifetime of the returned Storage object and smoldot shouldn't force any unpin (if I get it right). I'll try to test this variant. Why I think it might work:
|
Aah yup, this is a good thing to be aware of; always hold onto block refs if you want to do something later with the block, to help keep it pinned as long as possible!
This means that Subxt will not try to unpin any blocks so long as they are held onto (ie the However, Smoldot will refuse to keep blocks in memory indefinitely. Having a quick look I think Smoldot will allow 32 blocks to be pinned before dropping the subscription (see here and here). So basically, if we pin too many blocks on the Subxt side, Smoldot will end the chainHead subscription, leading to all blocks being "unpinned" on the Subxt side and the subscription being restarted. Given that you mentioned ~30 blocks worth of history being needed, I think you'll be cutting it very close and things will be prone to failing on occasion if blocks aren't quite unpinned fast enough.
I can think of a few options now:
|
We indeed hit this since we store way too many BlockRef while reading the snapshot and submitting a solution. I think that now that I know we are severely constrained on the number of pinnable blocks, I can try to do something for that so basically this:
Keep you posted! |
|
Is this |
We have two orthogonal problems: 1. we need to avoid that smoldot can unpin the block too early (and it can do so normally if we have a null pointer in the block ref - this is the issue the current version of the code has ) 2. we can't keep around too many blocks since smoldot has a limit of 32 -> the current code can be optimized to require MUCH less BlockRef than it's doing now. I am experimenting to reduce significantly the number of these BLockRef during snapshot and signed phase while keeping them around (i.e. with a valid pointer) long enough. You are suggesting another alternative which could also be explored. |
|
Going back to your suggestion @kianenigma , I think we can make the miner much more robust vs BLockRef: During Signed phase, all snapshot data is guaranteed to be available and immutable so what we could do is
so we massively reduce the number to BlockRef needed (basically down to 1 or so) |
Connect using the embedded smoldot light client with
--smoldotas altenative to WebSocket RPC connection via--wss.The two options are mutually exclusive.
This PR can be merged without risk of regressions for the deployed miner and can help us testing smoldot variant in these obscure times where we are facing issues with multiple RPC nodes (see paritytech/polkadot-sdk#10719).
A small nightly test has been added where we just check basic connectivity towards Polkadot, Kusama, Westend and Paseo AssetHub via smoldot.
See an example here
Related issue: #1141
In a follow-up PR, we can add a script to actually update chain specs and an associated GH workflow to periodically update them (e.g. to run once per week or so)