> Light clients are crucial elements in blockchain ecosystems. They help users access and interact with a blockchain in a secure, decentralized and trustless manner. [https://www.parity.io/blog/what-is-a-light-client/](https://www.parity.io/blog/what-is-a-light-client/) > ## Owner(s) @Daan van der Plas # Smoldot: Hello world! For the last 1,5 months I’ve gone through the [Smoldot code](https://github.com/paritytech/smoldot/tree/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87) which was written by Pierre Kruger. Smoldot is a Rust/Substrate client alternative which contains two clients: the full client and the wasm light node. As for this knowledge base, it is solely focused on the wasm light node implementation. I will start by giving a short introduction to Smoldot and Substrate Connect. Thereafter I want to guide you through the code and give you an overview of how everything works and why it is important. Because Pierre has done an amazing job with documenting everything I have often used links to the source code. Alright, lets go! Table of contents: - Smoldot and Substrate Connect - Smoldot abstract - Chainspec - Networking service - Synchronization service - Runtime service - Transaction service - JSON-RPC service Before we begin, the code executes [asynchronously](https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html) and uses many [asynchronous tasks](https://docs.rs/async-std/latest/async_std/task/). Tasks will be mentioned throughout the code walk through and are shown in the diagrams, provided for each service, with a symbol (T). Moreover, the arrows between the service pending and other services represent [mspc-channels](https://github.com/rust-lang/futures-rs/blob/41478f5e69dddc51939c2695cd6f66d6b7b70d54/futures-channel/src/mpsc/mod.rs#L1-L79) which are used to share information between these asynchronous tasks. Simply put, the channels have a transmit and receiving end. Both ends have shared ownership over a variable, allocated on the heap. The sending end notifies the receiving end when it has changed the shared variable. # Smoldot and Substrate Connect Most blockchain user interfaces in the ecosystem work by connecting via a server (e.g. PolkadotJS) to a few trusted blockchain nodes which represent a central point of failure. Generally, if one wants to securely interact in a trustless manner with a blockchain, syncing a full node is necessary, which requires a lot of knowledge, effort, and resources. This is where light clients come into play. Put simply, a light client joins the peer-to-peer network of a chain and is able to interact with multiple full nodes. Unlike full nodes, light clients don’t need to run 24/7 and store a lot of data. As a matter of fact, light clients rely on full nodes for obtaining the information they need, e.g. requesting the balance of a specific user. However, and this is very important, after obtaining this information a light client verifies the information is correct. As a result, people that interact with blockchain user interfaces will independently and trustlessly obtain information from the blockchain due to a light client running on their device! In addition, ****************************Smoldot**************************** validates a submitted transaction (coming from the user) before it sends it to full nodes to be added to the transaction pool. The need for trusted blockchain nodes is now only necessary for light clients to join the peer-to-peer network. **Substrate Connect** provides the infrastructure necessary to run light clients directly in the browser. In addition, the browser extension enables resource sharing across browser tabs. Without the extension, Substrate Connect runs in the browser with each browser tab running a light client instance. This route will no doubt negatively impact page loading speed, providing a suboptimal user experience, especially compared to Web2 alternatives. Furthermore, Substrate Connect doesn’t require a TLS certificate to connect to nodes, as the connection is initiated from within the browser extension, which has more access rights than a typical website. Substrate Connect works in all major browsers, and when using the extension, it acts as a bridge, where Smoldot will run in the extension, making it possible for every tab or website to sync with the chain. Now lets unravel how Smoldot syncs with the chain! # Smoldot abstract First, this abstract is made to cover the essentials of Smoldot without having to read the whole document. Everything will be described again and more elaborately beginning from the [chainspec](#Chainspec). Whether Smoldot is used to interact with the **relay chain** or a **parachain**, it always needs to sync with the relay chain. As for a parachain, due to Polkadot's shared security, the parachain's finalized state is guaranteed on the relay chain. In other words, information regarding a parachain block, whether it is included, reverted or finalized, is acquired from the relay chain. To verify whether a relay chain block is finalized, a light client needs to know the ********************elected validators******************** from the ********************************GRANDPA******************************** protocol. By knowing the elected validators for a given era Smoldot can verify the **justification** which contains the proof of the finality of a block. The justification exists of a set of GRANDPA **commits** signed by all the elected validators. If more than 2/3 + 1 of the elected validators voted with their commit on the block and the signatures are correct, Smoldot can conclude the block is finalized. The elected validators form the **authority set** for a given GRANDPA **era** and are elected by the **[NPoS](https://wiki.polkadot.network/docs/learn-consensus)** algorithm. Changes to the authority set are crucial to track for a node who tries to sync with the relay chain in order to verify justifications. It also offers an alternative and less resourceful way of getting to the head of the chain, called the [warp sync](https://www.notion.so/KB-Light-clients-67e768036ac94a279d977814f76d5255) protocol. Instead of requesting all the blocks to get to the current state of the chain, a light client only needs to request **fragments**. These fragments provide the necessary proofs of the changes that have been made to the authority set. When it is up to date with the latest GRANDPA era, Smoldot will start syncing with the chain similar to a full node. To clarify, instead of holding the state and verifying the content of the new (non-finalized) blocks, it will only receive and [verify](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/verify.rs#L18-L66) new (non-finalized) **block headers**. Smoldot verifies a (non-finalized) block header by verifying the **authenticity** of the block. In other words, whether the author of the block was selected by the **BABE** protocol. In short, BABE breaks time into **epochs**, with each epoch being broken into **slots**. BABE will select an author (or several) to author a block in each slot. On the whole, Smoldot verifies **consensus**- and **finality** by keeping track of the authority set (the active validator set). The starting authority set will be obtained through the runtime and when it is up to date with the chain it acquires subsequent changes from the block headers. # Chainspec In order to give Smoldot the necessary information to start syncing with a chain it requires the [chainspec](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain_spec.rs#L18-L34). A chain specification is the description of everything that is required for the client to successfully interact with a certain blockchain. From the chainspec the [chain information](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain/chain_information.rs#L18-L39) is built. In the code, the chain information is all the crucial information Smoldot needs to know to verify the consensus and finality. It needs an initial state for the chain information and it is constructed from either: - The genesis state: The state of the chain, the current content of the storage (data structure: [the patricia merkle trie](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie.rs#L18-L85)) has a special key named `:code` which contains the WebAssembly code of the runtime. Besides the WASM code, it contains the runtime version and runtime APIs it provides. As for adding a relay chain, specific runtime calls of the GRANDPA-pallet are required to build the starting chain information: - [GrandpaApiAuthorities](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/primitives/finality-grandpa/src/lib.rs#L561-L567) - [GrandpaApiCurrentSetId](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/primitives/finality-grandpa/src/lib.rs#L598-L599) Prior to executing the dispatchables it builds the runtime (see [runtime service](<#Runtime service>)) - The [checkpoint](https://github.com/Daanvdplas/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain_spec/light_sync_state.rs#L65): A point in time of the finalized chain that can be used to obtain the chain information from. - From the database: From local environment, not from the chainspec. (⇒ All resulting in the [ChainInformation](https://github.com/Daanvdplas/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain/chain_information.rs#L118-L129)) As a result, the chain information provides the starting authority set for the warp sync process. However, to sync with a chain it needs information from other peers and therefore needs to join the peer-to-peer network. # Networking service ![](https://i.imgur.com/5LcReWd.png) In order to connect to other nodes it needs the [networking service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/network_service.rs#L18-L37). It is responsible for joining the peer-to-peer network, for Substrate-based chains, the [libp2p protocol](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p.rs#L18-L91) is used. The networking service starts three background tasks: 1. Processing the networking service. 2. Peer discovery. 3. Processing existing connections. For more information regarding [Substrate's Networking Protocol](https://crates.parity.io/sc_network/index.html). ## Processing the networking service (T) This task ensures that Smoldot is always connected to a certain amount of peers. The amount of `in-slots` and `out-slots` refer, respectively, to the maximum amount of peers that can connect to Smoldot and the maximum amount of peers Smoldot connects to. In addition, when a connection is established, it requests to open [substreams](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection.rs#L38-L77) with the given peer. Last but not least, it coordinates the requests and responses from the connections to Smoldot and vice versa. In the code the [the coordinator](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/collection.rs#L18-L48) is responsible for this. In detail: - Assign `out-slots`: Only if there are slots left, it tries to make a connection with closest peers obtained from the **[k-buckets](<##Peer discovery (T)>)**, referred as **desired peers**. - Start new connections (and notification substreams) with desired peers: When a peer is set as **desired,** Smoldot tries to make a connection. The peer will respond with either making a **Single stream connection** or a **Multi stream WebRTC connection**. An asynchronous task will be spawned for each connection to further progress with the handshake phase and requesting / closing new (inbound / outbound) substreams. All together, everything to exchange information with a given peer (see [Processing existing connections](<## Processing existing connections (T)>)). - The coordinator is updated about the connectivity status with a given peer. Moreover, from the inbound open substreams Smoldot receives information. The coordinator is responsible for distributing it to the right service. The other way around, it sends information through outbound open substreams coming from other services within Smoldot. - Process requests and updates from other services: The coordinator is also responsible for requesting new substreams, based on requests from other services, that it needs with a given peer. In addition, when a service has received incorrect information the coordinator needs to be updated in order to e.g. shut down the connection. ## Peer discovery (T) For a peer-to-peer network it is necessary to have the information to connect to any given peer, available at any given time. Due to its origin, the storage of this information needs to be decentralized and is therefore divided over all the peers in the network through the [Kademlia distributed hash table](https://docs.ipfs.tech/concepts/dht/#distributed-hash-tables-dhts) (DHT). In the DHT each node has so called [k-buckets](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/kademlia/kbuckets.rs#L18-L67), which form a partial view of the complete list of all the peers in the network. Peer discovery is done by sending a Kademlia "find node" request to a single peer. More specifically, based on the [Kademlia request-response protocol](https://github.com/libp2p/specs/tree/master/kad-dht#rpc-messages), it builds a wire message to ask the target to return the nodes closest to the parameter. To decide which peer it sends this request to: - It creates a Sha256 hash of a random created peerID, the target hash. - It checks the k-buckets for the closest peers. - It hashes the peerIDs of closest peers and obtains a list with ascending order of the log2 distance between the target hash and the k-bucket peer’s hash. - It obtains the peer with the shortest distance and where it already has an established connection. As a result, Smoldot receives a new list of peers with their [multiadresses](https://docs.libp2p.io/concepts/fundamentals/addressing/). These peers will be inserted into the k-buckets, if there is space, and/or replaces the peers that have not been connected. After the initialization of the network, sync, runtime and transaction service, the bootnodes (obtained from the chainspec) are added to Smoldot’s k-buckets and Smoldot can start connecting to the peer-to-peer network. ## Processing existing connections (T) Smoldot has two types of connections due to API-related purposes, more specifically two ways of calling the browser's APIs. - In the **Single stream** connection the browser just sends or receives data and smoldot implements substreams on top of that. So the substreams are handled internally. More specifically, when a connection is established, the [handshake phase](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection/single_stream_handshake.rs#L18-L30) starts with the [multistream-select protocol](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection/multistream_select.rs#L18-L56). As of now, Smoldot supports the [noise protocol](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection/noise.rs#L18-L61) for encryption and the [yamux protocol](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection/yamux.rs#L18-L47) for multiplexing. After the handshake has finished, the connection is in the established phase and [substreams](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/libp2p/connection/established/substream.rs#L18-L24) can be opened by the local and remote endpoint. - In the **Multi stream [WebRTC](https://docs.libp2p.io/concepts/transports/webrtc/)** connection the browser handles the substreams. There are two types of substreams: 1. Notifications: - Block announcements protocol - GRANDPA announcements protocol (GRANDPA commits) - Transaction announcements protocol (not implemented for Smoldot) 2. Request-response: - [IPFS id protocol](https://github.com/libp2p/specs/blob/master/identify/README.md) - Sync protocol - Light protocol - Kademlia protocol - [Sync warp protocol](<###Warp sync>) - State protocol When a connections is made, a block announcement substream is requested, if accepted, followed by a request for additional GRANDPA announcements. In addition, when an inbound substream is opened it requests for an outbound substream. This is used to update the peers with Smoldot’s view of the state of the chain. # Synchronization service Now that the networking service has been set up it starts a new background task, [the synchronization service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync.rs#L18-L69). The role of the sync service is to do whatever necessary to obtain and stay up to date with the best and finalized blocks of the chain. As for the relay chain it starts with the warp sync protocol to get to the latest GRANDPA era as fast as possible, then it changes to the [all-forks](https://www.notion.so/KB-Light-clients-67e768036ac94a279d977814f76d5255) protocol. The [parachain synchronization](<###Parachain and parathreads sync>) relies on the relay chain, more specifically the runtime service of the relay chain. In general, Smoldot will track a list of [sources](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all_forks/sources.rs#L18-L26), which represent peers that Smoldot receives new block headers and GRANDPA commits from. From the information it receives from a given peer, it knows which (non-finalized) blocks this peer is aware of. ## Relay chain ![](https://i.imgur.com/aAkqL2c.png) The flow of the relay chain synchronization service exists of: - Sending new requests to peers to progress sync state machine: - **Fragment** request, for the warp sync. - **Storage** request, provided with a merkle proof. - **Runtime call** request: In order to execute a runtime call, Smoldot needs specific merkle values from storage. Provided with a merkle proof. - **Block** request: For the all-fork sync, if a block(s) is missing between (non-finalized) blocks that it receives and the latest finalized block Smoldot knows. Or a json-rpc request about a block Smoldot doesn't know about. - Process network [events](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/network_service.rs#L861-L886). - Process [requests](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L802-L825) from: - Runtime service. - Transaction service. - JSON-RPC service. ### Warp sync Thanks to GRANDPA, Smoldot [i](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/warp_sync.rs#L18-L92)s able to get up to date with the latest GRANDPA **era** through the [warp sync protocol](https://spec.polkadot.network/#sect-sync-warp-lightclient). Instead of requesting all the blocks to get to the current state of the chain, a light client only needs to request **fragments**. These fragments provide the necessary proofs of the [changes](https://github.com/paritytech/substrate/blob/b9e97232900a285b64d13986177edd1328efc525/client/consensus/grandpa/src/authorities.rs#L690) that have been made to the authority set, also known as the ******elected validators******. The elected validators are elected by the **NPoS** algorithm to participate in the GRANDPA protocol for that given **era** ([more information](https://substrate.stackexchange.com/questions/5922/are-justification-in-the-block-the-actual-votes-cast-by-validators-in-grandpa)). By knowing the elected validators, Smoldot can verify a [justification](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/finality/justification.rs#L18-L29). In other words, it can verify the finality of a block within a given era. Changes to the authority set are signed by the previous authority set and stated in the block header at the end of the era. When the respective block is finalized and the justification is verified by Smoldot, it knows the new authority set in a trustless manner! The [warp syncing process](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/warp_sync.rs#L18-L92) can be split into 4 [phases](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/warp_sync.rs#L275-L336): 1. Requesting the fragments. - Block headers where authority set changes occurred. 2. [Verifying fragments](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/protocol/grandpa_warp_sync.rs#L18-L36). - Verifying the justification of the header where the changes occurred: - If any block between the latest finalized one and the target block triggers any GRANDPA authorities change, then we need to finalize that triggering block before finalizing the one targeted by the justification. - Check if public keys are within the authority set. - Check that justification contains a number of signatures equal to at least (2/3 + 1) of the number of authorities. - Verify signatures. When Smoldot is up to date with the latest GRANDPA authority set, i.e. latest era: 1. Download the latest finalized runtime: - Send storage request, to sync service, to obtain runtime code. - Decode the header of the given block to obtain the state root. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Send [storage proof request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L383-L386). - [Decode and verify proof](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie/proof_decode.rs#L18-L39) - Obtained`:code` (encoded WASM blob). - Download runtime - Build the WASM virtual machine. 2. Build latest the chain information: - Send call proof request, to sync service, to execute runtime call. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Sending [call proof request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/service/requests_responses.rs#L514-L521). - [Decode and verify proof](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie/proof_decode.rs#L18-L39). - Executing BABE related runtime calls: - [BabeApiCurrentEpoch](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/primitives/consensus/babe/src/lib.rs#L396-L397) - [BabeApiNextEpoch](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/primitives/consensus/babe/src/lib.rs#L399-L401) - [BabeApiConfiguration](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/primitives/consensus/babe/src/lib.rs#L386-L387) As might be noticed, this time the dispatchables are BABE related instead of GRANDPA related. That is because the BABE protocol decides which elected validator is allowed to author a block when, important for the all-forks syncing protocol. ### All-forks sync The [all-forks syncing](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all_forks.rs#L18-L81) strategy is very similar to how full nodes sync with the network; holding the state, verifying the content of the new (non-finalized) blocks and GRANDPA commits. Yet, Smoldot will only receive and [verify](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/verify.rs#L18-L66) new (non-finalized) **block headers** and GRANDPA commits. Smoldot verifies a (non-finalized) block header by verifying the **authenticity**. In other words, whether the author of the block was selected by the **BABE** protocol. [BABE](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/verify/babe.rs#L18-L130) breaks time into **epochs**, with each epoch broken into **slots**. BABE will select an author (or several) to author a block in each slot. Each slot can have a primary and secondary author (or “slot leader”). **Primary** slot leaders are assigned randomly, using **VRF**. VRF takes an epoch random seed (agreed upon in advance by all nodes), a slot number and the author’s private key. Each author evaluates its VRF for each slot in an epoch. For each slot whose output is below some agreed-upon threshold, the validator has the right to author a block in that slot. Because the function is random, however, sometimes there are slots without a leader. In order to ensure a consistent block time, BABE uses a round-robin system to assign **secondary** slot leaders. The header of the first block produced after a transition to a new epoch contains the public keys that are allowed to sign new blocks for the next epoch. When this block is finalized, Smoldot knows the block authors for the next epoch. The all-forks syncing process : 1. Verify block headers - Block number. - Compare latest block header with new block header’s parent hash. - Verify whether a block header provides a correct proof of the legitimacy of the authorship. - BABE: - Verify that the signature from the [seal](https://substrate.stackexchange.com/questions/236/what-is-sealing-in-a-blockchain/241) is the same as the hash of the unsealed header signed with the public key of the block author. - Primary slot: verify the [VRF](https://wiki.polkadot.network/docs/learn-randomness) output and proof. - Secondary slot: create a Blake2b hash from the epoch’s randomness and the slot number. The authority who matches the authority index from ⇒ hash % number of authorities. Correspondingly, it [determines if it is the best block](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain/blocks_tree/best_block.rs#L49-L63), it checks for consensus updates in the header digest, and adds the block to the state machine: - [The finalized block header, plus a tree of authenticated non-finalized block headers](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain/blocks_tree.rs#L18-L59). - The “[disjoint blocks](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all_forks/disjoint.rs#L18-L42)” [state machine](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all_forks/pending_blocks.rs#L18-L87) . 1. Verifying GRANDPA [commits](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/finality/grandpa/commit.rs#L18-L32): - Check if public keys are within authority set. - Check if commit is about a block which is the target block or a descendant. - Verify signatures. - If any block between the latest finalized one and the target block triggers any GRANDPA authorities change, then we need to finalize that triggering block before finalizing the one targeted by the justification. Important to mention, when the warp sync finished, there is a high probability that there is a misalignment in the received information and Smoldot’s latest finalized block. In order to correct this misalignment, Smoldot [requests](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all.rs#L1747-L1772) the necessary blocks and justifications. 1. Verifying justifications: - If any block between the latest finalized one and the target block triggers any GRANDPA authorities change, then we need to finalize that triggering block before finalizing the one targeted by the justification. - Check if public keys are within authority set. - Check that justification contains a number of signatures equal to at least (2/3 + 1) of the number of authorities. - Verify signatures. After succesfully verifying a new block or GRANDPA commit, Smoldot [sends an update](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service/standalone.rs#L746-L765) to other [light clients it is connected to](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/protocol/block_announces.rs#L23-L62). In addition, it updates the services that are subscribed to the sync service: - Runtime service - Transaction service - JSON-RPC service. Last, it informs all its present and future peers of the state of the local node regarding the best and finalized block. ## Parachain ![](https://hackmd.io/_uploads/SkqM035F3.png) *It is recommended to first read the runtime service with the relay chain in mind.* The relay chain [stores](https://github.com/paritytech/polkadot/blob/aa4b5fc5df538fe281af8379fba45bc1937a2dd9/runtime/parachains/src/paras/mod.rs#L550-L553) the [head-data](https://substrate.stackexchange.com/questions/396/what-is-head-data-and-how-do-i-get-it), also known as [parahead](https://spec.polkadot.network/#defn-para-block), of every registered parachain. The flow of the [parachain and parathread syncing service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/para.rs#L18-L34) exists of: - Processing relay chain [notifications](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/runtime_service.rs#L633-L684). - Updates from the runtime service. - Fetching new paraheads. - Processing network [events](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/network_service.rs#L861-L886). - GRANDPA commit not used. - Processing [requests](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L802-L825) from: - Runtime service. - Transaction service. - JSON-RPC service. ### Parachain and parathread sync First we [subscribe](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/runtime_service.rs#L191-L215) to the runtime service to obtain the current state of the relay chain and get notified about new blocks. This is done by subscribing to the runtime service of the relay chain. When the finalized runtime is downloaded by the runtime service the parachain syncing service can fetch [paraheads](https://substrate.stackexchange.com/questions/396/what-is-head-data-and-how-do-i-get-it), i.e. best and finalized blocks, by calling the [`ParachainHost_persisted_validate_data`](https://spec.polkadot.network/#sect-rt-api-persisted-validation-data). However, in order to execute this extrinsic, Smoldot needs the merkle node values which are required during runtime execution by: - Sending a call proof request, to the sync service, to execute runtime call. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Sending [call proof request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/service/requests_responses.rs#L514-L521). - [Decode and verify proof](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie/proof_decode.rs#L18-L39). - Starting a WASM virtual machine, with automatic storage overlay and logs management, to execute the `ParachainHost_persisted_validation_data` extrinsic. ⇒ If successful, obtain parahead from [PersistedValidationData](https://github.com/paritytech/polkadot/blob/aa4b5fc5df538fe281af8379fba45bc1937a2dd9/primitives/src/v1/mod.rs#L480-L510). The parahead, head data, contains information about a parachain block. When the relay chain block, where the parahead is obtained from, is finalized, the parahead is finalized as well. When a parahead is finalized, it checks whether it is up to date with the block height of other parachain nodes. New blocks and finality updates are notified to the subscribers: - Runtime service. - Transaction service. - JSON-RPC service. # Runtime service ![](https://i.imgur.com/6eq2ahU.png) The next asynchronous task is the [runtime service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/runtime_service.rs#L18-L55). Essentially, this service wants to have the latest finalized downloaded runtime to provide to other services. Therefore it needs to stay up to date with the chain. In order to stay up to date with the chain it [subscribes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L182-L198) to the synchronization service. When a service subscribes to the sync service it receives the current state of the chain (the finalized block and the non-finalized descendants) and gets [notified](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L729-L770) regarding new non-finalized (best) blocks and finality updates. As a result, the runtime service holds a data structure of a tree of non-finalized blocks that all descend from the finalized block. These new block headers contain the [header digest](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/header.rs#L371) which states whether the runtime has been upgraded. If so, the runtime service has to download the new runtime: - Send storage request, to sync service, to obtain runtime code. - Decode the header of the given block to obtain the state root. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Send [storage proof request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L383-L386). - [Decode and verify proof](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie/proof_decode.rs#L18-L39). - Receive `:code` (encoded WASM blob) - Download runtime - Through [wasmi](https://docs.rs/wasmi/0.9.1/wasmi/) it is able to interpret the WASM blob in native binary as a [WASM virtual machine](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/executor/vm.rs#L18-L75). The dispatchables from the runtime are exported and the host functions are imported. That all together makes the [WASM virtual machine specific to a Substrate/Polkadot runtime](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/executor/host.rs#L18-L192). When executing a runtime call through a WASM virtual machine, the execution could be interrupted because it requests execution of a host function. The virtual machine is then interrupted, the host function is executed and the virtual machine will be resumed with the result of this host function. When the runtime service gets notified about the block which stated the runtime upgrade, it has the latest finalized runtime again. Accordingly, the data structure will be [pruned](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/chain/fork_tree.rs#L236-L240) and the following services can utilize the latest (upgraded) finalized runtime: - Parachain and parathreads syncing service - Transactions service - JSON-RPC service # Transaction service ![](https://i.imgur.com/NRhDDRG.png) The [transaction service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/transactions.rs#L18-L86) handles everything related to transactions. It holds a data structure called the [transaction pool](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/transactions/pool.rs#L18-L79) which holds a list of pending transactions, transactions that should later be included in blocks. Furthermore, a list of transactions that have been included in non-finalized blocks. As for the light client the [transaction service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/transactions_service.rs#L18-L68) and the [transaction pool](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/transactions/light_pool.rs#L18-L60) are most of the time idle. This is due to the fact it is only operating when the user submits a transaction. The transaction service utilizes the runtime service. Identical to the parachain syncing service it wants to use the latest finalized runtime. The transaction service needs the runtime to validate incoming transactions from the JSON-RPC service. It validates a submitted transaction against the latest (best) block. If valid, it sends the transaction to peers through the networking service. Moreover, it will check the new (best) blocks for whether the transaction is included and when the block, where the transaction is included, is finalized. To have the submitted transaction being added to a block the transaction will be gossiped to peers through the networking service. The validation of a transaction: - Sending a call proof request, to the sync service, to execute a runtime call. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Sending [call proof request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/network/service/requests_responses.rs#L514-L521). - [Decode and verify proof](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie/proof_decode.rs#L18-L39). - Starting a WASM virtual machine with automatic storage overlay and log management to execute the [`TaggedTransactionQueue_validate_transaction`](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/transactions/validate.rs#L245-L247) extrinsic. - Call [transaction pool runtime API](https://github.com/paritytech/polkadot/blob/aa4b5fc5df538fe281af8379fba45bc1937a2dd9/runtime/polkadot/src/lib.rs#L1753-L1761). - [Validate transaction](https://github.com/paritytech/substrate/blob/a6da808575fe403ab2bd7f6cd009896cdc6fd71a/frame/executive/src/lib.rs#L538-L570). After validating the transaction and sending it to peers, it will download the block bodies from latest new blocks: - Send a block request, to the sync service, to find the submitted transaction. - Send a request for a list of peers that it [assumes](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/sync_service.rs#L259-L262) are aware of this block. - Send a [block request](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/sync/all.rs#L1747-L1772), specifically requesting for the body. - Verify the block body (happens on arrival in the network service). - Build the [trie](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/trie.rs#L18-L85) root of the list of extrinsics of the block and compare with extrinsics root from block header. - Check the extrinsics in the block body for the submitted transaction (by hash). It updates the submitter of the transaction about the status of the transaction. # JSON-RPC service ![](https://i.imgur.com/clAU1r3.png) The last asynchronous task that will be spawned handles the [JSON-RPC service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/json_rpc.rs#L18-L60). The [JSON-RPC service](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/json_rpc_service.rs#L18-L35) holds a [state machine](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/json_rpc/requests_subscriptions.rs#L18-L106) which consists of a list of clients (Smoldot API users), pending outgoing messages, pending [request(s)](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/json_rpc/requests_subscriptions.rs#L70-L87) and active [subscription(s)](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/src/json_rpc/requests_subscriptions.rs#L89-L106). It all starts with [a submitted JSON-RPC request by the API user](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/examples/basic.rs#L89-L94): - Obtain JSON-RPC request from state machine and [match it to its method](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/json_rpc_service.rs#L834-L1253). - Methods can consist of: - Sync service request(s), i.e. block header(s) and peer(s). - Network service request(s), i.e. block body(s), call proof(s) and storage proof(s). - Runtime service request(s), i.e. (un)subscribe to (finalized) block header(s). - Execution of Runtime call(s), i.e. storage query(s). - Depending on the request it responds with: - a single response (e.g. `chain_get_header`) - through a subscription (e.g. `chain_subscribe_new_heads`) Because this service is dependent on a lot of other services, multiple lightweight tasks are created which are handled by this service. For each subscription it spawns a new lightweight task that waits for updates and notifies the API user (these updates need to be [manually](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/examples/basic.rs#L98-L103) polled by the API user). In addition, the service starts another mini task dedicated to filling the [Cache](https://github.com/paritytech/smoldot/blob/76994b52cd658a4ac1dc7a08f6bd4eba702e0e87/bin/light-base/src/json_rpc_service.rs#L526-L567) with new blocks from the runtime service. The API user is more likely to ask for information about recent blocks and perform calls on them, hence a cache of recent blocks.