Skip to content

Commit

Permalink
feat(cmd/rofl): Add initial support for ROFL app management
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Jul 15, 2024
1 parent f0064d2 commit c72d1ce
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 0 deletions.
306 changes: 306 additions & 0 deletions cmd/rofl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

"github.com/oasisprotocol/oasis-sdk/client-sdk/go/client"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/rofl"

"github.com/oasisprotocol/cli/cmd/common"
cliConfig "github.com/oasisprotocol/cli/config"
)

var (
roflPolicyFn string
roflAdminAddress string

roflCmd = &cobra.Command{
Use: "rofl",
Short: "ROFL app management",
Aliases: []string{"r"},
}

roflCreateCmd = &cobra.Command{
Use: "create <policy.json>",
Short: "Create a new ROFL application",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)
txCfg := common.GetTransactionConfig()
policyFn := args[0]

if npa.Account == nil {
cobra.CheckErr("no accounts configured in your wallet")
}
if npa.ParaTime == nil {
cobra.CheckErr("no ParaTime selected")
}

policy := roflLoadPolicy(policyFn)

// When not in offline mode, connect to the given network endpoint.
ctx := context.Background()
var conn connection.Connection
if !txCfg.Offline {
var err error
conn, err = connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)
}

// Prepare transaction.
tx := rofl.NewCreateTx(nil, &rofl.Create{
Policy: *policy,
})

acc := common.LoadAccount(cfg, npa.AccountName)
sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil)
cobra.CheckErr(err)

var appID rofl.AppID
if !common.BroadcastOrExportTransaction(ctx, npa.ParaTime, conn, sigTx, meta, &appID) {
return
}

fmt.Printf("Created ROFL application: %s\n", appID)
},
}

roflUpdateCmd = &cobra.Command{
Use: "update <app-id> --policy <policy.json> --admin <address>",
Short: "Create a new ROFL application",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)
txCfg := common.GetTransactionConfig()
rawAppID := args[0]
var appID rofl.AppID
if err := appID.UnmarshalText([]byte(rawAppID)); err != nil {
cobra.CheckErr(fmt.Errorf("malformed ROFL app ID: %w", err))
}

if npa.Account == nil {
cobra.CheckErr("no accounts configured in your wallet")
}
if npa.ParaTime == nil {
cobra.CheckErr("no ParaTime selected")
}

if roflPolicyFn == "" || roflAdminAddress == "" {
fmt.Println("You must specify both --policy and --admin.")
return
}

// When not in offline mode, connect to the given network endpoint.
ctx := context.Background()
var conn connection.Connection
if !txCfg.Offline {
var err error
conn, err = connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)
}

updateBody := rofl.Update{
ID: appID,
Policy: *roflLoadPolicy(roflPolicyFn),
}

// Update administrator address.
if roflAdminAddress == "self" {
roflAdminAddress = npa.AccountName
}
var err error
updateBody.Admin, _, err = common.ResolveLocalAccountOrAddress(npa.Network, roflAdminAddress)
if err != nil {
cobra.CheckErr(fmt.Errorf("bad administrator address: %w", err))
}

// Prepare transaction.
tx := rofl.NewUpdateTx(nil, &updateBody)

acc := common.LoadAccount(cfg, npa.AccountName)
sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil)
cobra.CheckErr(err)

common.BroadcastOrExportTransaction(ctx, npa.ParaTime, conn, sigTx, meta, nil)
},
}

roflRemoveCmd = &cobra.Command{
Use: "remove <app-id>",
Short: "Remove an existing ROFL application",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)
txCfg := common.GetTransactionConfig()
rawAppID := args[0]
var appID rofl.AppID
if err := appID.UnmarshalText([]byte(rawAppID)); err != nil {
cobra.CheckErr(fmt.Errorf("malformed ROFL app ID: %w", err))
}

if npa.Account == nil {
cobra.CheckErr("no accounts configured in your wallet")
}
if npa.ParaTime == nil {
cobra.CheckErr("no ParaTime selected")
}

// When not in offline mode, connect to the given network endpoint.
ctx := context.Background()
var conn connection.Connection
if !txCfg.Offline {
var err error
conn, err = connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)
}

// Prepare transaction.
tx := rofl.NewRemoveTx(nil, &rofl.Remove{
ID: appID,
})

acc := common.LoadAccount(cfg, npa.AccountName)
sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil)
cobra.CheckErr(err)

common.BroadcastOrExportTransaction(ctx, npa.ParaTime, conn, sigTx, meta, nil)
},
}

roflShowCmd = &cobra.Command{
Use: "show <app-id>",
Short: "Show information about a ROFL application",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)
rawAppID := args[0]
var appID rofl.AppID
if err := appID.UnmarshalText([]byte(rawAppID)); err != nil {
cobra.CheckErr(fmt.Errorf("malformed ROFL app ID: %w", err))
}

// Establish connection with the target network.
ctx := context.Background()
conn, err := connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)

appCfg, err := conn.Runtime(npa.ParaTime).ROFL.App(ctx, client.RoundLatest, appID)
cobra.CheckErr(err)

fmt.Printf("App ID: %s\n", appCfg.ID)
fmt.Printf("Admin: ")
switch appCfg.Admin {
case nil:
fmt.Printf("none\n")
default:
fmt.Printf("%s\n", *appCfg.Admin)
}
stakedAmnt := helpers.FormatParaTimeDenomination(npa.ParaTime, appCfg.Stake)
fmt.Printf("Staked amount: %s\n", stakedAmnt)
fmt.Printf("Policy:\n")
policyJSON, _ := json.MarshalIndent(appCfg.Policy, " ", " ")
fmt.Printf(" %s\n", string(policyJSON))

fmt.Println()
fmt.Printf("=== Instances ===\n")

appInstances, err := conn.Runtime(npa.ParaTime).ROFL.AppInstances(ctx, client.RoundLatest, appID)
cobra.CheckErr(err)

if len(appInstances) > 0 {
for _, ai := range appInstances {
fmt.Printf("- RAK: %s\n", ai.RAK)
fmt.Printf(" Node ID: %s\n", ai.NodeID)
fmt.Printf(" Expiration: %d\n", ai.Expiration)
}
} else {
fmt.Println("No registered app instances.")
}
},
}

roflTrustRootCmd = &cobra.Command{
Use: "trust-root",
Short: "Show a recent trust root for a ROFL application",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)

// Establish connection with the target network.
ctx := context.Background()
conn, err := connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)

// Fetch latest consensus block.
height, err := common.GetActualHeight(
ctx,
conn.Consensus(),
)
cobra.CheckErr(err)

blk, err := conn.Consensus().GetBlock(ctx, height)
cobra.CheckErr(err)

// TODO: Support different output formats.
fmt.Printf("TrustRoot {\n")
fmt.Printf(" height: %d,\n", height)
fmt.Printf(" hash: \"%s\".into(),\n", blk.Hash)
fmt.Printf(" runtime_id: \"%s\".into(),\n", npa.ParaTime.ID)
fmt.Printf(" chain_context: \"%s\".to_string(),\n", npa.Network.ChainContext)
fmt.Printf("}\n")
},
}
)

func roflLoadPolicy(fn string) *rofl.AppAuthPolicy {
// Load app policy.
rawPolicy, err := os.ReadFile(fn)
cobra.CheckErr(err)

// Parse policy.
var policy rofl.AppAuthPolicy
if err = json.Unmarshal(rawPolicy, &policy); err != nil {
cobra.CheckErr(fmt.Errorf("malformed ROFL app policy: %w", err))
}
return &policy
}

func init() {
f := flag.NewFlagSet("", flag.ContinueOnError)
f.StringVar(&roflPolicyFn, "policy", "", "set the ROFL application policy")
f.StringVar(&roflAdminAddress, "admin", "", "set the administrator address")

roflCreateCmd.Flags().AddFlagSet(common.SelectorFlags)
roflCreateCmd.Flags().AddFlagSet(common.RuntimeTxFlags)

roflUpdateCmd.Flags().AddFlagSet(common.SelectorFlags)
roflUpdateCmd.Flags().AddFlagSet(common.RuntimeTxFlags)
roflUpdateCmd.Flags().AddFlagSet(f)

roflRemoveCmd.Flags().AddFlagSet(common.SelectorFlags)
roflRemoveCmd.Flags().AddFlagSet(common.RuntimeTxFlags)

roflShowCmd.Flags().AddFlagSet(common.SelectorFlags)

roflTrustRootCmd.Flags().AddFlagSet(common.SelectorFlags)
roflTrustRootCmd.Flags().AddFlagSet(common.HeightFlag)

roflCmd.AddCommand(roflCreateCmd)
roflCmd.AddCommand(roflUpdateCmd)
roflCmd.AddCommand(roflRemoveCmd)
roflCmd.AddCommand(roflShowCmd)
roflCmd.AddCommand(roflTrustRootCmd)
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ func init() {
rootCmd.AddCommand(addressBookCmd)
rootCmd.AddCommand(contractCmd)
rootCmd.AddCommand(txCmd)
rootCmd.AddCommand(roflCmd)
}
1 change: 1 addition & 0 deletions examples/setup/first-run.out
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Available Commands:
help Help about any command
network Consensus layer operations
paratime ParaTime layer operations
rofl ROFL app management
transaction Raw transaction operations
wallet Manage accounts in the local wallet

Expand Down

0 comments on commit c72d1ce

Please sign in to comment.