Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rofl: Add Apps query and emit event on instance registration #2159

Merged
merged 1 commit into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions client-sdk/go/modules/rofl/rofl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (

// Queries.
methodApp = types.NewMethodName("rofl.App", AppQuery{})
methodApps = types.NewMethodName("rofl.Apps", nil)
methodAppInstance = types.NewMethodName("rofl.AppInstance", AppInstanceQuery{})
methodAppInstances = types.NewMethodName("rofl.AppInstances", AppQuery{})
methodParameters = types.NewMethodName("rofl.Parameters", nil)
Expand All @@ -41,6 +42,9 @@ type V1 interface {
// App queries the given application configuration.
App(ctx context.Context, round uint64, id AppID) (*AppConfig, error)

// Apps queries all application configurations.
Apps(ctx context.Context, round uint64) ([]*AppConfig, error)

// AppInstance queries a specific registered instance of the given application.
AppInstance(ctx context.Context, round uint64, id AppID, rak types.PublicKey) (*Registration, error)

Expand Down Expand Up @@ -94,6 +98,16 @@ func (a *v1) App(ctx context.Context, round uint64, id AppID) (*AppConfig, error
return &appCfg, nil
}

// Implements V1.
func (a *v1) Apps(ctx context.Context, round uint64) ([]*AppConfig, error) {
var apps []*AppConfig
err := a.rc.Query(ctx, round, methodApps, nil, &apps)
if err != nil {
return nil, err
}
return apps, nil
}

// Implements V1.
func (a *v1) AppInstance(ctx context.Context, round uint64, id AppID, rak types.PublicKey) (*Registration, error) {
var instance Registration
Expand Down Expand Up @@ -191,6 +205,14 @@ func DecodeEvent(event *types.Event) ([]client.DecodedEvent, error) {
for _, ev := range evs {
events = append(events, &Event{AppRemoved: ev})
}
case InstanceRegisteredEventCode:
var evs []*InstanceRegisteredEvent
if err := cbor.Unmarshal(event.Value, &evs); err != nil {
return nil, fmt.Errorf("decode rofl instance registered event value: %w", err)
}
for _, ev := range evs {
events = append(events, &Event{InstanceRegistered: ev})
}
default:
return nil, fmt.Errorf("invalid rofl event code: %v", event.Code)
}
Expand Down
15 changes: 12 additions & 3 deletions client-sdk/go/modules/rofl/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ const (
AppUpdatedEventCode = 2
// AppRemovedEventCode is the event code for the application removed event.
AppRemovedEventCode = 3
// InstanceRegisteredEventCode is the event code for the instance registered event.
InstanceRegisteredEventCode = 4
)

// AppCreatedEvent is an application created event.
Expand All @@ -148,11 +150,18 @@ type AppRemovedEvent struct {
ID AppID `json:"id"`
}

// InstanceRegisteredEvent is an instance registered event.
type InstanceRegisteredEvent struct {
AppID AppID `json:"app_id"`
RAK types.PublicKey `json:"rak"`
}

// Event is a rofl module event.
type Event struct {
AppCreated *AppCreatedEvent
AppUpdated *AppUpdatedEvent
AppRemoved *AppRemovedEvent
AppCreated *AppCreatedEvent
AppUpdated *AppUpdatedEvent
AppRemoved *AppRemovedEvent
InstanceRegistered *InstanceRegisteredEvent
}

// StakeThresholds contains staking thresholds for managing ROFL.
Expand Down
5 changes: 5 additions & 0 deletions runtime-sdk/src/modules/rofl/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::crypto::signature::PublicKey;

use super::{app_id::AppId, MODULE_NAME};

/// Events emitted by the ROFL module.
Expand All @@ -12,4 +14,7 @@ pub enum Event {

#[sdk_event(code = 3)]
AppRemoved { id: AppId },

#[sdk_event(code = 4)]
InstanceRegistered { app_id: AppId, rak: PublicKey },
}
19 changes: 19 additions & 0 deletions runtime-sdk/src/modules/rofl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ pub trait API {
/// Get an application's configuration.
fn get_app(id: app_id::AppId) -> Result<types::AppConfig, Error>;

/// Get all application configurations.
fn get_apps() -> Result<Vec<types::AppConfig>, Error>;

/// Get all registered instances for an application.
fn get_instances(id: app_id::AppId) -> Result<Vec<types::Registration>, Error>;
}
Expand Down Expand Up @@ -147,6 +150,10 @@ impl<Cfg: Config> API for Module<Cfg> {
state::get_app(id).ok_or(Error::UnknownApp)
}

fn get_apps() -> Result<Vec<types::AppConfig>, Error> {
Ok(state::get_apps())
}

fn get_instances(id: app_id::AppId) -> Result<Vec<types::Registration>, Error> {
Ok(state::get_registrations_for_app(id))
}
Expand Down Expand Up @@ -390,6 +397,13 @@ impl<Cfg: Config> Module<Cfg> {
};
state::update_registration(registration)?;

CurrentState::with(|state| {
state.emit_event(Event::InstanceRegistered {
app_id: body.app,
rak: body.ect.capability_tee.rak.into(),
})
});

Ok(())
}

Expand Down Expand Up @@ -638,6 +652,11 @@ impl<Cfg: Config> Module<Cfg> {
Self::get_app(args.id)
}

#[handler(query = "rofl.Apps", expensive)]
fn query_apps<C: Context>(_ctx: &C, _args: ()) -> Result<Vec<types::AppConfig>, Error> {
Self::get_apps()
}

/// Returns a specific registered instance for the given ROFL application.
#[handler(query = "rofl.AppInstance")]
fn query_app_instance<C: Context>(
Expand Down
17 changes: 17 additions & 0 deletions runtime-sdk/src/modules/rofl/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ pub fn get_app(app_id: AppId) -> Option<types::AppConfig> {
})
}

/// Retrieves all application configurations.
pub fn get_apps() -> Vec<types::AppConfig> {
CurrentState::with_store(|store| {
let store = storage::PrefixStore::new(store, &MODULE_NAME);
let apps = storage::TypedStore::new(storage::PrefixStore::new(store, &APPS));
apps.iter()
.map(|(_, cfg): (AppId, types::AppConfig)| cfg)
.collect()
})
}

/// Updates an application configuration.
pub fn set_app(cfg: types::AppConfig) {
CurrentState::with_store(|store| {
Expand Down Expand Up @@ -281,9 +292,15 @@ mod test {
let app = get_app(app_id).expect("application config should be updated");
assert_eq!(app, cfg);

let apps = get_apps();
assert_eq!(apps.len(), 1);
assert_eq!(apps[0], cfg);

remove_app(app_id);
let app = get_app(app_id);
assert!(app.is_none(), "application should have been removed");
let apps = get_apps();
assert_eq!(apps.len(), 0);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions runtime-sdk/src/modules/rofl/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ fn test_management_ops() {
..Default::default()
}
);
let apps = Module::<Config>::get_apps().unwrap();
assert_eq!(apps.len(), 2);
let instances = Module::<Config>::get_instances(app_id).unwrap();
assert_eq!(instances.len(), 0);

Expand Down Expand Up @@ -196,6 +198,8 @@ fn test_management_ops() {
..Default::default()
}
);
let apps = Module::<Config>::get_apps().unwrap();
assert_eq!(apps.len(), 2);

// Remove application. Alice should not be allowed to do it.
let remove = types::Remove { id: app_id };
Expand All @@ -216,6 +220,10 @@ fn test_management_ops() {
assert_eq!(balance, 1_000); // Returned stake for one application.
let balance = Accounts::get_balance(*ADDRESS_APP_STAKE_POOL, Denomination::NATIVE).unwrap();
assert_eq!(balance, 1_000); // One application remains.

// Ensure one app is left.
let apps = Module::<Config>::get_apps().unwrap();
assert_eq!(apps.len(), 1);
}

#[test]
Expand Down
34 changes: 32 additions & 2 deletions tests/e2e/rofl/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"time"

"github.com/oasisprotocol/oasis-core/go/common/quantity"

Expand Down Expand Up @@ -131,7 +132,18 @@ func QueryTest(ctx context.Context, env *scenario.Env) error {
env.Logger.Info("retrieved application config", "app_cfg", appCfg)

if appCfg.ID != exampleAppID {
return fmt.Errorf("expected app ID '%s', got '%s'", exampleAppID, appCfg.ID)
return fmt.Errorf("app: expected app ID '%s', got '%s'", exampleAppID, appCfg.ID)
}

apps, err := rf.Apps(ctx, client.RoundLatest)
if err != nil {
return err
}
if len(apps) != 1 {
return fmt.Errorf("apps: expected 1 application, got %d", len(apps))
}
if apps[0].ID != exampleAppID {
return fmt.Errorf("apps: expected app ID '%s', got '%s'", exampleAppID, apps[0].ID)
}

instances, err := rf.AppInstances(ctx, client.RoundLatest, exampleAppID)
Expand All @@ -153,7 +165,8 @@ func QueryTest(ctx context.Context, env *scenario.Env) error {
}

// Query individual instance and ensure it is equal.
instance, err := rf.AppInstance(ctx, client.RoundLatest, exampleAppID, rak)
var instance *rofl.Registration
instance, err = rf.AppInstance(ctx, client.RoundLatest, exampleAppID, rak)
if err != nil {
return fmt.Errorf("failed to query instance '%s': %w", rak, err)
}
Expand All @@ -167,5 +180,22 @@ func QueryTest(ctx context.Context, env *scenario.Env) error {
return fmt.Errorf("expected %d application instances, got %d", expected, len(instances))
}

// InstanceRegistered events should be emitted on every re-registration.
ch, err := env.Client.WatchEvents(ctx, []client.EventDecoder{rf}, false)
if err != nil {
return err
}
waitCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
ev, err := scenario.WaitForRuntimeEventUntil[*rofl.Event](waitCtx, ch, func(ev *rofl.Event) bool {
return ev.InstanceRegistered != nil
})
if err != nil {
return err
}
if ev.InstanceRegistered == nil || ev.InstanceRegistered.AppID != exampleAppID {
return fmt.Errorf("expected rofl.InstanceRegistered event to be emitted, got: %v", ev)
}

return nil
}
Loading