Skip to content

Commit

Permalink
Add tests for quorum package
Browse files Browse the repository at this point in the history
Signed-off-by: rodion <rodion.lim@partior.com>
  • Loading branch information
rodion-lim-partior committed Jul 2, 2024
1 parent 19cb418 commit aecd489
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 28 deletions.
81 changes: 81 additions & 0 deletions internal/blockchain/ethereum/quorum/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package quorum

import (
"encoding/json"
"fmt"
"testing"

"github.com/hyperledger/firefly-cli/internal/utils"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)

func TestUnlockAccount(t *testing.T) {
tests := []struct {
Name string
RPCUrl string
Address string
Password string
StatusCode int
ApiResponse *JSONRPCResponse
}{
{
Name: "TestUnlockAccount-1",
RPCUrl: "http://127.0.0.1:8545",
Address: "user-1",
Password: "POST",
StatusCode: 200,
ApiResponse: &JSONRPCResponse{
JSONRPC: "2.0",
ID: 0,
Error: nil,
Result: "mock result",
},
},
{
Name: "TestUnlockAccountError-2",
RPCUrl: "http://127.0.0.1:8545",
Address: "user-1",
Password: "POST",
StatusCode: 200,
ApiResponse: &JSONRPCResponse{
JSONRPC: "2.0",
ID: 0,
Error: &JSONRPCError{500, "invalid account"},
Result: "mock result",
},
},
{
Name: "TestUnlockAccountHTTPError-3",
RPCUrl: "http://localhost:8545",
Address: "user-1",
Password: "POST",
StatusCode: 500,
ApiResponse: &JSONRPCResponse{
JSONRPC: "2.0",
ID: 0,
Error: nil,
Result: "mock result",
},
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
apiResponse, _ := json.Marshal(tc.ApiResponse)
// mockResponse
httpmock.RegisterResponder("POST", tc.RPCUrl,
httpmock.NewStringResponder(tc.StatusCode, string(apiResponse)))
client := NewQuorumClient(tc.RPCUrl)
utils.StartMockServer(t)
err := client.UnlockAccount(tc.Address, tc.Password)
utils.StopMockServer(t)

// expect errors when returned status code != 200 or ApiResponse comes back with non nil error
if tc.StatusCode != 200 || tc.ApiResponse.Error != nil {
assert.NotNil(t, err, "expects error to be returned when either quorum returns an application error or non 200 http response")
} else {
assert.NoError(t, err, fmt.Sprintf("unable to unlock account: %v", err))
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package quorum

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/hyperledger/firefly-cli/internal/log"
"github.com/hyperledger/firefly-cli/pkg/types"
"github.com/stretchr/testify/assert"
)

func TestCreateTesseraKeys(t *testing.T) {
ctx := log.WithVerbosity(log.WithLogger(context.Background(), &log.StdoutLogger{}), false)
testCases := []struct {
Name string
Stack *types.Stack
TesseraImage string
KeysPrefix string
KeysName string
}{
{
Name: "testcase1",
Stack: &types.Stack{
Name: "Org-1_quorum",
InitDir: t.TempDir(),
},
TesseraImage: "quorumengineering/tessera:24.4",
KeysPrefix: "",
KeysName: "tm",
},
{
Name: "testcase2",
Stack: &types.Stack{
Name: "Org-1_quorum",
InitDir: t.TempDir(),
},
TesseraImage: "quorumengineering/tessera:24.4",
KeysPrefix: "xyz",
KeysName: "tm",
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
privateKey, publicKey, tesseraKeysPath, err := CreateTesseraKeys(ctx, tc.TesseraImage, filepath.Join(tc.Stack.InitDir, "tessera", "tessera_0", "keystore"), tc.KeysPrefix, tc.KeysName)
if err != nil {
t.Log("unable to create tessera keys", err)
}
//validate properties of tessera keys
assert.NotEmpty(t, privateKey)
assert.NotEmpty(t, publicKey)
assert.NotEmpty(t, tesseraKeysPath)

expectedOutputName := tc.KeysName
if tc.KeysPrefix != "" {
expectedOutputName = fmt.Sprintf("%s_%s", tc.KeysPrefix, expectedOutputName)
}
assert.Equal(t, tesseraKeysPath, filepath.Join(tc.Stack.InitDir, "tessera", "tessera_0", "keystore", expectedOutputName), "invalid output path")

assert.Nil(t, err)
})
}
}

func TestCreateTesseraEntrypoint(t *testing.T) {
ctx := log.WithVerbosity(log.WithLogger(context.Background(), &log.StdoutLogger{}), false)
testCases := []struct {
Name string
Stack *types.Stack
StackName string
MemberCount int
}{
{
Name: "testcase1",
Stack: &types.Stack{
Name: "Org-1_quorum",
InitDir: t.TempDir(),
},
StackName: "org1",
MemberCount: 4,
},
{
Name: "testcase2",
Stack: &types.Stack{
Name: "Org-2_quorum",
InitDir: t.TempDir(),
},
StackName: "org2",
MemberCount: 0,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
err := CreateTesseraEntrypoint(ctx, tc.Stack.InitDir, tc.StackName, tc.MemberCount)
if err != nil {
t.Log("unable to create tessera docker entrypoint", err)
}
path := filepath.Join(tc.Stack.InitDir, "docker-entrypoint.sh")
_, err = os.Stat(path)
assert.NoError(t, err, "docker entrypoint file not created")

b, err := os.ReadFile(path)
assert.NoError(t, err, "unable to read docker entrypoint file")
for i := 0; i < tc.MemberCount; i++ {
strings.Contains(string(b), fmt.Sprintf("member%dtessera", i))
}
})
}
}
27 changes: 15 additions & 12 deletions internal/blockchain/ethereum/quorum/quorum_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (

var quorumImage = "quorumengineering/quorum:24.4"
var tesseraImage = "quorumengineering/tessera:24.4"
var exposedBlockchainPortMultiplier = 10
var ExposedBlockchainPortMultiplier = 10

// TODO: Probably randomize this and make it different per member?
var keyPassword = "correcthorsebatterystaple"
Expand All @@ -47,6 +47,7 @@ type QuorumProvider struct {
ctx context.Context
stack *types.Stack
connector connector.Connector
dockerMgr docker.IDockerManager
}

func NewQuorumProvider(ctx context.Context, stack *types.Stack) *QuorumProvider {
Expand All @@ -62,6 +63,7 @@ func NewQuorumProvider(ctx context.Context, stack *types.Stack) *QuorumProvider
ctx: ctx,
stack: stack,
connector: connector,
dockerMgr: docker.NewDockerManager(),
}
}

Expand Down Expand Up @@ -125,7 +127,7 @@ func (p *QuorumProvider) FirstTimeSetup() error {
// Copy connector config to each member's volume
connectorConfigPath := filepath.Join(p.stack.StackDir, "runtime", "config", fmt.Sprintf("%s_%v.yaml", p.connector.Name(), i))
connectorConfigVolumeName := fmt.Sprintf("%s_%s_config_%v", p.stack.Name, p.connector.Name(), i)
if err := docker.CopyFileToVolume(p.ctx, connectorConfigVolumeName, connectorConfigPath, "config.yaml"); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, connectorConfigVolumeName, connectorConfigPath, "config.yaml"); err != nil {
return err
}

Expand All @@ -135,39 +137,39 @@ func (p *QuorumProvider) FirstTimeSetup() error {

// Copy the wallet files of each member to their respective blockchain volume
keystoreDirectory := filepath.Join(blockchainDir, fmt.Sprintf("quorum_%d", i), "keystore")
if err := docker.CopyFileToVolume(p.ctx, quorumVolumeNameMember, keystoreDirectory, "/"); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, quorumVolumeNameMember, keystoreDirectory, "/"); err != nil {
return err
}

if p.stack.TesseraEnabled {
// Copy member specific tessera key files
if err := docker.MkdirInVolume(p.ctx, tesseraVolumeNameMember, rootDir); err != nil {
if err := p.dockerMgr.MkdirInVolume(p.ctx, tesseraVolumeNameMember, rootDir); err != nil {
return err
}
tmKeystoreDirectory := filepath.Join(tesseraDir, fmt.Sprintf("tessera_%d", i), "keystore")
if err := docker.CopyFileToVolume(p.ctx, tesseraVolumeNameMember, tmKeystoreDirectory, rootDir); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, tesseraVolumeNameMember, tmKeystoreDirectory, rootDir); err != nil {
return err
}
// Copy tessera docker-entrypoint file
tmEntrypointPath := filepath.Join(tesseraDir, fmt.Sprintf("tessera_%d", i), DockerEntrypoint)
if err := docker.CopyFileToVolume(p.ctx, tesseraVolumeNameMember, tmEntrypointPath, rootDir); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, tesseraVolumeNameMember, tmEntrypointPath, rootDir); err != nil {
return err
}
}

// Copy quorum docker-entrypoint file
quorumEntrypointPath := filepath.Join(blockchainDir, fmt.Sprintf("quorum_%d", i), DockerEntrypoint)
if err := docker.CopyFileToVolume(p.ctx, quorumVolumeNameMember, quorumEntrypointPath, rootDir); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, quorumVolumeNameMember, quorumEntrypointPath, rootDir); err != nil {
return err
}

// Copy the genesis block information
if err := docker.CopyFileToVolume(p.ctx, quorumVolumeNameMember, path.Join(blockchainDir, "genesis.json"), "genesis.json"); err != nil {
if err := p.dockerMgr.CopyFileToVolume(p.ctx, quorumVolumeNameMember, path.Join(blockchainDir, "genesis.json"), "genesis.json"); err != nil {
return err
}

// Initialize the genesis block
if err := docker.RunDockerCommand(p.ctx, p.stack.StackDir, "run", "--rm", "-v", fmt.Sprintf("%s:/data", quorumVolumeNameMember), quorumImage, "--datadir", "/data", "init", "/data/genesis.json"); err != nil {
if err := p.dockerMgr.RunDockerCommand(p.ctx, p.stack.StackDir, "run", "--rm", "-v", fmt.Sprintf("%s:/data", quorumVolumeNameMember), quorumImage, "--datadir", "/data", "init", "/data/genesis.json"); err != nil {
return err
}
}
Expand All @@ -190,6 +192,7 @@ func (p *QuorumProvider) PostStart(firstTimeSetup bool) error {
for _, member := range p.stack.Members {
if member.Account.(*ethereum.Account).Address == address {
memberIndex = *member.Index
break
}
}
if err := p.unlockAccount(address, keyPassword, memberIndex); err != nil {
Expand All @@ -204,7 +207,7 @@ func (p *QuorumProvider) unlockAccount(address, password string, memberIndex int
l := log.LoggerFromContext(p.ctx)
verbose := log.VerbosityFromContext(p.ctx)
// exposed blockchain port is the default for node 0, we need to add the port multiplier to get the right rpc for the correct node
quorumClient := NewQuorumClient(fmt.Sprintf("http://127.0.0.1:%v", p.stack.ExposedBlockchainPort+(memberIndex*exposedBlockchainPortMultiplier)))
quorumClient := NewQuorumClient(fmt.Sprintf("http://127.0.0.1:%v", p.stack.ExposedBlockchainPort+(memberIndex*ExposedBlockchainPortMultiplier)))
retries := 10
for {
if err := quorumClient.UnlockAccount(address, password); err != nil {
Expand Down Expand Up @@ -250,7 +253,7 @@ func (p *QuorumProvider) GetDockerServiceDefinitions() []*docker.ServiceDefiniti
ContainerName: fmt.Sprintf("%s_member%dtessera", p.stack.Name, i),
Volumes: []string{fmt.Sprintf("tessera_%d:/data", i)},
Logging: docker.StandardLogOptions,
Ports: []string{fmt.Sprintf("%d:%s", p.stack.ExposedPtmPort+(i*exposedBlockchainPortMultiplier), TmTpPort)}, // defaults 4100, 4110, 4120, 4130
Ports: []string{fmt.Sprintf("%d:%s", p.stack.ExposedPtmPort+(i*ExposedBlockchainPortMultiplier), TmTpPort)}, // defaults 4100, 4110, 4120, 4130
Environment: p.stack.EnvironmentVars,
EntryPoint: []string{"/bin/sh", "-c", "/data/docker-entrypoint.sh"},
Deploy: map[string]interface{}{"restart_policy": map[string]string{"condition": "on-failure", "max_attempts": "3"}},
Expand All @@ -265,7 +268,7 @@ func (p *QuorumProvider) GetDockerServiceDefinitions() []*docker.ServiceDefiniti
ContainerName: fmt.Sprintf("%s_quorum_%d", p.stack.Name, i),
Volumes: []string{fmt.Sprintf("quorum_%d:/data", i)},
Logging: docker.StandardLogOptions,
Ports: []string{fmt.Sprintf("%d:8545", p.stack.ExposedBlockchainPort+(i*exposedBlockchainPortMultiplier))}, // defaults 5100, 5110, 5120, 5130
Ports: []string{fmt.Sprintf("%d:8545", p.stack.ExposedBlockchainPort+(i*ExposedBlockchainPortMultiplier))}, // defaults 5100, 5110, 5120, 5130
Environment: p.stack.EnvironmentVars,
EntryPoint: []string{"/bin/sh", "-c", "/data/docker-entrypoint.sh"},
DependsOn: quorumDependsOn,
Expand Down
Loading

0 comments on commit aecd489

Please sign in to comment.