From 5bac04652c2c6864a4e8c94922246dd13bb452f5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Fri, 7 Mar 2025 15:23:57 -0500 Subject: [PATCH 1/7] netdeploy: add KmdJSONOverride --- daemon/kmd/config/config.go | 16 +++++++ netdeploy/networkTemplate.go | 47 +++++++++++++++++-- netdeploy/remote/nodeConfig.go | 1 + .../testdata/nettemplates/TwoNodes50Each.json | 6 ++- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/daemon/kmd/config/config.go b/daemon/kmd/config/config.go index 57a12ba5eb..235935950a 100644 --- a/daemon/kmd/config/config.go +++ b/daemon/kmd/config/config.go @@ -69,6 +69,11 @@ type ScryptParams struct { ScryptP int `json:"scrypt_p"` } +// DefaultConfig returns the default KMDConfig +func DefaultConfig(dataDir string) KMDConfig { + return defaultConfig(dataDir) +} + // defaultConfig returns the default KMDConfig func defaultConfig(dataDir string) KMDConfig { return KMDConfig{ @@ -121,3 +126,14 @@ func LoadKMDConfig(dataDir string) (cfg KMDConfig, err error) { err = cfg.Validate() return } + +// SaveKMDConfig writes the kmd configuration to disk +func SaveKMDConfig(dataDir string, cfg KMDConfig) error { + err := cfg.Validate() + if err != nil { + return err + } + configFilename := filepath.Join(dataDir, kmdConfigFilename) + + return codecs.SaveObjectToFile(configFilename, cfg, true) +} diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 865edf6ce5..3bfc1a6815 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -29,6 +29,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + kmdconfig "github.com/algorand/go-algorand/daemon/kmd/config" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/gen" "github.com/algorand/go-algorand/libgoal" @@ -131,10 +132,27 @@ func (t NetworkTemplate) createNodeDirectories(targetFolder string, binDir strin } } + var kmdDir string + if len(cfg.KmdJSONOverride) > 0 { + kmdDir = filepath.Join(nodeDir, libgoal.DefaultKMDDataDir) + err = os.MkdirAll(kmdDir, 0700) // kmd requires 700 permissions + if err != nil { + return + } + err = createKMDConfigFile(cfg, kmdDir) + if err != nil { + return + } + } + if importKeys && hasWallet { var client libgoal.Client - client, err = libgoal.MakeClientWithBinDir(binDir, nodeDir, "", libgoal.KmdClient) - if err != nil { + if client, err = libgoal.MakeClientFromConfig(libgoal.ClientConfig{ + AlgodDataDir: nodeDir, + KMDDataDir: kmdDir, + CacheDir: "", + BinDir: binDir, + }, libgoal.KmdClient); err != nil { return } _, err = client.CreateWallet(libgoal.UnencryptedWalletName, nil, crypto.MasterDerivationKey{}) @@ -241,12 +259,21 @@ func (t NetworkTemplate) Validate() error { return fmt.Errorf("invalid template: at least one relay is required when more than a single node presents") } - // Validate JSONOverride decoding + // Validate ConfigJSONOverride decoding for _, cfg := range t.Nodes { local := config.GetDefaultLocal() err := decodeJSONOverride(cfg.ConfigJSONOverride, &local) if err != nil { - return fmt.Errorf("invalid template: unable to decode JSONOverride: %w", err) + return fmt.Errorf("invalid template: unable to decode ConfigJSONOverride: %w", err) + } + } + + // Validate KmdJSONOverride decoding + for _, cfg := range t.Nodes { + kmdconf := kmdconfig.KMDConfig{} + err := decodeJSONOverride(cfg.KmdJSONOverride, &kmdconf) + if err != nil { + return fmt.Errorf("invalid template: unable to decode KmdJSONOverride: %w", err) } } @@ -293,7 +320,7 @@ func countRelayNodes(nodeCfgs []remote.NodeConfigGoal) (relayCount int) { return } -func decodeJSONOverride(override string, cfg *config.Local) error { +func decodeJSONOverride[T any](override string, cfg *T) error { if override != "" { reader := strings.NewReader(override) dec := json.NewDecoder(reader) @@ -340,3 +367,13 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in return cfg, cfg.SaveToFile(configFile) } + +func createKMDConfigFile(node remote.NodeConfigGoal, kmdDir string) error { + cfg := kmdconfig.DefaultConfig(kmdDir) + err := decodeJSONOverride(node.KmdJSONOverride, &cfg) + if err != nil { + return err + } + + return kmdconfig.SaveKMDConfig(kmdDir, cfg) +} diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index abe257b1e4..5e71c2b362 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -63,5 +63,6 @@ type NodeConfigGoal struct { P2PPeerID string `json:",omitempty"` DeadlockDetection int `json:"-"` ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete + KmdJSONOverride string `json:",omitempty"` // Raw json to merge into default kmd-config.json PeerList string `json:",omitempty"` // Semicolon separated list of peers to connect to. Only applicable for non-relays } diff --git a/test/testdata/nettemplates/TwoNodes50Each.json b/test/testdata/nettemplates/TwoNodes50Each.json index 7cdefb47ef..12d851fa66 100644 --- a/test/testdata/nettemplates/TwoNodes50Each.json +++ b/test/testdata/nettemplates/TwoNodes50Each.json @@ -22,14 +22,16 @@ "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } - ] + ], + "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" }, { "Name": "Node", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } - ] + ], + "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" } ] } From aa4fc05c88af12e1363dc916dafd0cd4d6f40fd8 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Fri, 7 Mar 2025 15:38:29 -0500 Subject: [PATCH 2/7] add TwoNodes50EachFuture.json --- test/testdata/nettemplates/TwoNodes50EachFuture.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/testdata/nettemplates/TwoNodes50EachFuture.json b/test/testdata/nettemplates/TwoNodes50EachFuture.json index 12a9b0223e..d10a14c884 100644 --- a/test/testdata/nettemplates/TwoNodes50EachFuture.json +++ b/test/testdata/nettemplates/TwoNodes50EachFuture.json @@ -23,14 +23,16 @@ "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } - ] + ], + "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" }, { "Name": "Node", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } - ] + ], + "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" } ] } From a27b0efe5413c189b31cace336e3f416ddcb4b19 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Fri, 7 Mar 2025 16:03:45 -0500 Subject: [PATCH 3/7] Fix unit tests --- netdeploy/networkTemplates_test.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go index 130667f036..7c35a7eaf3 100644 --- a/netdeploy/networkTemplates_test.go +++ b/netdeploy/networkTemplates_test.go @@ -235,7 +235,21 @@ func TestDevModeValidate(t *testing.T) { }, }, } - require.ErrorContains(t, tmpl.Validate(), "unable to decode JSONOverride") + require.ErrorContains(t, tmpl.Validate(), "unable to decode ConfigJSONOverride") + }) + + t.Run("KmdJSONOverride does not parse", func(t *testing.T) { + t.Parallel() + tmpl := NetworkTemplate{ + Genesis: devmodeGenesis, + Nodes: []remote.NodeConfigGoal{ + { + IsRelay: false, + KmdJSONOverride: "DOES NOT PARSE", + }, + }, + } + require.ErrorContains(t, tmpl.Validate(), "unable to decode KmdJSONOverride") }) t.Run("ConfigJSONOverride unknown key", func(t *testing.T) { @@ -252,6 +266,20 @@ func TestDevModeValidate(t *testing.T) { require.ErrorContains(t, tmpl.Validate(), "json: unknown field \"Unknown Key\"") }) + t.Run("KmdJSONOverride unknown key", func(t *testing.T) { + t.Parallel() + tmpl := NetworkTemplate{ + Genesis: devmodeGenesis, + Nodes: []remote.NodeConfigGoal{ + { + IsRelay: false, + KmdJSONOverride: "{\"Unknown Key\": \"Valid JSON\"}", + }, + }, + } + require.ErrorContains(t, tmpl.Validate(), "json: unknown field \"Unknown Key\"") + }) + t.Run("Valid multi-node DevMode", func(t *testing.T) { t.Parallel() tmpl := NetworkTemplate{ From 168cfa151b6452aeec1fb0c921adb90d457a79a3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 11 Mar 2025 15:22:22 -0400 Subject: [PATCH 4/7] CR: remove KmdJSONOverride, add LibGoalFixture override --- netdeploy/network.go | 7 +++++ netdeploy/networkTemplate.go | 30 ++++++++----------- netdeploy/networkTemplates_test.go | 28 ----------------- netdeploy/remote/nodeConfig.go | 1 - .../features/transactions/asset_test.go | 2 ++ test/framework/fixtures/libgoalFixture.go | 10 ++++++- .../testdata/nettemplates/TwoNodes50Each.json | 6 ++-- .../nettemplates/TwoNodes50EachFuture.json | 6 ++-- 8 files changed, 35 insertions(+), 55 deletions(-) diff --git a/netdeploy/network.go b/netdeploy/network.go index 6951969031..ec93b3800e 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -78,6 +78,13 @@ func OverrideConsensusVersion(ver protocol.ConsensusVersion) TemplateOverride { } } +// OverrideKmdConfig changes the KMD config. +func OverrideKmdConfig(kmdConfig TemplateKMDConfig) TemplateOverride { + return func(template *NetworkTemplate) { + template.kmdConfig = kmdConfig + } +} + // CreateNetworkFromTemplate uses the specified template to deploy a new private network // under the specified root directory. func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, binDir string, importKeys bool, nodeExitCallback nodecontrol.AlgodExitErrorCallback, consensus config.ConsensusProtocols, overrides ...TemplateOverride) (Network, error) { diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 3bfc1a6815..791ca45fc4 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -43,6 +43,15 @@ type NetworkTemplate struct { Genesis gen.GenesisData Nodes []remote.NodeConfigGoal Consensus config.ConsensusProtocols + kmdConfig TemplateKMDConfig +} + +// TemplateKMDConfig is a subset of the kmd configuration that can be overridden in the network template +// by using OverrideKmdConfig TemplateOverride opts. +// The reason why config.KMDConfig cannot be used directly is that it contains DataDir field which is +// is not known until the template instantiation. +type TemplateKMDConfig struct { + SessionLifetimeSecs uint64 } var defaultNetworkTemplate = NetworkTemplate{ @@ -133,13 +142,13 @@ func (t NetworkTemplate) createNodeDirectories(targetFolder string, binDir strin } var kmdDir string - if len(cfg.KmdJSONOverride) > 0 { + if (t.kmdConfig != TemplateKMDConfig{}) { kmdDir = filepath.Join(nodeDir, libgoal.DefaultKMDDataDir) err = os.MkdirAll(kmdDir, 0700) // kmd requires 700 permissions if err != nil { return } - err = createKMDConfigFile(cfg, kmdDir) + err = createKMDConfigFile(t.kmdConfig, kmdDir) if err != nil { return } @@ -268,15 +277,6 @@ func (t NetworkTemplate) Validate() error { } } - // Validate KmdJSONOverride decoding - for _, cfg := range t.Nodes { - kmdconf := kmdconfig.KMDConfig{} - err := decodeJSONOverride(cfg.KmdJSONOverride, &kmdconf) - if err != nil { - return fmt.Errorf("invalid template: unable to decode KmdJSONOverride: %w", err) - } - } - // Follow nodes cannot be relays // Relays cannot have peer list for _, cfg := range t.Nodes { @@ -368,12 +368,8 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in return cfg, cfg.SaveToFile(configFile) } -func createKMDConfigFile(node remote.NodeConfigGoal, kmdDir string) error { +func createKMDConfigFile(kmdConfig TemplateKMDConfig, kmdDir string) error { cfg := kmdconfig.DefaultConfig(kmdDir) - err := decodeJSONOverride(node.KmdJSONOverride, &cfg) - if err != nil { - return err - } - + cfg.SessionLifetimeSecs = kmdConfig.SessionLifetimeSecs return kmdconfig.SaveKMDConfig(kmdDir, cfg) } diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go index 7c35a7eaf3..13aff8d842 100644 --- a/netdeploy/networkTemplates_test.go +++ b/netdeploy/networkTemplates_test.go @@ -238,20 +238,6 @@ func TestDevModeValidate(t *testing.T) { require.ErrorContains(t, tmpl.Validate(), "unable to decode ConfigJSONOverride") }) - t.Run("KmdJSONOverride does not parse", func(t *testing.T) { - t.Parallel() - tmpl := NetworkTemplate{ - Genesis: devmodeGenesis, - Nodes: []remote.NodeConfigGoal{ - { - IsRelay: false, - KmdJSONOverride: "DOES NOT PARSE", - }, - }, - } - require.ErrorContains(t, tmpl.Validate(), "unable to decode KmdJSONOverride") - }) - t.Run("ConfigJSONOverride unknown key", func(t *testing.T) { t.Parallel() tmpl := NetworkTemplate{ @@ -266,20 +252,6 @@ func TestDevModeValidate(t *testing.T) { require.ErrorContains(t, tmpl.Validate(), "json: unknown field \"Unknown Key\"") }) - t.Run("KmdJSONOverride unknown key", func(t *testing.T) { - t.Parallel() - tmpl := NetworkTemplate{ - Genesis: devmodeGenesis, - Nodes: []remote.NodeConfigGoal{ - { - IsRelay: false, - KmdJSONOverride: "{\"Unknown Key\": \"Valid JSON\"}", - }, - }, - } - require.ErrorContains(t, tmpl.Validate(), "json: unknown field \"Unknown Key\"") - }) - t.Run("Valid multi-node DevMode", func(t *testing.T) { t.Parallel() tmpl := NetworkTemplate{ diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 5e71c2b362..abe257b1e4 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -63,6 +63,5 @@ type NodeConfigGoal struct { P2PPeerID string `json:",omitempty"` DeadlockDetection int `json:"-"` ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete - KmdJSONOverride string `json:",omitempty"` // Raw json to merge into default kmd-config.json PeerList string `json:",omitempty"` // Semicolon separated list of peers to connect to. Only applicable for non-relays } diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index b83860bdd1..06e9ad665f 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -682,6 +682,8 @@ func TestAssetGroupCreateSendDestroy(t *testing.T) { txSend, err = client1.MakeUnsignedAssetSendTx(assetID3, 0, account1, "", "") _, err = helperFillSignBroadcast(client1, wh1, account1, txSend, err) a.Error(err) + + a.FailNow("test") } func TestAssetSend(t *testing.T) { diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index c6659b33be..9af69b3c8a 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -127,7 +127,15 @@ func (f *LibGoalFixture) setup(test TestingTB, testName string, templateFile str importKeys := false // Don't automatically import root keys when creating folders, we'll import on-demand file, err := os.Open(templateFile) f.failOnError(err, "Template file could not be opened: %v") - network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, overrides...) + defer file.Close() + + // Override the kmd session lifetime to 5 minutes to prevent kmd wallet handles from expiring + kmdConfOverride := netdeploy.OverrideKmdConfig(netdeploy.TemplateKMDConfig{SessionLifetimeSecs: 300}) + // copy overrides to prevent caller's data from being modified + extraOverrides := append([]netdeploy.TemplateOverride(nil), overrides...) + extraOverrides = append(extraOverrides, kmdConfOverride) + + network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, extraOverrides...) f.failOnError(err, "CreateNetworkFromTemplate failed: %v") f.network = network diff --git a/test/testdata/nettemplates/TwoNodes50Each.json b/test/testdata/nettemplates/TwoNodes50Each.json index 12d851fa66..7cdefb47ef 100644 --- a/test/testdata/nettemplates/TwoNodes50Each.json +++ b/test/testdata/nettemplates/TwoNodes50Each.json @@ -22,16 +22,14 @@ "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } - ], - "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" + ] }, { "Name": "Node", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } - ], - "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" + ] } ] } diff --git a/test/testdata/nettemplates/TwoNodes50EachFuture.json b/test/testdata/nettemplates/TwoNodes50EachFuture.json index d10a14c884..12a9b0223e 100644 --- a/test/testdata/nettemplates/TwoNodes50EachFuture.json +++ b/test/testdata/nettemplates/TwoNodes50EachFuture.json @@ -23,16 +23,14 @@ "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } - ], - "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" + ] }, { "Name": "Node", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } - ], - "KmdJSONOverride": "{\"session_lifetime_secs\": 300}" + ] } ] } From ce4ba1fbe57b426a61e420a43fcb698bb2071a4f Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 11 Mar 2025 15:23:43 -0400 Subject: [PATCH 5/7] remove debugging code --- test/e2e-go/features/transactions/asset_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 06e9ad665f..b83860bdd1 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -682,8 +682,6 @@ func TestAssetGroupCreateSendDestroy(t *testing.T) { txSend, err = client1.MakeUnsignedAssetSendTx(assetID3, 0, account1, "", "") _, err = helperFillSignBroadcast(client1, wh1, account1, txSend, err) a.Error(err) - - a.FailNow("test") } func TestAssetSend(t *testing.T) { From e2b21ed3fef35681d8f975e15cf7ff2a1805d1cc Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 11 Mar 2025 16:34:44 -0400 Subject: [PATCH 6/7] group TemplateKMDConfig ops --- netdeploy/networkTemplate.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 791ca45fc4..4f1c6c54fa 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -54,6 +54,11 @@ type TemplateKMDConfig struct { SessionLifetimeSecs uint64 } +func (c TemplateKMDConfig) apply(cfg kmdconfig.KMDConfig) kmdconfig.KMDConfig { + cfg.SessionLifetimeSecs = c.SessionLifetimeSecs + return cfg +} + var defaultNetworkTemplate = NetworkTemplate{ Genesis: gen.DefaultGenesis, } @@ -369,7 +374,6 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in } func createKMDConfigFile(kmdConfig TemplateKMDConfig, kmdDir string) error { - cfg := kmdconfig.DefaultConfig(kmdDir) - cfg.SessionLifetimeSecs = kmdConfig.SessionLifetimeSecs + cfg := kmdConfig.apply(kmdconfig.DefaultConfig(kmdDir)) return kmdconfig.SaveKMDConfig(kmdDir, cfg) } From e5f179aa3599d7efe349420fec38841ce5fbcb16 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 12 Mar 2025 10:42:15 -0400 Subject: [PATCH 7/7] Update netdeploy/networkTemplate.go Co-authored-by: cce <51567+cce@users.noreply.github.com> --- netdeploy/networkTemplate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 4f1c6c54fa..92a7691e16 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -43,7 +43,7 @@ type NetworkTemplate struct { Genesis gen.GenesisData Nodes []remote.NodeConfigGoal Consensus config.ConsensusProtocols - kmdConfig TemplateKMDConfig + kmdConfig TemplateKMDConfig // set by OverrideKmdConfig } // TemplateKMDConfig is a subset of the kmd configuration that can be overridden in the network template