Replies: 2 comments 1 reply
-
So this basically means we cannot have a static list of installed modules as described above. |
Beta Was this translation helpful? Give feedback.
-
Hey @hu55a1n1, great initiative putting the thoughts and design of how we should design the module system for BaseCoin, which can be served as the foundation for the design for cosmos-rs. Here are some of my suggestions to improve the design: Design in Rust vs GoI think the ICS specs serve as very good reference on the high-level architecture that we should aim for designing a good module system. On the other hand, the specific interface design tend to be specific to Go, and I think we can try and improve the design to make it more suited in Rust. I feel that the reason the Go interface design is specified in English specs is because the Go type system is not strong enough to capture the high-level requirements for different Cosmos Go modules to interoperate. But if we can design Rust types/traits that directly capture the requirements, then it provides more flexibility on how different Rust modules can interoperate.
|
Beta Was this translation helpful? Give feedback.
-
Phase 3 and beyond
As we continue
basecoin
development into phase-3, this might be a good time to revisit its design based on ICS specs. Described below is the first iteration of a design proposal which is expected to evolve over time.Modules
ICS 24 - Module system describes the requirements expected of the module system. Modules must be ->
* able to execute self-contained, potentially mutually distrusted packages of code
* able to control how other modules can communicate with them
* identifiable
* manipulatable by a master module or execution environment
Additionally, module code must be deterministic (unless otherwise specified).
We define the 'master module' as the
basecoin
code that manages the routing of messages and interaction between other modules. Note that the [execution environment <> master module <> module] relation is (somewhat) analogous to the [OS <> Kernel <> thread] relation.Implementation
We define the below trait and examine how it achieves the above requirements.
Self-contained
Note that all
Module
trait functions are static. This means that modules are forced to depend on the 'master module' for access to the state. The 'master module' is responsible for making sure that modules are isolated and have exclusive access to their key-value store paths (described in detail in the store section below).Module interaction
Note that modules are isolated from each other and unaware of the existence of other modules. They must depend on the master module for interacting with other modules.
TBD - define
trait MasterModule
? ics-025-handler-interface? ics-05-port-binding-as-module-manager?Identifiable
We define the
IdentifiableBy
trait as below ->This requirement is described in detail in ICS 5 - Data Structures ->
The generic parameter may represent a pointer value (that is guaranteed to be unique) as in the case of the Cosmos SDK StoreKey implementation or an Ethereum smart-contract address or any such unique identifier type.
The
identifier()
function is purposely not clubbed into theModule
trait and separated into its own trait because we MUST guarantee that every module has a unique identifier for a specified type. We could leverage build.rs scripts or procedural/derive macros to generate these identifiers at compile time.Manipulatable
All supported modules are added to the
SupportedModules
enum ->Installed modules are stored in a static array ->
The master module is able to loop over all installed modules and decides which messages to dispatch to which module.
Although this approach might not seem ideal, it does avoid dynamic dispatch. Also, we could use procedural macros to avoid code duplication.
Store
According to ICS 24 - Key/value Store, the host state machine must provide a key/value store that provides the standard
get
,set
anddelete
functionality. Note however that realistically, theget
functionality must be provided for all heights of the blockchain.ICS24 doesn't impose any particular storage backend or backend data layout.
Implementation
The
Store
trait is heavily influenced by the tendermock project's definition.The store trait maybe extended with provided methods such as ->
A
provableStore
must additionally implement the following methods ->The tendermock project has a AVL tree based store implementation that we could reuse.
provableStore and privateStore
Additionally, host state machines are required to provide two instances of the above interface -
The
provableStore
is required to write to a key/value store whose data can be externally proved with a ICS 23 - vector commitment and must use canonical proto3 data structures.For the current implementation, we only provide a single instance of a
provableStore
andPath
s that are meant to use theprivateStore
may also use it. IIUC, this is also how the cosmos-sdk store is currently implemented. Another way to implement this is to allow modules to have state and makeModule
trait functions non-static by giving them access toself
. Modules can then use their state as theprivateStore
. eg.struct FooModule(FooModulePrivateStore)
.Permissioning
Modules are expected to have exclusive access to their
Path
s.One way to implement this would be to have an
IdentifiableBy<ics24::Identifier>
impl for all modules and use that identifier as the sub-store prefix. The master module is expected to guarantee exclusive access of store module paths to modules.Proxy stores
TBD - implementation details
Concurrency
Rust's preferred way to do concurrency is to use the Monitor pattern with RAII scoped lock guards. One thing to note about this approach is that often there is room for optimizing the granularity of locking.
For example, we could optimize the current implementation of the
Store
(i.e.BaseCoinState
) by using atomic types (e.g.std::sync::atomic::AtomicU64
) for the height and having theRwLock
on theStore
itself. This would allow us to access the height without having to acquire the lock on the store and starve otherStore
readers/writers. So instead of this ->We have this ->
Partitioning
Ideally, we would like each module to be able to concurrently read/write to their path space. This should be possible because we know that modules are guaranteed exclusive access to their store paths, for example, if the storage was backed by a
Vec<u8>
we could partition it and use someunsafe
code to allow multiple modules to write to their own partitions simultaneously without invoking UB. In practice, however, this would require modifying some of the trait methods described earlier.TBD - implementation details
Write-ahead logging (WAL) v/s Shadow-paging
Write-ahead logging is a well-known technique for providing atomicity and durability guarantees in storage systems. The idea is to record all changes to 'stable storage' (i.e. a log) before applying them to the actual data store. The log usually records both redo and undo information and this technique allows for in-place data-store updates.
left-right - a popular Rust concurrency primitive uses a technique similar to WAL for achieving high concurrency reads over a single-writer data structure.
Another technique that provides the same guarantees as WAL but differs in its implementation is shadow-paging.
Note that these backends facilitate further concurrency related optimizations such as those described in Orga's concurrency model or those that rely on account validity predicates.
In the future, it would be nice to have interfaces that support backends that employ both these techniques.
Accounts
Since accounts may be shared between modules, we might want to create an accounts module that exposes an interface for other modules to interact with it. This could also be part of the so-called
ModuleManager
. Also, for a functional account system, we would need anonce
.TBD - implementation details
Beta Was this translation helpful? Give feedback.
All reactions