From 1bd23b7314539217cb00aee9958842c63e359c90 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 13 Oct 2023 15:54:43 -0700 Subject: [PATCH 1/3] tapdb: add unit test to expose issue with global fed cfg In this commit, we add a unit test that will fail without the nxet commit. The issue is that we don't start with the default config, so if one is updated, then we only return 1 config, so we won't properly return both configs. --- fn/iter.go | 12 ++++++ tapdb/universe_federation_test.go | 67 +++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/fn/iter.go b/fn/iter.go index 1c9e81f3e..9faf706cb 100644 --- a/fn/iter.go +++ b/fn/iter.go @@ -37,3 +37,15 @@ func Enumerate[T any](items []T, f func(int, T)) { f(i, item) } } + +// MakeSlice is a generic function short hand for making a slice out of a set +// of elements. This can be used to avoid having to specify the type of the +// slice as well as the types of the elements. +func MakeSlice[T any](items ...T) []T { + ts := make([]T, len(items)) + for i, item := range items { + ts[i] = item + } + + return ts +} diff --git a/tapdb/universe_federation_test.go b/tapdb/universe_federation_test.go index 6f27b7465..c6ece8a9e 100644 --- a/tapdb/universe_federation_test.go +++ b/tapdb/universe_federation_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/clock" @@ -112,3 +113,69 @@ func TestFederationConfigDefault(t *testing.T) { require.NoError(t, err) require.Equal(t, defaultGlobalSyncConfigs, globalConfig) } + +// TestFederationGlobalConfigCRUD tests that we're able to properly update the +// global and local federation configs. +func TestFederationGlobalConfigCRUD(t *testing.T) { + t.Parallel() + + testClock := clock.NewTestClock(time.Now()) + fedDB, _ := newTestFederationDb(t, testClock) + + ctx := context.Background() + + // First, we'll add a new global config, but just for the issuance + // proof type. + newGlobalProof := fn.MakeSlice(&universe.FedGlobalSyncConfig{ + ProofType: universe.ProofTypeIssuance, + AllowSyncInsert: true, + AllowSyncExport: true, + }) + + err := fedDB.UpsertFederationSyncConfig( + ctx, newGlobalProof, nil, + ) + require.NoError(t, err) + + // If we query for the global config, then we should get the issuance + // proof type we just modified, and also the existing global config for + // transfer proof type. + expectedCfgs := append( + newGlobalProof, defaultGlobalSyncConfigs[1], + ) + + dbGlobalCfg, _, err := fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + + require.Equal(t, expectedCfgs, dbGlobalCfg) + + // Next, make the same modification, but for the transfer proof type. + newGlobalTransfer := fn.MakeSlice(&universe.FedGlobalSyncConfig{ + ProofType: universe.ProofTypeTransfer, + AllowSyncInsert: true, + AllowSyncExport: true, + }) + err = fedDB.UpsertFederationSyncConfig( + ctx, newGlobalTransfer, nil, + ) + require.NoError(t, err) + + dbGlobalCfg, _, err = fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + + expectedCfgs = append(newGlobalProof, newGlobalTransfer...) + require.Equal(t, expectedCfgs, dbGlobalCfg) + + // Finally, we should be able to update them both in the same txn. + for _, cfg := range expectedCfgs { + cfg.AllowSyncInsert = false + cfg.AllowSyncExport = false + } + + err = fedDB.UpsertFederationSyncConfig(ctx, expectedCfgs, nil) + require.NoError(t, err) + + dbGlobalCfg, _, err = fedDB.QueryFederationSyncConfigs(ctx) + require.NoError(t, err) + require.Equal(t, expectedCfgs, dbGlobalCfg) +} From 9e22ee716edfc140e614b089da2f8102df671ea9 Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Fri, 13 Oct 2023 14:11:29 -0400 Subject: [PATCH 2/3] tapdb: fix overwriting of default configs In this commit, we fix an issue with the way we returned the global configs. We always assumed that there would be two items in the db. However if we only updated one of them, then only a single version will be returned. This means we'll only return one config, when we should always return two. --- tapdb/universe_federation.go | 37 ++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tapdb/universe_federation.go b/tapdb/universe_federation.go index ae02bb9c5..ee84e6ef2 100644 --- a/tapdb/universe_federation.go +++ b/tapdb/universe_federation.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -12,6 +13,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapdb/sqlc" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/clock" + "golang.org/x/exp/maps" ) type ( @@ -308,8 +310,11 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( var ( readTx UniverseFederationOptions - globalConfigs []*universe.FedGlobalSyncConfig - uniConfigs []*universe.FedUniSyncConfig + globalConfigs []*universe.FedGlobalSyncConfig + globalConfigSet = make( + map[universe.ProofType]*universe.FedGlobalSyncConfig, + ) + uniConfigs []*universe.FedUniSyncConfig ) err := u.db.ExecTx(ctx, &readTx, func(db UniverseServerStore) error { @@ -328,14 +333,16 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( return err default: - // Parse global db sync configs. - globalConfigs = make( - []*universe.FedGlobalSyncConfig, - len(globalDbConfigs), - ) - for i := range globalDbConfigs { - config := globalDbConfigs[i] + // Start with the default global sync configs. This + // ensures we don't always clobber with items in the DB + // that may be blank. + for _, config := range defaultGlobalSyncConfigs { + globalConfigSet[config.ProofType] = config + } + // Parse global db sync configs and overwrite the + // default configs. + for _, config := range globalDbConfigs { proofType, err := universe.ParseStrProofType( config.ProofType, ) @@ -343,12 +350,22 @@ func (u *UniverseFederationDB) QueryFederationSyncConfigs( return err } - globalConfigs[i] = &universe.FedGlobalSyncConfig{ + globalDbConf := &universe.FedGlobalSyncConfig{ ProofType: proofType, AllowSyncInsert: config.AllowSyncInsert, AllowSyncExport: config.AllowSyncExport, } + + //nolint:lll + globalConfigSet[globalDbConf.ProofType] = globalDbConf } + + // Return config options in a stable order. + globalConfigs = maps.Values(globalConfigSet) + sort.Slice(globalConfigs, func(i, j int) bool { + return globalConfigs[i].ProofType < + globalConfigs[j].ProofType + }) } // Query for universe specific sync configs. From 59ee9c1a3aa53063d89dd031424ea7e706b39bdb Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Wed, 11 Oct 2023 22:07:03 -0400 Subject: [PATCH 3/3] tapcli: expose universe sync config settings --- cmd/tapcli/universe.go | 500 ++++++++++++++++++++++++++++++++++++++--- rpcserver.go | 4 + 2 files changed, 470 insertions(+), 34 deletions(-) diff --git a/cmd/tapcli/universe.go b/cmd/tapcli/universe.go index a34b84b36..d09d769fc 100644 --- a/cmd/tapcli/universe.go +++ b/cmd/tapcli/universe.go @@ -6,9 +6,9 @@ import ( "fmt" tap "github.com/lightninglabs/taproot-assets" + "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/taprpc" - "github.com/lightninglabs/taproot-assets/taprpc/universerpc" unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightninglabs/taproot-assets/universe" "github.com/lightningnetwork/lnd/lncfg" @@ -19,14 +19,14 @@ const ( proofTypeName = "proof_type" ) -func getUniverseClient(ctx *cli.Context) (universerpc.UniverseClient, func()) { +func getUniverseClient(ctx *cli.Context) (unirpc.UniverseClient, func()) { conn := getClientConn(ctx, false) cleanUp := func() { conn.Close() } - return universerpc.NewUniverseClient(conn), cleanUp + return unirpc.NewUniverseClient(conn), cleanUp } // TODO(roasbeef): all should be able to connect to remote uni @@ -74,7 +74,7 @@ var universeRootsCommand = cli.Command{ Action: universeRoots, } -func parseUniverseID(ctx *cli.Context, mustParse bool) (*universerpc.ID, error) { +func parseProofType(ctx *cli.Context) (*unirpc.ProofType, error) { proofType, err := universe.ParseStrProofType(ctx.String(proofTypeName)) if err != nil { return nil, err @@ -84,6 +84,19 @@ func parseUniverseID(ctx *cli.Context, mustParse bool) (*universerpc.ID, error) return nil, err } + if rpcProofType == unirpc.ProofType_PROOF_TYPE_UNSPECIFIED { + return nil, fmt.Errorf("invalid proof type") + } + + return &rpcProofType, nil +} + +func parseUniverseID(ctx *cli.Context, mustParse bool) (*unirpc.ID, error) { + rpcProofType, err := parseProofType(ctx) + if err != nil { + return nil, err + } + switch { // Both the asset ID and the group key can't be set. case ctx.IsSet(assetIDName) && ctx.IsSet(groupKeyName): @@ -99,11 +112,11 @@ func parseUniverseID(ctx *cli.Context, mustParse bool) (*universerpc.ID, error) if err != nil { return nil, err } - return &universerpc.ID{ - Id: &universerpc.ID_AssetId{ + return &unirpc.ID{ + Id: &unirpc.ID_AssetId{ AssetId: assetIDBytes, }, - ProofType: rpcProofType, + ProofType: *rpcProofType, }, nil case ctx.IsSet(groupKeyName): @@ -114,11 +127,11 @@ func parseUniverseID(ctx *cli.Context, mustParse bool) (*universerpc.ID, error) return nil, err } - return &universerpc.ID{ - Id: &universerpc.ID_GroupKey{ + return &unirpc.ID{ + Id: &unirpc.ID_GroupKey{ GroupKey: groupKeyBytes, }, - ProofType: rpcProofType, + ProofType: *rpcProofType, }, nil // Neither was set, so we'll return nil. @@ -141,7 +154,7 @@ func universeRoots(ctx *cli.Context) error { // for all the known universe roots. if universeID == nil { universeRoots, err := client.AssetRoots( - ctxc, &universerpc.AssetRootRequest{}, + ctxc, &unirpc.AssetRootRequest{}, ) if err != nil { return err @@ -151,7 +164,7 @@ func universeRoots(ctx *cli.Context) error { return nil } - rootReq := &universerpc.AssetRootQuery{ + rootReq := &unirpc.AssetRootQuery{ Id: universeID, } @@ -198,7 +211,7 @@ func deleteUniverseRoot(ctx *cli.Context) error { return err } - rootReq := &universerpc.DeleteRootQuery{ + rootReq := &unirpc.DeleteRootQuery{ Id: universeID, } @@ -366,7 +379,7 @@ var universeProofQueryCommand = cli.Command{ Action: universeProofQuery, } -func parseAssetKey(ctx *cli.Context) (*universerpc.AssetKey, error) { +func parseAssetKey(ctx *cli.Context) (*unirpc.AssetKey, error) { if !ctx.IsSet(outpointName) || !ctx.IsSet(scriptKeyName) { return nil, fmt.Errorf("outpoint and script key must be set") } @@ -376,14 +389,14 @@ func parseAssetKey(ctx *cli.Context) (*universerpc.AssetKey, error) { return nil, err } - return &universerpc.AssetKey{ - Outpoint: &universerpc.AssetKey_Op{ + return &unirpc.AssetKey{ + Outpoint: &unirpc.AssetKey_Op{ Op: &unirpc.Outpoint{ HashStr: outpoint.Hash.String(), Index: int32(outpoint.Index), }, }, - ScriptKey: &universerpc.AssetKey_ScriptKeyStr{ + ScriptKey: &unirpc.AssetKey_ScriptKeyStr{ ScriptKeyStr: ctx.String(scriptKeyName), }, }, nil @@ -403,7 +416,7 @@ func universeProofQuery(ctx *cli.Context) error { if err != nil { return err } - uProof, err := client.QueryProof(ctxc, &universerpc.UniverseKey{ + uProof, err := client.QueryProof(ctxc, &unirpc.UniverseKey{ Id: universeID, LeafKey: assetKey, }) @@ -478,12 +491,12 @@ func universeProofInsert(ctx *cli.Context) error { return err } - req := &universerpc.AssetProof{ - Key: &universerpc.UniverseKey{ + req := &unirpc.AssetProof{ + Key: &unirpc.UniverseKey{ Id: universeID, LeafKey: assetKey, }, - AssetLeaf: &universerpc.AssetLeaf{ + AssetLeaf: &unirpc.AssetLeaf{ Asset: rpcAsset, IssuanceProof: rawProof, }, @@ -541,9 +554,9 @@ func universeSync(ctx *cli.Context) error { return err } - var targets []*universerpc.SyncTarget + var targets []*unirpc.SyncTarget if universeID != nil { - targets = append(targets, &universerpc.SyncTarget{ + targets = append(targets, &unirpc.SyncTarget{ Id: universeID, }) } @@ -554,7 +567,7 @@ func universeSync(ctx *cli.Context) error { // TODO(roasbeef): add support for full sync - syncResp, err := client.SyncUniverse(ctxc, &universerpc.SyncRequest{ + syncResp, err := client.SyncUniverse(ctxc, &unirpc.SyncRequest{ UniverseHost: ctx.String(universeHostName), SyncTargets: targets, }) @@ -580,6 +593,7 @@ var universeFederationCommand = cli.Command{ universeFederationListCommand, universeFederationAddCommand, universeFederationDelCommand, + universeFederationConfigCommand, }, } @@ -597,7 +611,7 @@ func universeFederationList(ctx *cli.Context) error { defer cleanUp() servers, err := client.ListFederationServers( - ctxc, &universerpc.ListFederationServersRequest{}, + ctxc, &unirpc.ListFederationServersRequest{}, ) if err != nil { return err @@ -636,8 +650,8 @@ func universeFederationAdd(ctx *cli.Context) error { defer cleanUp() resp, err := client.AddFederationServer( - ctxc, &universerpc.AddFederationServerRequest{ - Servers: []*universerpc.UniverseFederationServer{ + ctxc, &unirpc.AddFederationServerRequest{ + Servers: []*unirpc.UniverseFederationServer{ { Host: ctx.String(universeHostName), }, @@ -687,8 +701,8 @@ func universeFederationDel(ctx *cli.Context) error { defer cleanUp() resp, err := client.DeleteFederationServer( - ctxc, &universerpc.DeleteFederationServerRequest{ - Servers: []*universerpc.UniverseFederationServer{ + ctxc, &unirpc.DeleteFederationServerRequest{ + Servers: []*unirpc.UniverseFederationServer{ { Id: int32(ctx.Int(universeServerID)), Host: ctx.String(universeHostName), @@ -704,6 +718,424 @@ func universeFederationDel(ctx *cli.Context) error { return nil } +var universeFederationConfigCommand = cli.Command{ + Name: "config", + ShortName: "c", + Usage: "Change the sync behavior of the local Universe", + Description: ` + Manage the sync behavior of the local Universe. These settings are + defined by the proof type (issuance or transfer), the sync behavior + (insert from remote Universe or export to remote Universe), and the + scope (all assets or specific assets). + `, + Subcommands: []cli.Command{ + universeFederationGlobalConfig, + universeFederationLocalConfig, + universeFederationConfigInfo, + }, +} + +var ( + universeConfigScope = "config_scope" + proofInsertName = "allow_insert" + proofExportName = "allow_export" + universeSyncConfigArgs = []cli.Flag{ + cli.StringFlag{ + Name: proofTypeName, + Usage: "the type of proof", + }, + cli.StringFlag{ + Name: proofInsertName, + Usage: "if true, remote Universes can push proofs to" + + "the local Universe", + }, + cli.StringFlag{ + Name: proofExportName, + Usage: "if true, remote Universes can pull proofs from" + + "the local Universe", + }, + } +) + +func isValidBool(arg string) (bool, error) { + // The cli.BoolFlag works for variables that are false by default, but + // here we don't want a default for this argument. + switch arg { + case "true": + return true, nil + case "false": + return false, nil + default: + return false, fmt.Errorf("invalid bool flag") + } +} + +func parseConfigArgs(ctx *cli.Context) (*bool, *bool, error) { + var ( + insertOpt, exportOpt bool + err error + ) + + if !ctx.IsSet(proofInsertName) && !ctx.IsSet(proofExportName) { + return nil, nil, fmt.Errorf("either insert or export must be " + + "set") + } + + // Parse the insert and export flags, which are not mutually exclusive. + if ctx.IsSet(proofInsertName) { + insertOpt, err = isValidBool(ctx.String(proofInsertName)) + if err != nil { + return nil, nil, fmt.Errorf("invalid proof insert flag") + } + } + + if ctx.IsSet(proofExportName) { + exportOpt, err = isValidBool(ctx.String(proofExportName)) + if err != nil { + return nil, nil, fmt.Errorf("invalid proof export flag") + } + } + + switch { + case ctx.IsSet(proofExportName) && ctx.IsSet(proofInsertName): + return &insertOpt, &exportOpt, nil + + case ctx.IsSet(proofInsertName): + return &insertOpt, nil, nil + + case ctx.IsSet(proofExportName): + return nil, &exportOpt, nil + + default: + return nil, nil, nil + } +} + +var universeFederationGlobalConfig = cli.Command{ + Name: "global", + ShortName: "g", + Usage: "Change the global sync behavior of the local Universe", + Description: ` + Manage the sync behavior of the local Universe that will apply to all + assets by default. Per-asset sync behavior will override global + settings. These settings are defined by the proof type (issuance or + transfer) and the sync behavior (insert from remote Universe or export + to remote Universe). + `, + Flags: universeSyncConfigArgs, + Action: universeFederationUpdateGlobalConfig, +} + +func universeFederationUpdateGlobalConfig(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getUniverseClient(ctx) + defer cleanUp() + + // There is no default proof type, so we need to make sure this + // flag is set. + if !ctx.IsSet(proofTypeName) { + return fmt.Errorf("must specify proof type") + } + + rpcProofType, err := parseProofType(ctx) + if err != nil { + return err + } + + insertOpt, exportOpt, err := parseConfigArgs(ctx) + if err != nil { + return err + } + + // Read the current global config for the matching proof type. + syncConfigs, err := client.QueryFederationSyncConfig( + ctxc, &unirpc.QueryFederationSyncConfigRequest{}, + ) + if err != nil { + return err + } + + // This should never occur, as we always have the default configs. + if syncConfigs.GlobalSyncConfigs == nil { + return fmt.Errorf("no global sync configs found") + } + + currentConfig, err := fn.First( + syncConfigs.GlobalSyncConfigs, + func(cfg *unirpc.GlobalFederationSyncConfig) bool { + return cfg.ProofType == *rpcProofType + }, + ) + + if err != nil { + return fmt.Errorf("no existing config found") + } + + // Modify the existing config and update it on disk. + if insertOpt != nil { + currentConfig.AllowSyncInsert = *insertOpt + } + if exportOpt != nil { + currentConfig.AllowSyncExport = *exportOpt + } + + configReq := &unirpc.SetFederationSyncConfigRequest{ + GlobalSyncConfigs: []*unirpc.GlobalFederationSyncConfig{ + currentConfig, + }, + } + + resp, err := client.SetFederationSyncConfig(ctxc, configReq) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} + +var universeFederationLocalConfig = cli.Command{ + Name: "local", + ShortName: "l", + Usage: "Change the sync behavior of the local Universe for a " + + "specific asset", + Description: ` + Manage the sync behavior of the local Universe for a specific asset. + Local settings will override global settings. These settings are + defined by the proof type (issuance or transfer) and the sync behavior + (insert from remote Universe or export to remote Universe). + `, + Flags: append(universeSyncConfigArgs, + cli.StringFlag{ + Name: assetIDName, + Usage: "the asset ID of the universe to configure", + }, + cli.StringFlag{ + Name: groupKeyName, + Usage: "the group key of the universe to configure", + }), + Action: universeFederationUpdateLocalConfig, +} + +func universeFederationUpdateLocalConfig(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getUniverseClient(ctx) + defer cleanUp() + + universeID, err := parseUniverseID(ctx, true) + if err != nil { + return err + } + + if universeID == nil { + return fmt.Errorf("invalid universe ID") + } + + insertOpt, exportOpt, err := parseConfigArgs(ctx) + if err != nil { + return err + } + + // Read the current local config for the matching Universe if it exists. + syncConfigs, err := client.QueryFederationSyncConfig( + ctxc, &unirpc.QueryFederationSyncConfigRequest{}, + ) + if err != nil { + return err + } + + var localConfig *unirpc.AssetFederationSyncConfig + switch { + case syncConfigs.AssetSyncConfigs == nil: + // Create a new config for this asset. + localConfig = &unirpc.AssetFederationSyncConfig{ + Id: universeID, + } + + default: + // We have some asset-specific configs, so search for one that + // matches the specified asset. + localConfig, err = fn.First( + syncConfigs.AssetSyncConfigs, + func(cfg *unirpc.AssetFederationSyncConfig) bool { + return cfg.Id == universeID + }, + ) + + if err != nil { + // Create a new config for this asset. + localConfig = &unirpc.AssetFederationSyncConfig{ + Id: universeID, + } + } + } + + if insertOpt != nil { + localConfig.AllowSyncInsert = *insertOpt + } + if exportOpt != nil { + localConfig.AllowSyncExport = *exportOpt + } + + configReq := &unirpc.SetFederationSyncConfigRequest{ + AssetSyncConfigs: []*unirpc.AssetFederationSyncConfig{ + localConfig, + }, + } + + resp, err := client.SetFederationSyncConfig(ctxc, configReq) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} + +var universeFederationConfigInfo = cli.Command{ + Name: "info", + ShortName: "i", + Usage: "Get the sync behavior of the local Universe", + Description: ` + Get the sync behavior of the local Universe. These settings are + defined by the proof type (issuance or transfer), the sync behavior + (insert from remote Universe or export to remote Universe), and the + scope (all assets or specific assets). + `, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: universeConfigScope, + Usage: "the scope (global or local) of the config", + }, + cli.StringFlag{ + Name: assetIDName, + Usage: "the asset ID of the universe", + }, + cli.StringFlag{ + Name: groupKeyName, + Usage: "the group key of the universe", + }, + }, + Action: universeFederationGetConfigInfo, +} + +func universeFederationGetConfigInfo(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getUniverseClient(ctx) + defer cleanUp() + + validScopes := []string{"global", "local"} + parseConfigScope := func(ctx *cli.Context) (string, error) { + if !ctx.IsSet(universeConfigScope) { + return "", fmt.Errorf("config scope not specified") + } + + scope := ctx.String(universeConfigScope) + isValid := fn.Any(validScopes, func(s string) bool { + return s == scope + }) + + if !isValid { + return "", fmt.Errorf("invalid config scope") + } + + return scope, nil + } + + // Fetch all configs, and then filter passed on the flags set. + syncConfigs, err := client.QueryFederationSyncConfig( + ctxc, &unirpc.QueryFederationSyncConfigRequest{}, + ) + if err != nil { + return err + } + + if !ctx.IsSet(assetIDName) && !ctx.IsSet(groupKeyName) { + // The default scope is both global and local. + if !ctx.IsSet(universeConfigScope) { + printRespJSON(syncConfigs) + return nil + } + + scope, err := parseConfigScope(ctx) + if err != nil { + return err + } + + switch scope { + case "global": + for _, config := range syncConfigs.GlobalSyncConfigs { + config := config + printRespJSON(config) + } + + return nil + + case "local": + return fmt.Errorf("local scope requires " + + "universe ID fields") + } + } + + // Match configs for both proof types for the specified Universe. + err = ctx.Set(proofTypeName, "issuance") + if err != nil { + return err + } + + uniIdIssuance, err := parseUniverseID(ctx, true) + if err != nil { + return err + } + + err = ctx.Set(proofTypeName, "transfer") + if err != nil { + return err + } + + uniIdTransfer, err := parseUniverseID(ctx, true) + if err != nil { + return err + } + + matchingLocalConfig := func(c *unirpc.AssetFederationSyncConfig) bool { + return c.Id == uniIdIssuance || c.Id == uniIdTransfer + } + + printLocalConfig := func() error { + localConfig := fn.Filter( + syncConfigs.AssetSyncConfigs, matchingLocalConfig, + ) + if len(localConfig) == 0 { + return fmt.Errorf("no matching universe configs found") + } + + for _, config := range localConfig { + config := config + printRespJSON(config) + } + + return nil + } + + // If an asset was specified, the scope can be missing or local. + if !ctx.IsSet(universeConfigScope) { + return printLocalConfig() + } + + scope, err := parseConfigScope(ctx) + if err != nil { + return err + } + + if scope == "global" { + return fmt.Errorf("cannot specify global scope and a specific" + + "asset") + } + + return printLocalConfig() +} + var universeInfoCommand = cli.Command{ Name: "info", ShortName: "i", @@ -719,7 +1151,7 @@ func universeInfo(ctx *cli.Context) error { client, cleanUp := getUniverseClient(ctx) defer cleanUp() - resp, err := client.Info(ctxc, &universerpc.InfoRequest{}) + resp, err := client.Info(ctxc, &unirpc.InfoRequest{}) if err != nil { return err } @@ -749,7 +1181,7 @@ func universeStatsSummaryCommand(ctx *cli.Context) error { client, cleanUp := getUniverseClient(ctx) defer cleanUp() - resp, err := client.UniverseStats(ctxc, &universerpc.StatsRequest{}) + resp, err := client.UniverseStats(ctxc, &unirpc.StatsRequest{}) if err != nil { return err } @@ -836,10 +1268,10 @@ func universeStatsQueryCommand(ctx *cli.Context) error { } } - resp, err := client.QueryAssetStats(ctxc, &universerpc.AssetStatsQuery{ + resp, err := client.QueryAssetStats(ctxc, &unirpc.AssetStatsQuery{ AssetNameFilter: ctx.String(assetName), AssetIdFilter: assetID, - AssetTypeFilter: func() universerpc.AssetTypeFilter { + AssetTypeFilter: func() unirpc.AssetTypeFilter { switch { case ctx.String(assetTypeName) == "normal": return unirpc.AssetTypeFilter_FILTER_ASSET_NORMAL @@ -907,7 +1339,7 @@ func universeEventStatsQueryCommand(ctx *cli.Context) error { client, cleanUp := getUniverseClient(ctx) defer cleanUp() - resp, err := client.QueryEvents(ctxc, &universerpc.QueryEventsRequest{ + resp, err := client.QueryEvents(ctxc, &unirpc.QueryEventsRequest{ StartTimestamp: ctx.Int64(startTime), EndTimestamp: ctx.Int64(endTime), }) diff --git a/rpcserver.go b/rpcserver.go index 1999380b5..b7aa3672e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2758,6 +2758,10 @@ func unmarshalAssetSyncConfig( config *unirpc.AssetFederationSyncConfig) (*universe.FedUniSyncConfig, error) { + if config == nil { + return nil, fmt.Errorf("empty universe sync config") + } + // Parse the universe ID from the RPC form. uniID, err := UnmarshalUniID(config.Id) if err != nil {