Skip to content

Commit

Permalink
Merge pull request #310 from partior-3p/feature/set516_support_quorum…
Browse files Browse the repository at this point in the history
…_tessera

Support quorum tessera blockchain node
  • Loading branch information
peterbroadhurst authored Jul 11, 2024
2 parents 39028cd + ae22e11 commit c0fa99a
Show file tree
Hide file tree
Showing 21 changed files with 2,361 additions and 83 deletions.
63 changes: 63 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ func initCommon(args []string) error {
if err := validateIPFSMode(initOptions.IPFSMode); err != nil {
return err
}
if err := validateConsensus(initOptions.Consensus); err != nil {
return err
}
if err := validatePrivateTransactionManagerSelection(initOptions.PrivateTransactionManager, initOptions.BlockchainNodeProvider); err != nil {
return err
}
if err := validatePrivateTransactionManagerBlockchainConnectorCombination(initOptions.PrivateTransactionManager, initOptions.BlockchainConnector); err != nil {
return err
}

fmt.Println("initializing new FireFly stack...")

Expand Down Expand Up @@ -200,6 +209,57 @@ func validateBlockchainProvider(providerString, nodeString string) error {
return nil
}

func validateConsensus(consensusString string) error {
v, err := fftypes.FFEnumParseString(context.Background(), types.Consensus, consensusString)
if err != nil {
return nil
}

if v != types.ConsensusClique {
return errors.New("currently only Clique consensus is supported")
}

return nil
}

func validatePrivateTransactionManagerSelection(privateTransactionManagerInput string, nodeString string) error {
privateTransactionManager, err := fftypes.FFEnumParseString(context.Background(), types.PrivateTransactionManager, privateTransactionManagerInput)
if err != nil {
return err
}

if !privateTransactionManager.Equals(types.PrivateTransactionManagerNone) {
v, err := fftypes.FFEnumParseString(context.Background(), types.BlockchainNodeProvider, nodeString)
if err != nil {
return nil
}

if v != types.BlockchainNodeProviderQuorum {
return errors.New("private transaction manager can only be enabled if blockchain node provider is Quorum")
}
}
return nil
}

func validatePrivateTransactionManagerBlockchainConnectorCombination(privateTransactionManagerInput string, blockchainConnectorInput string) error {
privateTransactionManager, err := fftypes.FFEnumParseString(context.Background(), types.PrivateTransactionManager, privateTransactionManagerInput)
if err != nil {
return err
}

blockchainConnector, err := fftypes.FFEnumParseString(context.Background(), types.BlockchainConnector, blockchainConnectorInput)
if err != nil {
return nil
}

if !privateTransactionManager.Equals(types.PrivateTransactionManagerNone) {
if !blockchainConnector.Equals(types.BlockchainConnectorEthconnect) {
return errors.New("currently only Ethconnect blockchain connector is supported with a private transaction manager")
}
}
return nil
}

func validateTokensProvider(input []string, blockchainNodeProviderInput string) error {
tokenProviders := make([]fftypes.FFEnum, len(input))
for i, t := range input {
Expand Down Expand Up @@ -246,10 +306,13 @@ func randomHexString(length int) (string, error) {
func init() {
initCmd.PersistentFlags().IntVarP(&initOptions.FireFlyBasePort, "firefly-base-port", "p", 5000, "Mapped port base of FireFly core API (1 added for each member)")
initCmd.PersistentFlags().IntVarP(&initOptions.ServicesBasePort, "services-base-port", "s", 5100, "Mapped port base of services (100 added for each member)")
initCmd.PersistentFlags().IntVar(&initOptions.PtmBasePort, "ptm-base-port", 4100, "Mapped port base of private transaction manager (10 added for each member)")
initCmd.PersistentFlags().StringVarP(&initOptions.DatabaseProvider, "database", "d", "sqlite3", fmt.Sprintf("Database type to use. Options are: %v", fftypes.FFEnumValues(types.DatabaseSelection)))
initCmd.Flags().StringVarP(&initOptions.BlockchainConnector, "blockchain-connector", "c", "evmconnect", fmt.Sprintf("Blockchain connector to use. Options are: %v", fftypes.FFEnumValues(types.BlockchainConnector)))
initCmd.Flags().StringVarP(&initOptions.BlockchainProvider, "blockchain-provider", "b", "ethereum", fmt.Sprintf("Blockchain to use. Options are: %v", fftypes.FFEnumValues(types.BlockchainProvider)))
initCmd.Flags().StringVarP(&initOptions.BlockchainNodeProvider, "blockchain-node", "n", "geth", fmt.Sprintf("Blockchain node type to use. Options are: %v", fftypes.FFEnumValues(types.BlockchainNodeProvider)))
initCmd.PersistentFlags().StringVar(&initOptions.PrivateTransactionManager, "private-transaction-manager", "none", fmt.Sprintf("Private Transaction Manager to use. Options are: %v", fftypes.FFEnumValues(types.PrivateTransactionManager)))
initCmd.PersistentFlags().StringVar(&initOptions.Consensus, "consensus", "clique", fmt.Sprintf("Consensus algorithm to use. Options are %v", fftypes.FFEnumValues(types.Consensus)))
initCmd.PersistentFlags().StringArrayVarP(&initOptions.TokenProviders, "token-providers", "t", []string{"erc20_erc721"}, fmt.Sprintf("Token providers to use. Options are: %v", fftypes.FFEnumValues(types.TokenProvider)))
initCmd.PersistentFlags().IntVarP(&initOptions.ExternalProcesses, "external", "e", 0, "Manage a number of FireFly core processes outside of the docker-compose stack - useful for development and debugging")
initCmd.PersistentFlags().StringVarP(&initOptions.FireFlyVersion, "release", "r", "latest", fmt.Sprintf("Select the FireFly release version to use. Options are: %v", fftypes.FFEnumValues(types.ReleaseChannelSelection)))
Expand Down
5 changes: 5 additions & 0 deletions internal/blockchain/ethereum/connector/ethconnect/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package ethconnect
import (
"fmt"
"os"
"path/filepath"

"github.com/hyperledger/firefly-cli/internal/blockchain/ethereum/connector"
"github.com/hyperledger/firefly-cli/pkg/types"
Expand Down Expand Up @@ -58,6 +59,10 @@ type HTTP struct {

func (e *Config) WriteConfig(filename string, extraConnectorConfigPath string) error {
configYamlBytes, _ := yaml.Marshal(e)
basedir := filepath.Dir(filename)
if err := os.MkdirAll(basedir, 0755); err != nil {
return err
}
if err := os.WriteFile(filename, configYamlBytes, 0755); err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions internal/blockchain/ethereum/connector/evmconnect/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package evmconnect
import (
"fmt"
"os"
"path/filepath"

"github.com/hyperledger/firefly-cli/internal/blockchain/ethereum/connector"
"github.com/hyperledger/firefly-cli/pkg/types"
Expand Down Expand Up @@ -75,6 +76,10 @@ type GasOracleConfig struct {

func (e *Config) WriteConfig(filename string, extraEvmconnectConfigPath string) error {
configYamlBytes, _ := yaml.Marshal(e)
basedir := filepath.Dir(filename)
if err := os.MkdirAll(basedir, 0755); err != nil {
return err
}
if err := os.WriteFile(filename, configYamlBytes, 0755); err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions internal/blockchain/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import (
)

type Account struct {
Address string `json:"address"`
PrivateKey string `json:"privateKey"`
Address string `json:"address"`
PrivateKey string `json:"privateKey"`
PtmPublicKey string `json:"ptmPublicKey"` // Public key used for Tessera
}

func GenerateAddressAndPrivateKey() (address string, privateKey string) {
Expand Down
93 changes: 93 additions & 0 deletions internal/blockchain/ethereum/quorum/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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 quorum

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)

type QuorumClient struct {
rpcURL string
}

type JSONRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}

type JSONRPCResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Error *JSONRPCError `json:"error,omitempty"`
Result interface{} `json:"result,omitempty"`
}

type JSONRPCError struct {
Code int `json:"code"`
Message string `json:"message"`
}

func NewQuorumClient(rpcURL string) *QuorumClient {
return &QuorumClient{
rpcURL: rpcURL,
}
}

func (g *QuorumClient) UnlockAccount(address string, password string) error {
requestBody, err := json.Marshal(&JSONRPCRequest{
JSONRPC: "2.0",
ID: 0,
Method: "personal_unlockAccount",
Params: []interface{}{address, password, 0},
})
if err != nil {
return err
}
req, err := http.NewRequest("POST", g.rpcURL, bytes.NewBuffer(requestBody))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("%s [%d] %s", req.URL, resp.StatusCode, responseBody)
}
var rpcResponse *JSONRPCResponse
err = json.Unmarshal(responseBody, &rpcResponse)
if err != nil {
return err
}
if rpcResponse.Error != nil {
return fmt.Errorf(rpcResponse.Error.Message)
}
return nil
}
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))
}
})
}
}
Loading

0 comments on commit c0fa99a

Please sign in to comment.