Letting sensors send transactions without reading metadata

Hi all, EnsRationis, software architect at Robonomics, a parachain on Polkadot for IoT. Our sensors are small ESP32 boards, some of them connected only by LoRa radio. We want them to build and sign full transactions themselves, and we’d like feedback on the design before we write the code.

Signing and talking to a node are the easy part. The hard part is knowing the exact shape of every call, and that shape changes with runtime upgrades. A microcontroller can’t read runtime metadata, so this knowledge has to become plain code at build time.

Our plan in three steps:

  1. The runtime repository publishes a ready-made description of the runtime: every pallet, call and type, plus our own transaction extension that decides who pays. It is produced from metadata once, when the crate is built, so nobody downstream reads metadata. If something can’t be described exactly, the build fails instead of guessing.
  2. A separate generator takes that description and writes C code only for the calls a given firmware uses.
  3. Each firmware gets a fingerprint of the calls it relies on. After a runtime upgrade we can see which devices are still compatible: a sensor that only records data shouldn’t need new firmware because an unrelated pallet changed.

Status: only the preparation step is merged (runtime metadata moved into its own crate). The rest is design, no code yet.

Question: has anyone already published such a ready-made runtime description for no_std or non-Rust clients? We’d rather reuse it than build another one.

Details:

A short update on the other half of this: how a device talks to the chain once it has a signed transaction.

akru, our lead developer, recorded the cps command-line tool reading a node of our CPS tree from the production Robonomics parachain. There is no RPC server in between. The tool starts a light client inside itself, joins the Polkadot and Robonomics peer-to-peer networks, checks finality on its own and then reads the data. Transactions go out over the same connection, so writing works the same way.

cps-light-client-demo

The middle of the recording is sped up 10×. Startup, while the light client catches up with finality, took about two minutes here. That’s too slow for an interactive CLI, and akru is working on it. For a service that starts once and runs for weeks, it hardly matters.

How it fits the plan above: an ESP32 can’t run a light client, so microcontrollers still need the generated C code from the first post. The gateway next to them can: a Raspberry Pi-class box that receives their LoRa or Meshtastic packets can now pass signed transactions to the parachain directly, without trusting anyone’s RPC endpoint.

The light-client backend is part of a work-in-progress pull request, #9 in the airalab/robins repository on GitHub.

A question for teams running light clients in long-lived services: do you keep the light client’s database between restarts to skip the warp sync, and does that work well for a parachain?

I’m not aware of such a project. However, I would hope that taking the current subxt generator it should be something easy possible. Or maybe it is just easier to generate c bindings for the rust code.

Yeah we are doing this with smoldot. We have done some fixes in smoldot around the database handling after restarts, but mostly performance stuff afair.

Thanks, Bastian, that’s useful on both counts.

On the light client, that settles it: we’ll keep the database between restarts.

On the runtime description: the subxt generator is the closest thing to what we need, and for a device with firmware written in Rust, plain subxt is probably the right answer as it is. Our sensor firmware is C, so we’ll take the subxt view of the runtime at build time and generate C only for the calls a device actually uses.

Since you’re here, a few broader questions where people on the Polkadot side probably know more than we do:

  1. Is embedded hardware anywhere on the roadmap for Polkadot SDK or subxt: microcontrollers like the ESP32, no_std without an allocator, clients in C?

  2. Hardware wallets had a similar problem, a device that can’t hold the full metadata, and got the metadata hash from RFC-0078. Our firmware fingerprint (#667) answers a narrower question: are the few calls this device uses still the same after an upgrade? Would it make sense to build it on the same merkleized metadata, so a device could check compatibility with a short proof instead of our own hash format?

  3. Has anyone run smoldot as a long-lived service on small ARM boards like a Raspberry Pi? That’s our gateway between LoRa/Meshtastic sensors and the chain, and we’d like to know how much memory to plan for.

Not that I’m aware of. But if you need any changes in polkadot-sdk to make your life easier, ping me :slight_smile:

Your approach already sounded to me like you are doing something similar to the merkleized metadata. Generally, I would assume that you could use it as a base for your work.

But then your devices would need to talk to some kind of endpoint to give them these pieces to check if they changed or not. We have introduced a long of changes of the years, to ensure that the transaction format is not changing anymore. I assume, if you have some small pallet, with a fixed interface you should be able to achieve an implementation that doesn’t even need constant checking.

Again not sure, if anyone has done this before. But as smoldot is expected to be running in the browser, I would expect that memory usage is reasonable “small”. If you encounter any issues, feel free to open an issue.

I made two posts here on Twitter about ESP32. It does not directly use Smoldot but a slimmer C version that was easier to cross-compile. It stays within the 320KB RAM limit and has about 70KB free on my controller. Firmware flash is at about 1.2/3MB.

It relies on a recent checkpoint and does not follow authority changes currently. But the thing that it can do is to verify all 402 GRANDPA justifications in about 4 seconds to stay realtime in sync with the last finalized block (still has to do networking etc but stays below 6 sec).
Could also do a random sub-sample, but i wanted to see if its possible to verify all of them.

The videos use Paseo, which only has 38 justifications that need to be verified so its much faster but I also tested it on Polkadot mainnet.

It also uses an RPC to fetch the GRANDPA justifications since the WIFI stack was not really able to handle multiple p2p connections well - or at least i saw that as not worth the time to optimize for.

@OliverTY, is your C client public? We’d like to try it on our boards.

Thanks! Right now we’re in the middle of our first esp32 major update: migrating all 100+ air quality sensors on the sensors.social map, some of which carry artifacts from 5 years ago :slight_smile: So it’s going to be fun. Once we’ve moved over to the Robonomics 5.0 pallets, I think akru will get back to the question of how to keep working with the light client without hardcoded RPCs. We’ll post an update here in about a month on how it’s going and whether we need any support.

I put it in here now GitHub - ggwpez/polkadot-esp32: Code for minimal Polkadot ESP32 light client · GitHub but its Vibecoded, so dont use in security relevant context :smiley: