Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support quorum tessera blockchain node #310

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6f4f577
Add environment vars cli flag to stack init
rodion-lim-partior May 31, 2024
6c69867
Update concatenate environment variable function name
rodion-lim-partior Jun 28, 2024
4bc7744
Add quorum blockchain node provider
rodion-lim-partior May 31, 2024
2056886
Start multiple quorum and tessera containers
rodion-lim-partior Jun 20, 2024
85013cf
Switch to using entrypoint file for quorum
rodion-lim-partior Jun 20, 2024
7df502d
Unlock only member specific accounts per quorum node
rodion-lim-partior Jun 21, 2024
ddd5226
Store private transaction manager keys in stack state and add tests
rodion-lim-partior Jun 21, 2024
8f0eefb
Expose ptm base port as a cli flag
rodion-lim-partior Jun 21, 2024
fe36fcb
Add tesseraEnabled flag and explicitly only allow quorum with tessera…
rodion-lim-partior Jun 27, 2024
91c6a1f
Make quorum consensus configurable via cli flag
rodion-lim-partior Jun 27, 2024
a8910c1
Remove private ptm private key references during stack creation
rodion-lim-partior Jun 27, 2024
23480c5
Remove duplicate dd for tessera
rodion-lim-partior Jun 27, 2024
e780adf
Refactor quorum docker entrypoint creation to its own file
rodion-lim-partior Jun 28, 2024
1589b82
Check that Tessera ports are available
rodion-lim-partior Jun 28, 2024
55a8385
Update tessera container name to have stack name
rodion-lim-partior Jun 28, 2024
c78b926
Fix PR review comments
rodion-lim-partior Jul 1, 2024
3af75a7
Remove unused volume parameter when creating quorum entrypoint
rodion-lim-partior Jul 1, 2024
19cb418
Fix incorrect consensus args
rodion-lim-partior Jul 1, 2024
2a12af0
Add tests for quorum package
rodion-lim-partior Jul 1, 2024
608c838
Update gas limit for quorum
rodion-lim-partior Jul 2, 2024
18dc785
Make consensus and tessera args generic
rodion-lim-partior Jul 10, 2024
ae22e11
Disable evmconnect with tessera temporarily
rodion-lim-partior Jul 10, 2024
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
37 changes: 37 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ func initCommon(args []string) error {
if err := validateIPFSMode(initOptions.IPFSMode); err != nil {
return err
}
if err := validateQuorumConsensus(initOptions.QuorumConsensus); err != nil {
return err
}
if err := validateTesseraSelection(initOptions.TesseraEnabled, initOptions.BlockchainNodeProvider); err != nil {
return err
}

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

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

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

if v != types.QuorumConsensusClique {
return errors.New("support for raft/ibft/qbft will come in future")
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
}

return nil
}

func validateTesseraSelection(tesseraEnabled bool, nodeString string) error {
if tesseraEnabled {
v, err := fftypes.FFEnumParseString(context.Background(), types.BlockchainNodeProvider, nodeString)
if err != nil {
return nil
}

if v != types.BlockchainNodeProviderQuorum {
return errors.New("tessera can only be enabled if blockchain node provider is quorum")
}
}
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 +279,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)")
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
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().BoolVar(&initOptions.TesseraEnabled, "tessera-enabled", false, "Enables private transaction manager Tessera to start alongside with Quorum")
initCmd.PersistentFlags().StringVar(&initOptions.QuorumConsensus, "quorum-consensus", "clique", fmt.Sprintf("Consensus algorithm used when Blockchain node type is Quorum. Options are %v", fftypes.FFEnumValues(types.QuorumConsensus)))
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -271,5 +307,6 @@ 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")
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.AddCommand(initCmd)
}
3 changes: 2 additions & 1 deletion internal/blockchain/ethereum/besu/besu_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ func (p *BesuProvider) GetDockerServiceDefinitions() []*docker.ServiceDefinition
Volumes: []string{
"besu:/data",
},
Logging: docker.StandardLogOptions,
Logging: docker.StandardLogOptions,
Environment: p.stack.EnvironmentVars,
},

VolumeNames: []string{"besu"},
Expand Down
3 changes: 2 additions & 1 deletion internal/blockchain/ethereum/connector/ethconnect/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func (e *Ethconnect) GetServiceDefinitions(s *types.Stack, dependentServices map
fmt.Sprintf("ethconnect_config_%s:/ethconnect/config", member.ID),
fmt.Sprintf("ethconnect_data_%s:/ethconnect/data", member.ID),
},
Logging: docker.StandardLogOptions,
Logging: docker.StandardLogOptions,
Environment: s.EnvironmentVars,
},
VolumeNames: []string{
fmt.Sprintf("ethconnect_config_%v", member.ID),
Expand Down
13 changes: 13 additions & 0 deletions internal/blockchain/ethereum/connector/ethconnect/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ func TestGetServiceDefinition(t *testing.T) {
},
ServiceName: "ethconnect_firefly_4",
},
{
Name: "test_env_vars_5",
Members: &types.Stack{
Members: []*types.Organization{{ID: "firefly_5", ExposedConnectorPort: 7892}},
VersionManifest: &types.VersionManifest{Ethconnect: &getManifest.ManifestEntry},
EnvironmentVars: map[string]interface{}{"HTTP_PROXY": ""},
},
DependentServices: map[string]string{
"service1": "running",
"service2": "stopped",
},
ServiceName: "ethconnect_firefly_5",
},
}
for _, tc := range testServices {
t.Run(tc.Name, func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/blockchain/ethereum/connector/evmconnect/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func (e *Evmconnect) GetServiceDefinitions(s *types.Stack, dependentServices map
fmt.Sprintf("%s/config/evmconnect_%s.yaml:/evmconnect/config.yaml", s.RuntimeDir, member.ID),
fmt.Sprintf("%s:/evmconnect/data", dataVolumeName),
},
Logging: docker.StandardLogOptions,
Logging: docker.StandardLogOptions,
Environment: s.EnvironmentVars,
},
VolumeNames: []string{
dataVolumeName,
Expand Down
20 changes: 19 additions & 1 deletion internal/blockchain/ethereum/connector/evmconnect/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ func TestGetServiceDefinition(t *testing.T) {
},
ServiceName: "evmconnect_firefly_4",
},
{
Name: "test_env_vars_5",
Members: &types.Stack{
Members: []*types.Organization{{ID: "firefly_5", ExposedConnectorPort: 7892}},
VersionManifest: &types.VersionManifest{Evmconnect: &getManifest.ManifestEntry},
EnvironmentVars: map[string]interface{}{"HTTP_PROXY": ""},
},
DependentServices: map[string]string{
"service1": "running",
"service2": "stopped",
},
ServiceName: "evmconnect_firefly_5",
},
}
for _, tc := range testServices {
t.Run(tc.Name, func(t *testing.T) {
Expand All @@ -90,7 +103,12 @@ func TestGetServiceDefinition(t *testing.T) {
if serviceDefinitions[0].ServiceName != tc.ServiceName {
t.Errorf("Expected ServiceName %q, got %q", tc.ServiceName, serviceDefinitions[0].ServiceName)
}

if len(tc.Members.EnvironmentVars) != len(serviceDefinitions[0].Service.Environment) {
t.Errorf("Expected EnvironmentVars %q, got %q", tc.Members.EnvironmentVars, serviceDefinitions[0].Service.Environment)
}
for k := range tc.Members.EnvironmentVars {
assert.Equal(t, tc.Members.EnvironmentVars[k], serviceDefinitions[0].Service.Environment[k])
}
})
}

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"`
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
}

func GenerateAddressAndPrivateKey() (address string, privateKey string) {
Expand Down
3 changes: 2 additions & 1 deletion internal/blockchain/ethereum/ethsigner/ethsigner.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ func (p *EthSignerProvider) GetDockerServiceDefinition(rpcURL string) *docker.Se
Interval: "15s", // 6000 requests in a day
Retries: 60,
},
Ports: []string{fmt.Sprintf("%d:8545", p.stack.ExposedBlockchainPort)},
Ports: []string{fmt.Sprintf("%d:8545", p.stack.ExposedBlockchainPort)},
Environment: p.stack.EnvironmentVars,
},
VolumeNames: []string{
"ethsigner",
Expand Down
1 change: 1 addition & 0 deletions internal/blockchain/ethereum/geth/geth_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func (p *GethProvider) GetDockerServiceDefinitions() []*docker.ServiceDefinition
Volumes: []string{"geth:/data"},
Logging: docker.StandardLogOptions,
Ports: []string{fmt.Sprintf("%d:8545", p.stack.ExposedBlockchainPort)},
Environment: p.stack.EnvironmentVars,
},
VolumeNames: []string{"geth"},
}
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 GethClient 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 NewGethClient(rpcURL string) *GethClient {
return &GethClient{
rpcURL: rpcURL,
}
}

func (g *GethClient) 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
}
114 changes: 114 additions & 0 deletions internal/blockchain/ethereum/quorum/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 (
"encoding/json"
"fmt"
"os"
"strings"
)

type Genesis struct {
Config *GenesisConfig `json:"config"`
Nonce string `json:"nonce"`
Timestamp string `json:"timestamp"`
ExtraData string `json:"extraData"`
GasLimit string `json:"gasLimit"`
Difficulty string `json:"difficulty"`
MixHash string `json:"mixHash"`
Coinbase string `json:"coinbase"`
Alloc map[string]*Alloc `json:"alloc"`
Number string `json:"number"`
GasUsed string `json:"gasUsed"`
ParentHash string `json:"parentHash"`
}

type GenesisConfig struct {
ChainID int64 `json:"chainId"`
HomesteadBlock int `json:"homesteadBlock"`
Eip150Block int `json:"eip150Block"`
Eip150Hash string `json:"eip150Hash"`
Eip155Block int `json:"eip155Block"`
Eip158Block int `json:"eip158Block"`
ByzantiumBlock int `json:"byzantiumBlock"`
ConstantinopleBlock int `json:"constantinopleBlock"`
PetersburgBlock int `json:"petersburgBlock"`
IstanbulBlock int `json:"istanbulBlock"`
Clique *CliqueConfig `json:"clique"`
}

type CliqueConfig struct {
Period int `json:"period"`
Epoch int `json:"epoch"`
}

type Alloc struct {
Balance string `json:"balance"`
}

func CreateGenesis(addresses []string, blockPeriod int, chainID int64) *Genesis {
if blockPeriod == -1 {
blockPeriod = 0
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
}
extraData := "0x0000000000000000000000000000000000000000000000000000000000000000"
alloc := make(map[string]*Alloc)
for _, address := range addresses {
alloc[address] = &Alloc{
Balance: "0x200000000000000000000000000000000000000000000000000000000000000",
}
extraData += address
}
extraData = strings.ReplaceAll(fmt.Sprintf("%-236s", extraData), " ", "0")

return &Genesis{
Config: &GenesisConfig{
ChainID: chainID,
HomesteadBlock: 0,
Eip150Block: 0,
Eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000",
Eip155Block: 0,
Eip158Block: 0,
ByzantiumBlock: 0,
ConstantinopleBlock: 0,
IstanbulBlock: 0,
Clique: &CliqueConfig{
Period: blockPeriod,
Epoch: 30000,
},
},
Nonce: "0x0",
Timestamp: "0x60edb1c7",
rodion-lim-partior marked this conversation as resolved.
Show resolved Hide resolved
ExtraData: extraData,
GasLimit: "0xffffff",
Difficulty: "0x1",
MixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
Coinbase: "0x0000000000000000000000000000000000000000",
Alloc: alloc,
Number: "0x0",
GasUsed: "0x0",
ParentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
}
}

func (g *Genesis) WriteGenesisJSON(filename string) error {
genesisJSONBytes, _ := json.MarshalIndent(g, "", " ")
if err := os.WriteFile(filename, genesisJSONBytes, 0755); err != nil {
return err
}
return nil
}
Loading
Loading