Search Apps Documentation Source Content File Folder Download Copy Actions Download

/p/gnoswap/version_manager

Directory · 5 Files
README.md Open

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 on runtime.CurrentRealm(). The manager validates it with rlm.IsCurrent() (rejecting spoofed/stale tokens) and identifies the registering version package via rlm.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

  1. Version Registration: All versions should register during init(cur realm)
  2. Interface Compliance: Ensure all versions implement the same domain interface
  3. Storage Compatibility: Design storage schema to be forward/backward compatible
  4. Testing: Test version switching thoroughly before production use
  5. 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() == falseErrSpoofedRealm)
  • 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 realm marker) and validated via rlm.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
  • gno.land/p/gnoswap/store: KVStore with permission-based access control