/p/gnoswap/version_manager
Directory · 5 Files
Version Manager
Runtime version management system for dynamic implementation switching without data migration.
Overview
Version Manager implements a Strategy Pattern-based system that enables hot-swapping between different versioned implementations of the same domain (e.g., v1, v2, v3) while maintaining a unified storage layer. This approach allows seamless upgrades without downtime or migration overhead.
Features
- Zero-Downtime Upgrades: Switch implementations at runtime without service interruption
- Unified Storage: All versions share a single KVStore owned by the domain (proxy) realm
- Domain-Scoped Security: Only authorized packages within the domain path can register
- Hot-Swapping: Instant version switching through dynamic strategy replacement
- Secure by Design: Implementation realms cannot directly modify storage (see Storage Access Model below)
Pattern: Strategy + Plugin Architecture
Usage
Step 1: Define Domain Interface
1// protocol_fee/types.gno
2package protocol_fee
3
4type ProtocolFee interface {
5 SetFeeRatio(ratio uint64) error
6 GetFeeRatio() uint64
7}
Step 2: Create Version Manager
1// protocol_fee/protocol_fee.gno
2package protocol_fee
3
4import "gno.land/p/gnoswap/version_manager"
5import "gno.land/p/gnoswap/store"
6
7var manager version_manager.VersionManager
8
9func init(cur realm) {
10 kvStore := store.NewKVStore(cur.Address())
11
12 manager = version_manager.NewVersionManager(
13 cur.PkgPath(),
14 kvStore,
15 // initializeDomainStoreFn carries the v2 interrealm marker (`_ int, rlm realm`):
16 // the leading 0 surfaces realm-threading at the call site.
17 func(_ int, rlm realm, kv store.KVStore) any {
18 return NewProtocolFeeStore(kv)
19 },
20 )
21}
22
23func GetManager() version_manager.VersionManager {
24 return manager
25}
26
27// RegisterInitializer is the crossing entry point each version package calls.
28// `cur` is the live crossing-frame realm token; it is threaded straight into the
29// version manager (the leading 0 is the v2 sentinel) so the manager can reject
30// spoofed/stale tokens via rlm.IsCurrent() and identify the caller via rlm.Previous().
31func RegisterInitializer(cur realm, initializer func(_ int, rlm realm, store any) any) {
32 if err := manager.RegisterInitializer(0, cur, initializer); err != nil {
33 panic(err)
34 }
35}
36
37// UpgradeImpl switches the active version. Authorization (admin / governance) is
38// enforced here in the /r/ realm; version_manager only rejects spoofed tokens.
39func UpgradeImpl(cur realm, packagePath string) {
40 if err := manager.ChangeImplementation(0, cur, packagePath); err != nil {
41 panic(err)
42 }
43}
Step 3: Implement Versions
1// protocol_fee/v1/v1.gno
2package v1
3
4import "gno.land/r/gnoswap/protocol_fee"
5
6type protocolFeeV1 struct {
7 store any
8}
9
10func init(cur realm) {
11 // Register this version during package initialization.
12 // `cross(cur)` invokes the domain's crossing entry point, which threads the
13 // live realm token into the version manager.
14 protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
15 return &protocolFeeV1{store: store}
16 })
17}
18
19func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
20 // v1 implementation
21}
22
23func (pf *protocolFeeV1) GetFeeRatio() uint64 {
24 // v1 implementation
25}
1// protocol_fee/v2/v2.gno
2package v2
3
4type protocolFeeV2 struct {
5 store any
6}
7
8func init(cur realm) {
9 // Register v2 — inactive until explicitly activated.
10 protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
11 return &protocolFeeV2{store: store}
12 })
13}
14
15func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
16 // v2 improved implementation
17}
18
19func (pf *protocolFeeV2) GetFeeRatio() uint64 {
20 // v2 improved implementation
21}
Step 4: Use Active Implementation
1// client code
2import "gno.land/r/gnoswap/protocol_fee"
3
4func UseFee() {
5 manager := protocol_fee.GetManager()
6 impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee)
7
8 ratio := impl.GetFeeRatio()
9 // Use the active version's implementation
10}
Step 5: Switch Versions at Runtime
1// governance or admin entry point
2func UpgradeToV2(cur realm) {
3 // Hot-swap to v2 — zero downtime. `cross(cur)` enters UpgradeImpl's crossing
4 // frame; UpgradeImpl threads the realm token into the version manager.
5 protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
6}
Workflow
Registration Flow
1. Domain package initializes version manager with KVStore
↓
2. v1 package calls RegisterInitializer (via the domain's crossing wrapper) during `init(cur realm)`
→ Manager validates the realm token (rlm.IsCurrent()) and caller domain path
→ Becomes active implementation
↓
3. v2 package calls RegisterInitializer during `init(cur realm)`
→ Registered for later activation
↓
4. v3 package calls RegisterInitializer during `init(cur realm)`
→ Registered
Version Switching Flow
1. Admin/governance calls ChangeImplementation (via the domain's UpgradeImpl wrapper)
→ Authorization is enforced in the /r/ wrapper
↓
2. Version Manager validates the realm token (rlm.IsCurrent()), rejecting spoofed/stale tokens
↓
3. Version Manager retrieves v2's initializer
↓
4. Executes v2 initializer with shared KVStore
↓
5. Updates currentImplementation pointer to v2
↓
6. v2 is now the active implementation
Storage Access Model
- Domain Ownership: The domain (proxy) realm owns the KVStore and has write permission
- Explicit Realm Threading: Registration/upgrade calls thread the live crossing-frame token (
rlm) into the manager instead of relying onruntime.CurrentRealm(). The manager validates it withrlm.IsCurrent()(rejecting spoofed/stale tokens) and identifies the registering version package viarlm.Previous() - No Direct Permission Grants: Implementation realms do not receive storage permissions directly; the proxy realm drives all storage access
- Security by Design: External callers cannot invoke implementation realms to modify storage
Best Practices
- Version Registration: All versions should register during
init(cur realm) - Interface Compliance: Ensure all versions implement the same domain interface
- Storage Compatibility: Design storage schema to be forward/backward compatible
- Testing: Test version switching thoroughly before production use
- Rollback Support: Keep previous versions registered for quick rollback capability
Error Handling
The package returns errors for:
- A spoofed or stale realm token (
rlm.IsCurrent() == false→ErrSpoofedRealm) - Unauthorized caller attempting to register (not in domain path)
- Duplicate registration of the same package path
- Attempting to switch to an unregistered version
- Invalid initializer function type
Use Cases
Protocol Upgrades
Upgrade DeFi protocol logic without disrupting active users:
1// Upgrade fee calculation algorithm
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
A/B Testing
Test new implementations before full rollout:
1// Switch to experimental version
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/experimental")
3
4// Rollback if issues detected
5protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v1")
Emergency Response
Quickly switch to a patched version during security incidents:
1// Deploy fixed version and immediately activate
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v1_hotfix")
Implementation Notes
- Built on Strategy Pattern for runtime algorithm swapping
- Uses Plugin Architecture for dynamic version loading
- Storage access is driven by the proxy realm; the live realm token is threaded explicitly (the v2
_ int, rlm realmmarker) and validated viarlm.IsCurrent() - No data migration required - all versions share the same storage
- Type assertions required when retrieving current implementation
- Map used for efficient initializer storage and lookup
Limitations
- Type Safety: Requires runtime type assertion to domain interface
- Storage Schema: Requires careful schema design for cross-version compatibility
- Registration Order: First registered version becomes the initial active implementation
- Domain Call Requirement: Implementation functions must be called through domain proxy for storage access
Related Packages
gno.land/p/gnoswap/store: KVStore with permission-based access control