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 95e0f496..9731432e 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -102,6 +102,9 @@ 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") 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..eeb44592 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -13,7 +13,6 @@ // 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 ( @@ -611,44 +610,44 @@ 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 + + // 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.InitOptions{ + FromBlock: options.FromBlock, + }) + 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) } @@ -864,7 +863,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++ { @@ -943,26 +942,12 @@ func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages 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{}{ @@ -971,40 +956,97 @@ func (s *StackManager) runFirstTimeSetup(options *types.StartOptions) (messages } 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, + 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, }, - Contract: []*types.ContractConfig{ + } + + 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 + } + } + } + + 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: "0", + 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 } - - // 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 err := docker.MkdirInVolume(s.ctx, dataVolumeName, "db"); err != nil { - return messages, err - } - } // Re-write the docker-compose config again, in case new values have been added