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() {
10    kvStore := store.NewKVStore("protocol_fee")
11
12    manager = version_manager.NewVersionManager(
13        "gno.land/r/gnoswap/protocol_fee",
14        kvStore,
15        func(kv store.KVStore) any {
16            return NewProtocolFeeStore(kv)
17        },
18    )
19}
20
21func GetManager() version_manager.VersionManager {
22    return manager
23}

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() {
11    // Register this version during package initialization
12    manager := protocol_fee.GetManager()
13    manager.RegisterInitializer(func(store any) any {
14        return &protocolFeeV1{store: store}
15    })
16}
17
18func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
19    // v1 implementation
20}
21
22func (pf *protocolFeeV1) GetFeeRatio() uint64 {
23    // v1 implementation
24}
 1// protocol_fee/v2/v2.gno
 2package v2
 3
 4type protocolFeeV2 struct {
 5    store any
 6}
 7
 8func init() {
 9    // Register v2
10    manager := protocol_fee.GetManager()
11    manager.RegisterInitializer(func(store any) any {
12        return &protocolFeeV2{store: store}
13    })
14}
15
16func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
17    // v2 improved implementation
18}
19
20func (pf *protocolFeeV2) GetFeeRatio() uint64 {
21    // v2 improved implementation
22}

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 function
2func UpgradeToV2() error {
3    manager := protocol_fee.GetManager()
4
5    // Hot-swap to v2 - zero downtime
6    return manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/v2")
7}

Workflow

Registration Flow

1. Domain package initializes version manager with KVStore
   ↓
2. v1 package calls RegisterInitializer during init()
   → Becomes active implementation
   ↓
3. v2 package calls RegisterInitializer during init()
   → Registered
   ↓
4. v3 package calls RegisterInitializer during init()
   → Registered

Version Switching Flow

1. Admin calls ChangeImplementation("path/to/v2")
   ↓
2. Version Manager retrieves v2's initializer
   ↓
3. Executes v2 initializer with shared KVStore
   ↓
4. Updates currentImplementation pointer to v2
   ↓
5. v2 is now the active implementation

Storage Access Model

  • Domain Ownership: The domain (proxy) realm owns the KVStore and has write permission
  • Realm Context Preservation: When domain calls implementation, runtime.CurrentRealm() remains the domain realm
  • No Direct Permission Grants: Implementation realms do not receive storage permissions directly
  • Security by Design: External callers cannot invoke implementation realms to modify storage

Best Practices

  1. Version Registration: All versions should register during init()
  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:

  • 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
2manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/v2")

A/B Testing

Test new implementations before full rollout:

1// Switch to experimental version
2manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/experimental")
3
4// Rollback if issues detected
5manager.ChangeImplementation("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
2manager.ChangeImplementation("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 controlled via realm context (runtime.CurrentRealm())
  • No data migration required - all versions share the same storage
  • Type assertions required when retrieving current implementation
  • AVL tree 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
  • gno.land/p/nt/avl: AVL tree for initializer storage