From 151290f7a3a9308852e9f4cc4828b70b8e268577 Mon Sep 17 00:00:00 2001 From: MKVEERENDRA Date: Sun, 17 Nov 2024 17:16:58 +0530 Subject: [PATCH 1/3] Fix: Solve issue #331 by adding FirstEvent support in FireFly config Signed-off-by: MKVEERENDRA --- cmd/start.go | 1 + .../blockchain/ethereum/ethtypes/types.go | 6 +- internal/stacks/stack_manager.go | 154 ++++++++++-------- 3 files changed, 93 insertions(+), 68 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 95e0f496..ebd78fcb 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -104,5 +104,6 @@ This command will start a stack and run it in the background. func init() { startCmd.Flags().BoolVarP(&startOptions.NoRollback, "no-rollback", "b", false, "Do not automatically rollback changes if first time setup fails") + startCmd.Flags().StringVarP(&startOptions.FirstEvent, "first-event", "f", "0", "Specify the starting block for event processing (default: 0)") rootCmd.AddCommand(startCmd) } diff --git a/internal/blockchain/ethereum/ethtypes/types.go b/internal/blockchain/ethereum/ethtypes/types.go index 4e8bb080..b73c6d10 100644 --- a/internal/blockchain/ethereum/ethtypes/types.go +++ b/internal/blockchain/ethereum/ethtypes/types.go @@ -19,7 +19,11 @@ package ethtypes type CompiledContracts struct { Contracts map[string]*CompiledContract `json:"contracts"` } - +// StartOptions holds the options for starting Firefly CLI +type StartOptions struct { + NoRollback bool + FirstEvent string // "0", "newest", or a specific block number +} type CompiledContract struct { Name string `json:"name"` ABI interface{} `json:"abi"` diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index 687de986..a8d8e861 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -611,44 +611,52 @@ func (s *StackManager) createMember(id string, index int, options *types.InitOpt func (s *StackManager) StartStack(options *types.StartOptions) (messages []string, err error) { fmt.Printf("starting FireFly stack '%s'... ", s.Stack.Name) - // Check to make sure all of our ports are available + + // Validate the FirstEvent value + if options.FirstEvent != "0" && options.FirstEvent != "newest" { + if _, err := strconv.Atoi(options.FirstEvent); err != nil { + return messages, fmt.Errorf("invalid FirstEvent value: %s", options.FirstEvent) + } + } + + // Check port availability err = s.checkPortsAvailable() if err != nil { return messages, err } + hasBeenRun, err := s.Stack.HasRunBefore() if err != nil { return messages, err } if !hasBeenRun { - setupMessages, err := s.runFirstTimeSetup(options) - messages = append(messages, setupMessages...) + // Pass FirstEvent to the first-time setup + setupMessages, err := s.runFirstTimeSetup(&types.StartOptions{ + NoRollback: false, // Example + FirstEvent: "newest", // Or the user-defined value + }) + messages = append(messages, setupMessages...) if err != nil { - // Something bad happened during setup + // Handle rollback if necessary if options.NoRollback { return messages, err } else { - // Rollback changes s.Log.Error(fmt.Errorf("an error occurred - rolling back changes")) resetErr := s.ResetStack() - - var finalErr error - if resetErr != nil { - finalErr = fmt.Errorf("%s - error resetting stack: %s", err.Error(), resetErr.Error()) - } else { - finalErr = fmt.Errorf("%s - all changes rolled back", err.Error()) + return messages, fmt.Errorf("%s - error resetting stack: %s", err.Error(), resetErr.Error()) } - - return messages, finalErr + return messages, fmt.Errorf("%s - all changes rolled back", err.Error()) } } } else { + // Standard startup sequence err = s.runStartupSequence(false) if err != nil { return messages, err } } + return messages, s.ensureFireflyNodesUp(true) } @@ -940,61 +948,73 @@ func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages newConfig.Namespaces.Predefined[0].Plugins = append(newConfig.Namespaces.Predefined[0].Plugins, types.FFEnumArrayToStrings(s.Stack.TokenProviders)...) + + var contractDeploymentResult *types.ContractDeploymentResult - if s.Stack.MultipartyEnabled { - if s.Stack.ContractAddress == "" { - // TODO: This code assumes that there is only one plugin instance per type. When we add support for - // multiple namespaces, this code will likely have to change a lot - s.Log.Info("deploying FireFly smart contracts") - contractDeploymentResult, err = s.blockchainProvider.DeployFireFlyContract() - if err != nil { - return messages, err - } - if contractDeploymentResult != nil { - if contractDeploymentResult.Message != "" { - messages = append(messages, contractDeploymentResult.Message) - } - s.Stack.State.DeployedContracts = append(s.Stack.State.DeployedContracts, contractDeploymentResult.DeployedContract) - } - } - } - - for _, member := range s.Stack.Members { - orgConfig := s.blockchainProvider.GetOrgConfig(s.Stack, member) - newConfig.Namespaces.Predefined[0].DefaultKey = orgConfig.Key - if s.Stack.MultipartyEnabled { - var contractLocation interface{} - if s.Stack.ContractAddress != "" { - contractLocation = map[string]interface{}{ - "address": s.Stack.ContractAddress, - } - } else { - contractLocation = contractDeploymentResult.DeployedContract.Location - } - options := make(map[string]interface{}) - if s.Stack.CustomPinSupport { - options["customPinSupport"] = true - } - - newConfig.Namespaces.Predefined[0].Multiparty = &types.MultipartyConfig{ - Enabled: true, - Org: orgConfig, - Node: &types.NodeConfig{ - Name: member.NodeName, - }, - Contract: []*types.ContractConfig{ - { - Location: contractLocation, - FirstEvent: "0", - Options: options, - }, - }, - } - } - - if err := s.patchFireFlyCoreConfigs(configDir, member, newConfig); err != nil { - return messages, err - } + if s.Stack.MultipartyEnabled { + if s.Stack.ContractAddress == "" { + s.Log.Info("deploying FireFly smart contracts") + contractDeploymentResult, err = s.blockchainProvider.DeployFireFlyContract() + if err != nil { + return messages, err + } + } + } + + for _, member := range s.Stack.Members { + orgConfig := s.blockchainProvider.GetOrgConfig(s.Stack, member) + + // Default to "0" if FirstEvent is not provided + firstEvent := "0" + if options.FirstEvent != "" { + firstEvent = options.FirstEvent + } + + var contractLocation interface{} + if s.Stack.ContractAddress != "" { + contractLocation = map[string]interface{}{ + "address": s.Stack.ContractAddress, + } + } else { + contractLocation = contractDeploymentResult.DeployedContract.Location + } + + optionsMap := make(map[string]interface{}) + if s.Stack.CustomPinSupport { + optionsMap["customPinSupport"] = true + } + + newConfig := &types.FireflyConfig{ + Namespaces: &types.NamespacesConfig{ + Default: "default", + Predefined: []*types.Namespace{ + { + Name: "default", + Description: "Default predefined namespace", + Plugins: []string{"database0", "blockchain0", "dataexchange0", "sharedstorage0"}, + Multiparty: &types.MultipartyConfig{ + Enabled: true, + Org: orgConfig, + Node: &types.NodeConfig{ + Name: member.NodeName, + }, + Contract: []*types.ContractConfig{ + { + Location: contractLocation, + FirstEvent: firstEvent, // Now configurable + Options: optionsMap, + }, + }, + }, + }, + }, + }, + } + + if err := s.patchFireFlyCoreConfigs(configDir, member, newConfig); err != nil { + return messages, err + } + // Create data directory with correct permissions inside volume dataVolumeName := fmt.Sprintf("%s_firefly_core_data_%s", s.Stack.Name, member.ID) From 5f68d1bbcb4c7b220f84b35470fd6218f73a0c1c Mon Sep 17 00:00:00 2001 From: MKVEERENDRA Date: Thu, 21 Nov 2024 17:00:35 +0530 Subject: [PATCH 2/3] Fix: Update configuration to use --from-block in init command Signed-off-by: MKVEERENDRA --- README.md | 21 ++++ cmd/init.go | 3 +- cmd/start.go | 4 +- internal/stacks/stack_manager.go | 207 ++++++++++++++++--------------- 4 files changed, 133 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 2b919fef..1d784c4e 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,27 @@ go install github.com/hyperledger/firefly-cli/ff@latest $ ff init ``` +### Stack Initialization Options + +When initializing a new stack, you can configure various options including: + +``` +--from-block For multiparty networks only: specify the starting block for event processing. + Use 'newest' for latest block, or a specific block number (default: "0") +``` + +Examples: +``` +# Initialize with default settings (starts from block 0) +ff init mystack + +# Initialize starting from the latest block +ff init mystack --from-block newest + +# Initialize starting from a specific block number +ff init mystack --from-block 1234567 +``` + ## Start a stack ``` diff --git a/cmd/init.go b/cmd/init.go index 212f355b..f97e8642 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -334,6 +334,7 @@ func init() { initCmd.PersistentFlags().StringArrayVar(&initOptions.OrgNames, "org-name", []string{}, "Organization name") initCmd.PersistentFlags().StringArrayVar(&initOptions.NodeNames, "node-name", []string{}, "Node name") initCmd.PersistentFlags().BoolVar(&initOptions.RemoteNodeDeploy, "remote-node-deploy", false, "Enable or disable deployment of FireFly contracts on remote nodes") - initCmd.PersistentFlags().StringToStringVar(&initOptions.EnvironmentVars, "environment-vars", map[string]string{}, "Common environment variables to set on all containers in FireFly stack") + initCmd.PersistentFlags().StringToStringVar(&initOptions.EnvironmentVars, "environment-vars", map[string]string{}, "Common environment variables to set on all containers in FireFly stack") + initCmd.Flags().StringVar(&initOptions.FromBlock, "from-block", "0","For multiparty networks only: specify the starting block for event processing (default: 0)") rootCmd.AddCommand(initCmd) } diff --git a/cmd/start.go b/cmd/start.go index ebd78fcb..9731432e 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -102,8 +102,10 @@ This command will start a stack and run it in the background. }, } + +// ... existing code ... + func init() { startCmd.Flags().BoolVarP(&startOptions.NoRollback, "no-rollback", "b", false, "Do not automatically rollback changes if first time setup fails") - startCmd.Flags().StringVarP(&startOptions.FirstEvent, "first-event", "f", "0", "Specify the starting block for event processing (default: 0)") rootCmd.AddCommand(startCmd) } diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index a8d8e861..6571bb99 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -1,19 +1,3 @@ -// Copyright © 2024 Kaleido, Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package stacks import ( @@ -612,13 +596,6 @@ func (s *StackManager) createMember(id string, index int, options *types.InitOpt func (s *StackManager) StartStack(options *types.StartOptions) (messages []string, err error) { fmt.Printf("starting FireFly stack '%s'... ", s.Stack.Name) - // Validate the FirstEvent value - if options.FirstEvent != "0" && options.FirstEvent != "newest" { - if _, err := strconv.Atoi(options.FirstEvent); err != nil { - return messages, fmt.Errorf("invalid FirstEvent value: %s", options.FirstEvent) - } - } - // Check port availability err = s.checkPortsAvailable() if err != nil { @@ -631,9 +608,8 @@ func (s *StackManager) StartStack(options *types.StartOptions) (messages []strin } if !hasBeenRun { // Pass FirstEvent to the first-time setup - setupMessages, err := s.runFirstTimeSetup(&types.StartOptions{ - NoRollback: false, // Example - FirstEvent: "newest", // Or the user-defined value + setupMessages, err := s.runFirstTimeSetup(&types.InitOptions{ + FromBlock: options.FromBlock, }) messages = append(messages, setupMessages...) if err != nil { @@ -872,7 +848,7 @@ func checkPortAvailable(port int) (bool, error) { } //nolint:gocyclo // TODO: Breaking this function apart would be great for code tidiness, but it's not an urgent priority -func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages []string, err error) { +func (s *StackManager) runFirstTimeSetup(options *types.InitOptions) (messages []string, err error) { configDir := filepath.Join(s.Stack.RuntimeDir, "config") for i := 0; i < len(s.Stack.Members); i++ { @@ -948,83 +924,114 @@ func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages newConfig.Namespaces.Predefined[0].Plugins = append(newConfig.Namespaces.Predefined[0].Plugins, types.FFEnumArrayToStrings(s.Stack.TokenProviders)...) - - var contractDeploymentResult *types.ContractDeploymentResult - if s.Stack.MultipartyEnabled { - if s.Stack.ContractAddress == "" { - s.Log.Info("deploying FireFly smart contracts") - contractDeploymentResult, err = s.blockchainProvider.DeployFireFlyContract() - if err != nil { - return messages, err - } - } - } - - for _, member := range s.Stack.Members { - orgConfig := s.blockchainProvider.GetOrgConfig(s.Stack, member) - - // Default to "0" if FirstEvent is not provided - firstEvent := "0" - if options.FirstEvent != "" { - firstEvent = options.FirstEvent - } - - var contractLocation interface{} - if s.Stack.ContractAddress != "" { - contractLocation = map[string]interface{}{ - "address": s.Stack.ContractAddress, - } - } else { - contractLocation = contractDeploymentResult.DeployedContract.Location - } - - optionsMap := make(map[string]interface{}) - if s.Stack.CustomPinSupport { - optionsMap["customPinSupport"] = true - } - - newConfig := &types.FireflyConfig{ - Namespaces: &types.NamespacesConfig{ - Default: "default", - Predefined: []*types.Namespace{ - { - Name: "default", - Description: "Default predefined namespace", - Plugins: []string{"database0", "blockchain0", "dataexchange0", "sharedstorage0"}, - Multiparty: &types.MultipartyConfig{ - Enabled: true, - Org: orgConfig, - Node: &types.NodeConfig{ - Name: member.NodeName, - }, - Contract: []*types.ContractConfig{ - { - Location: contractLocation, - FirstEvent: firstEvent, // Now configurable - Options: optionsMap, - }, - }, - }, - }, - }, - }, - } - - if err := s.patchFireFlyCoreConfigs(configDir, member, newConfig); err != nil { - return messages, err - } - - - // Create data directory with correct permissions inside volume - dataVolumeName := fmt.Sprintf("%s_firefly_core_data_%s", s.Stack.Name, member.ID) - if err := docker.CreateVolume(s.ctx, dataVolumeName); err != nil { - return messages, err + if s.Stack.MultipartyEnabled { + if s.Stack.ContractAddress == "" { + s.Log.Info("deploying FireFly smart contracts") + contractDeploymentResult, err = s.blockchainProvider.DeployFireFlyContract() + if err != nil { + return messages, err + } + + var contractLocation interface{} + if s.Stack.ContractAddress != "" { + contractLocation = map[string]interface{}{ + "address": s.Stack.ContractAddress, + } + } else { + contractLocation = contractDeploymentResult.DeployedContract.Location + } + + options := make(map[string]interface{}) + if s.Stack.CustomPinSupport { + options["customPinSupport"] = true + } + + if newConfig.Namespaces.Predefined[0].Multiparty == nil { + newConfig.Namespaces.Predefined[0].Multiparty = &types.MultipartyConfig{ + Enabled: true, + Org: orgConfig, + Node: &types.NodeConfig{ + Name: member.NodeName, + }, + } + } + + newConfig.Namespaces.Predefined[0].Multiparty.Contract = []*types.ContractConfig{ + { + Location: contractLocation, + FirstEvent: options.FromBlock, + Options: options, + }, + } + + if contractDeploymentResult.Message != "" { + messages = append(messages, contractDeploymentResult.Message) + } + s.Stack.State.DeployedContracts = append(s.Stack.State.DeployedContracts, contractDeploymentResult.DeployedContract) + if err = s.writeStackStateJSON(s.Stack.RuntimeDir); err != nil { + return messages, err + } } - if err := docker.MkdirInVolume(s.ctx, dataVolumeName, "db"); err != nil { - return messages, err + } + + for _, member := range s.Stack.Members { + orgConfig := s.blockchainProvider.GetOrgConfig(s.Stack, member) + newConfig.Namespaces.Predefined[0].DefaultKey = orgConfig.Key + + if s.Stack.MultipartyEnabled { + if s.Stack.ContractAddress == "" { + s.Log.Info("deploying FireFly smart contracts") + contractDeploymentResult, err = s.blockchainProvider.DeployFireFlyContract() + if err != nil { + return messages, err + } + + var contractLocation interface{} + if s.Stack.ContractAddress != "" { + contractLocation = map[string]interface{}{ + "address": s.Stack.ContractAddress, + } + } else { + contractLocation = contractDeploymentResult.DeployedContract.Location + } + + options := make(map[string]interface{}) + if s.Stack.CustomPinSupport { + options["customPinSupport"] = true + } + + if newConfig.Namespaces.Predefined[0].Multiparty == nil { + newConfig.Namespaces.Predefined[0].Multiparty = &types.MultipartyConfig{ + Enabled: true, + Org: orgConfig, + Node: &types.NodeConfig{ + Name: member.NodeName, + }, + } + } + + newConfig.Namespaces.Predefined[0].Multiparty.Contract = []*types.ContractConfig{ + { + Location: contractLocation, + FirstEvent: options.FromBlock, + Options: options, + }, + } + + if contractDeploymentResult.Message != "" { + messages = append(messages, contractDeploymentResult.Message) + } + s.Stack.State.DeployedContracts = append(s.Stack.State.DeployedContracts, contractDeploymentResult.DeployedContract) + if err = s.writeStackStateJSON(s.Stack.RuntimeDir); err != nil { + return messages, err + } + } } + if err := s.patchFireFlyCoreConfigs(configDir, member, newConfig); err != nil { + return messages, err + } } // Re-write the docker-compose config again, in case new values have been added From 8bb34e10c2809ebff2c3c8ab022cf630b14ecfe1 Mon Sep 17 00:00:00 2001 From: MKVEERENDRA <136776337+MKVEERENDRA@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:03:10 +0530 Subject: [PATCH 3/3] Update stack_manager.go Signed-off-by: MKVEERENDRA <136776337+MKVEERENDRA@users.noreply.github.com> --- internal/stacks/stack_manager.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index 6571bb99..eeb44592 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -1,3 +1,18 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package stacks import (