Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/deploy_ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hyperledger/firefly-cli/internal/docker"
"github.com/hyperledger/firefly-cli/internal/log"
"github.com/hyperledger/firefly-cli/internal/stacks"
"github.com/hyperledger/firefly-cli/pkg/types"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -55,6 +56,9 @@ solc --combined-json abi,bin contract.sol > contract.json
if err := stackManager.LoadStack(stackName); err != nil {
return err
}
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderEthereum) {
return fmt.Errorf("stack '%s' is not an Ethereum stack (blockchain provider: %s). Use 'ff deploy fabric' for Fabric stacks", stackName, stackManager.Stack.BlockchainProvider)
}
contractNames, err := stackManager.GetContracts(filename, args[2:])
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions cmd/deploy_fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hyperledger/firefly-cli/internal/docker"
"github.com/hyperledger/firefly-cli/internal/log"
"github.com/hyperledger/firefly-cli/internal/stacks"
"github.com/hyperledger/firefly-cli/pkg/types"
"github.com/spf13/cobra"
)

Expand All @@ -49,6 +50,9 @@ var deployFabricCmd = &cobra.Command{
if err := stackManager.LoadStack(stackName); err != nil {
return err
}
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderFabric) {
return fmt.Errorf("stack '%s' is not a Fabric stack (blockchain provider: %s). Use 'ff deploy ethereum' for Ethereum-based stacks", stackName, stackManager.Stack.BlockchainProvider)
}
contractAddress, err := stackManager.DeployContract(filename, filename, 0, args[2:])
if err != nil {
return fmt.Errorf("%s. usage: %s deploy <stack_name> <filename> <channel> <chaincode> <version>", err.Error(), ExecutableName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_cardano.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ var initCardanoCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ var initFabricCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_tezos.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ var initTezosCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
8 changes: 5 additions & 3 deletions cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ Pull the images for a stack .
if spin != nil {
spin.Start()
}
if err := stackManager.PullStack(&pullOptions); err != nil {
return err
}
err = stackManager.PullStack(&pullOptions)
if spin != nil {
spin.Stop()
}
// Throw an error after stopping the spin, this will prevent the user's terminal from having the spinner as overlay
if err != nil {
return err
}
return nil
},
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ This command will start a stack and run it in the background.
spin.Start()
}
messages, err := stackManager.StartStack(&startOptions)
if err != nil {
return err
}
if spin != nil {
spin.Stop()
}
if err != nil {
return err
}
fmt.Print("\n\n")
for _, message := range messages {
fmt.Printf("%s\n\n", message)
Expand Down
52 changes: 51 additions & 1 deletion internal/blockchain/fabric/fabric_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package fabric

import (
"archive/tar"
"compress/gzip"
"context"
_ "embed"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -438,7 +441,7 @@ func (p *FabricProvider) queryInstalled() (*QueryInstalledResponse, error) {
var res *QueryInstalledResponse
err = json.Unmarshal([]byte(str), &res)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse queryinstalled response as JSON: %w. Raw output: %s", err, str)
}
return res, nil
}
Expand Down Expand Up @@ -515,11 +518,58 @@ func (p *FabricProvider) GetContracts(filename string, extraArgs []string) ([]st
return []string{filename}, nil
}

// validateChaincodePackage checks that the chaincode package file exists and is a valid tar.gz file.
// Fabric chaincode packages created by 'peer lifecycle chaincode package' are tar.gz files.
func (p *FabricProvider) validateChaincodePackage(filename string) error {
// Check if file exists
fileInfo, err := os.Stat(filename)
if os.IsNotExist(err) {
return fmt.Errorf("chaincode package file not found: %s", filename)
}
if err != nil {
return fmt.Errorf("error accessing chaincode package file: %w", err)
}
if fileInfo.IsDir() {
return fmt.Errorf("chaincode package path is a directory, expected a file: %s", filename)
}

// Verify the file is a valid gzip archive
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open chaincode package file: %w", err)
}
defer file.Close()

gzReader, err := gzip.NewReader(file)
if err != nil {
return fmt.Errorf("invalid chaincode package file format. Expected a gzip-compressed tar archive (.tar.gz or .tgz) created by 'peer lifecycle chaincode package'. The file '%s' does not appear to be a valid gzip file: %w", filename, err)
}
defer gzReader.Close()

// Verify the gzip contains a valid tar archive by reading at least one header
tarReader := tar.NewReader(gzReader)
_, err = tarReader.Next()
if err == io.EOF {
return fmt.Errorf("invalid chaincode package: the tar.gz archive is empty")
}
if err != nil {
return fmt.Errorf("invalid chaincode package file format. The file '%s' is gzip-compressed but does not contain a valid tar archive: %w", filename, err)
}

return nil
}

func (p *FabricProvider) DeployContract(filename, contractName, instanceName string, member *types.Organization, extraArgs []string) (*types.ContractDeploymentResult, error) {
filename, err := filepath.Abs(filename)
if err != nil {
return nil, err
}

// Validate that the chaincode package file exists and is a valid gzip file
if err := p.validateChaincodePackage(filename); err != nil {
return nil, err
}

switch {
case len(extraArgs) < 1:
return nil, fmt.Errorf("channel not set")
Expand Down
141 changes: 141 additions & 0 deletions internal/blockchain/fabric/fabric_provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fabric

import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"os"
Expand Down Expand Up @@ -404,3 +407,141 @@ func TestRegisterIdentity(t *testing.T) {
})

}

func TestValidateChaincodePackage(t *testing.T) {
p := &FabricProvider{}

// Helper function to create a valid tar.gz file
createValidTarGz := func(t *testing.T, filename string) {
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tarWriter := tar.NewWriter(gzWriter)

// Add a file to the tar archive
content := []byte("test chaincode content")
header := &tar.Header{
Name: "metadata.json",
Mode: 0644,
Size: int64(len(content)),
}
err := tarWriter.WriteHeader(header)
assert.NoError(t, err)
_, err = tarWriter.Write(content)
assert.NoError(t, err)

err = tarWriter.Close()
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(filename, buf.Bytes(), 0644)
assert.NoError(t, err)
}

t.Run("valid tar.gz file", func(t *testing.T) {
tmpDir := t.TempDir()
validTarGzFile := filepath.Join(tmpDir, "valid.tar.gz")
createValidTarGz(t, validTarGzFile)

err := p.validateChaincodePackage(validTarGzFile)
assert.NoError(t, err)
})

t.Run("file not found", func(t *testing.T) {
err := p.validateChaincodePackage("/nonexistent/path/chaincode.tar.gz")
assert.Error(t, err)
assert.Contains(t, err.Error(), "chaincode package file not found")
})

t.Run("path is a directory", func(t *testing.T) {
tmpDir := t.TempDir()
err := p.validateChaincodePackage(tmpDir)
assert.Error(t, err)
assert.Contains(t, err.Error(), "chaincode package path is a directory")
})

t.Run("invalid - plain text file", func(t *testing.T) {
tmpDir := t.TempDir()
plainTextFile := filepath.Join(tmpDir, "plain.txt")
err := os.WriteFile(plainTextFile, []byte("this is not a gzip file"), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(plainTextFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
assert.Contains(t, err.Error(), "does not appear to be a valid gzip file")
})

t.Run("invalid - zip file", func(t *testing.T) {
tmpDir := t.TempDir()
// ZIP files start with PK (0x50, 0x4B)
zipLikeFile := filepath.Join(tmpDir, "fake.zip")
err := os.WriteFile(zipLikeFile, []byte{0x50, 0x4B, 0x03, 0x04, 0x00, 0x00}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(zipLikeFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
})

t.Run("invalid - random binary", func(t *testing.T) {
tmpDir := t.TempDir()
randomFile := filepath.Join(tmpDir, "random.bin")
err := os.WriteFile(randomFile, []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(randomFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
})

t.Run("invalid - gzip but not tar (plain gzipped content)", func(t *testing.T) {
tmpDir := t.TempDir()
gzipOnlyFile := filepath.Join(tmpDir, "gzip_only.gz")

var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
_, err := gzWriter.Write([]byte("this is gzipped but not a tar archive"))
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(gzipOnlyFile, buf.Bytes(), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(gzipOnlyFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not contain a valid tar archive")
})

t.Run("invalid - empty tar.gz", func(t *testing.T) {
tmpDir := t.TempDir()
emptyTarGzFile := filepath.Join(tmpDir, "empty.tar.gz")

var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tarWriter := tar.NewWriter(gzWriter)
// Close without adding any files
err := tarWriter.Close()
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(emptyTarGzFile, buf.Bytes(), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(emptyTarGzFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "tar.gz archive is empty")
})

t.Run("invalid - empty file", func(t *testing.T) {
tmpDir := t.TempDir()
emptyFile := filepath.Join(tmpDir, "empty.tar.gz")
err := os.WriteFile(emptyFile, []byte{}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(emptyFile)
assert.Error(t, err)
})
}
14 changes: 9 additions & 5 deletions internal/stacks/stack_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,11 +782,15 @@ func (s *StackManager) ResetStack() error {
}

func (s *StackManager) RemoveStack() error {
if err := s.runDockerComposeCommand("down"); err != nil {
return err
}
if err := s.removeVolumes(); err != nil {
return err
// Only run docker compose down if the docker-compose.yml file exists
composeFile := filepath.Join(s.Stack.StackDir, "docker-compose.yml")
if _, err := os.Stat(composeFile); err == nil {
if err := s.runDockerComposeCommand("down"); err != nil {
return err
}
if err := s.removeVolumes(); err != nil {
return err
}
}
return os.RemoveAll(s.Stack.StackDir)
}
Expand Down
Loading