From bb9c48c8965088ae6464093aa67a1a3b11f6a88e Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Wed, 2 Apr 2025 13:12:56 +0200 Subject: [PATCH 01/31] Refactor anchor peer removal logic and update FabricNodeForm - Removed the `--dev=false` argument from the launch configuration. - Updated the anchor peer removal logic in `channel.go` to continue on error instead of returning. - Commented out the peer removal validation in `anchor-peer-config.tsx` for future implementation. - Enhanced `FabricNodeForm` to include an `onChange` prop, allowing parent components to react to form value changes. - Integrated the `onChange` handler in the bulk create nodes page to update node configurations dynamically. These changes improve the flexibility and maintainability of the codebase, particularly in managing anchor peers and form interactions. Signed-off-by: dviejokfs --- .vscode/launch.json | 1 - pkg/fabric/channel/channel.go | 2 +- .../components/networks/anchor-peer-config.tsx | 16 ++++++++-------- web/src/components/nodes/fabric-node-form.tsx | 12 +++++++++--- web/src/pages/nodes/fabric/bulk-create.tsx | 7 ++++++- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d8895c5..241c9f4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,6 @@ "args": [ "serve", "--port=8100", - "--dev=false", "--db=./data/chainlaunch.db", ], "env": { diff --git a/pkg/fabric/channel/channel.go b/pkg/fabric/channel/channel.go index 7f2078f..1151cec 100644 --- a/pkg/fabric/channel/channel.go +++ b/pkg/fabric/channel/channel.go @@ -104,7 +104,7 @@ func (s *ChannelService) SetAnchorPeers(input *SetAnchorPeersInput) ([]byte, err Host: ap.Host, Port: ap.Port, }); err != nil { - return nil, fmt.Errorf("failed to remove anchor peer: %w", err) + continue } } diff --git a/web/src/components/networks/anchor-peer-config.tsx b/web/src/components/networks/anchor-peer-config.tsx index 349581c..cb0ddc8 100644 --- a/web/src/components/networks/anchor-peer-config.tsx +++ b/web/src/components/networks/anchor-peer-config.tsx @@ -66,14 +66,14 @@ export function AnchorPeerConfig({ organization, peers, currentAnchorPeers, onUp const handleRemoveAnchorPeer = async (host: string, port: number) => { const newAnchorPeers = currentAnchorPeers.filter((peer) => !(peer.host === host && peer.port === port)) - const peerToRemove = availablePeers.find((p) => { - const endpoint = parseEndpoint(p.node!.deploymentConfig!.externalEndpoint!) - return endpoint?.host === host && endpoint?.port === port - }) - - if (!peerToRemove) { - throw new Error('Peer not found') - } + // const peerToRemove = availablePeers.find((p) => { + // const endpoint = parseEndpoint(p.node!.deploymentConfig!.externalEndpoint!) + // return endpoint?.host === host && endpoint?.port === port + // }) + + // if (!peerToRemove) { + // throw new Error('Peer not found') + // } await onUpdateAnchorPeers(newAnchorPeers) } diff --git a/web/src/components/nodes/fabric-node-form.tsx b/web/src/components/nodes/fabric-node-form.tsx index 85e4f46..c593adc 100644 --- a/web/src/components/nodes/fabric-node-form.tsx +++ b/web/src/components/nodes/fabric-node-form.tsx @@ -4,8 +4,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDes import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { zodResolver } from '@hookform/resolvers/zod' -import { useEffect } from 'react' -import { useForm } from 'react-hook-form' +import { useEffect, useMemo } from 'react' +import { useForm, useWatch } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' @@ -46,9 +46,10 @@ interface FabricNodeFormProps { hideOrganization?: boolean hideNodeType?: boolean defaultValues?: FabricNodeFormValues + onChange?: (values: FabricNodeFormValues) => void } -export function FabricNodeForm({ onSubmit, isSubmitting, organizations, defaults, onNodeTypeChange, hideSubmit, hideOrganization, hideNodeType, defaultValues }: FabricNodeFormProps) { +export function FabricNodeForm({ onSubmit, isSubmitting, organizations, defaults, onNodeTypeChange, hideSubmit, hideOrganization, hideNodeType, defaultValues, onChange }: FabricNodeFormProps) { const form = useForm({ resolver: zodResolver(fabricNodeFormSchema), defaultValues: defaultValues || { @@ -65,6 +66,11 @@ export function FabricNodeForm({ onSubmit, isSubmitting, organizations, defaults }, }, }) + const values = useWatch({ control: form.control }) + + useEffect(() => { + onChange?.(values) + }, [values]) const nodeType = form.watch('fabricProperties.nodeType') diff --git a/web/src/pages/nodes/fabric/bulk-create.tsx b/web/src/pages/nodes/fabric/bulk-create.tsx index 212f19d..71fd22d 100644 --- a/web/src/pages/nodes/fabric/bulk-create.tsx +++ b/web/src/pages/nodes/fabric/bulk-create.tsx @@ -1,7 +1,6 @@ import { getNodesDefaultsFabric } from '@/api/client' import { getNodesDefaultsFabricOptions, getNodesOptions, getOrganizationsOptions, postNodesMutation } from '@/api/client/@tanstack/react-query.gen' import { HttpCreateNodeRequest, TypesFabricOrdererConfig, TypesFabricPeerConfig } from '@/api/client/types.gen' -// CreateFabricOrdererDto, CreateFabricPeerDto, CreateNodeDto import { FabricNodeForm, FabricNodeFormValues } from '@/components/nodes/fabric-node-form' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' @@ -382,6 +381,12 @@ export default function BulkCreateNodesPage() { newConfigs[index] = { ...values, name: config.name } setNodeConfigs(newConfigs) }} + onChange={(values) => { + const newConfigs = [...nodeConfigs] + newConfigs[index] = { ...values, name: config.name } + console.log('values', values, 'onChange') + setNodeConfigs(newConfigs) + }} organizations={organizations?.map((org) => ({ id: org.id!, name: org.mspId! })) || []} hideSubmit hideOrganization From 1eceff1b445845175c0327207fee64e7978ac437 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Thu, 3 Apr 2025 14:33:22 +0200 Subject: [PATCH 02/31] Add error message to internal error Signed-off-by: dviejokfs --- pkg/errors/errors.go | 4 +++- pkg/nodes/service/service.go | 1 + web/src/pages/nodes/besu/create.tsx | 2 +- web/src/pages/nodes/fabric/bulk-create.tsx | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 1a7a4c8..38acf0d 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -2,6 +2,8 @@ package errors import ( "fmt" + + "github.com/pkg/errors" ) type ErrorType string @@ -85,7 +87,7 @@ func NewConflictError(msg string, details map[string]interface{}) *AppError { func NewInternalError(msg string, err error, details map[string]interface{}) *AppError { return &AppError{ Type: InternalError, - Message: msg, + Message: errors.Wrap(err, msg).Error(), Details: details, Err: err, } diff --git a/pkg/nodes/service/service.go b/pkg/nodes/service/service.go index 1118e1d..d9835e9 100644 --- a/pkg/nodes/service/service.go +++ b/pkg/nodes/service/service.go @@ -1013,6 +1013,7 @@ func (s *NodeService) startNode(ctx context.Context, dbNode db.Node) error { } if startErr != nil { + s.logger.Error("Failed to start node", "error", startErr) // Update status to error if start failed if err := s.updateNodeStatus(ctx, dbNode.ID, types.NodeStatusError); err != nil { s.logger.Error("Failed to update node status after start error", "error", err) diff --git a/web/src/pages/nodes/besu/create.tsx b/web/src/pages/nodes/besu/create.tsx index 6e782c6..439e077 100644 --- a/web/src/pages/nodes/besu/create.tsx +++ b/web/src/pages/nodes/besu/create.tsx @@ -112,7 +112,7 @@ export default function CreateBesuNodePage() { navigate('/nodes') } catch (error: any) { toast.error('Failed to create node', { - description: error.message, + description: error.error.message, }) } } diff --git a/web/src/pages/nodes/fabric/bulk-create.tsx b/web/src/pages/nodes/fabric/bulk-create.tsx index 71fd22d..b7a19a3 100644 --- a/web/src/pages/nodes/fabric/bulk-create.tsx +++ b/web/src/pages/nodes/fabric/bulk-create.tsx @@ -255,7 +255,7 @@ export default function BulkCreateNodesPage() { navigate('/nodes') } catch (error: any) { toast.error('Failed to create nodes', { - description: error.message, + description: error.error.message, }) } } From 312c20b657b6d67e40726ba0d2d58032baec0194 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Thu, 3 Apr 2025 15:12:01 +0200 Subject: [PATCH 03/31] Refactor execSystemctl to check for sudo availability - Updated the execSystemctl function in the LocalBesu, LocalOrderer, and LocalPeer services to check if sudo is available before executing systemctl commands. - If sudo is available, commands are executed with sudo; otherwise, they are run directly. - This change enhances the flexibility of command execution across different environments. Signed-off-by: dviejokfs --- pkg/nodes/besu/service.go | 22 ++++++++++++++++++---- pkg/nodes/orderer/service.go | 22 ++++++++++++++++++---- pkg/nodes/peer/peer.go | 23 ++++++++++++++++++----- web/src/pages/nodes/[id].tsx | 2 +- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/pkg/nodes/besu/service.go b/pkg/nodes/besu/service.go index b3eef05..1556919 100644 --- a/pkg/nodes/besu/service.go +++ b/pkg/nodes/besu/service.go @@ -314,10 +314,24 @@ func (b *LocalBesu) stopLaunchdService() error { // execSystemctl executes a systemctl command func (b *LocalBesu) execSystemctl(command string, args ...string) error { - cmdArgs := append([]string{"systemctl", command}, args...) - cmd := exec.Command("sudo", cmdArgs...) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl %s failed: %w", command, err) + cmdArgs := append([]string{command}, args...) + + // Check if sudo is available + sudoPath, err := exec.LookPath("sudo") + if err == nil { + // sudo is available, use it + cmdArgs = append([]string{"systemctl"}, cmdArgs...) + cmd := exec.Command(sudoPath, cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } + } else { + // sudo is not available, run directly + cmd := exec.Command("systemctl", cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } } + return nil } diff --git a/pkg/nodes/orderer/service.go b/pkg/nodes/orderer/service.go index 2ef9b9c..dddbf08 100644 --- a/pkg/nodes/orderer/service.go +++ b/pkg/nodes/orderer/service.go @@ -229,11 +229,25 @@ func (o *LocalOrderer) stopLaunchdService() error { // execSystemctl executes a systemctl command func (o *LocalOrderer) execSystemctl(command string, args ...string) error { - cmdArgs := append([]string{"systemctl", command}, args...) - cmd := exec.Command("sudo", cmdArgs...) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl %s failed: %w", command, err) + cmdArgs := append([]string{command}, args...) + + // Check if sudo is available + sudoPath, err := exec.LookPath("sudo") + if err == nil { + // sudo is available, use it + cmdArgs = append([]string{"systemctl"}, cmdArgs...) + cmd := exec.Command(sudoPath, cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } + } else { + // sudo is not available, run directly + cmd := exec.Command("systemctl", cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } } + return nil } diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index 828f87c..657b3ca 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -566,14 +566,27 @@ func (p *LocalPeer) stopLaunchdService() error { return nil } - // execSystemctl executes a systemctl command func (p *LocalPeer) execSystemctl(command string, args ...string) error { - cmdArgs := append([]string{"systemctl", command}, args...) - cmd := exec.Command("sudo", cmdArgs...) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl %s failed: %w", command, err) + cmdArgs := append([]string{command}, args...) + + // Check if sudo is available + sudoPath, err := exec.LookPath("sudo") + if err == nil { + // sudo is available, use it + cmdArgs = append([]string{"systemctl"}, cmdArgs...) + cmd := exec.Command(sudoPath, cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } + } else { + // sudo is not available, run directly + cmd := exec.Command("systemctl", cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } } + return nil } diff --git a/web/src/pages/nodes/[id].tsx b/web/src/pages/nodes/[id].tsx index 70192e4..6aadbdc 100644 --- a/web/src/pages/nodes/[id].tsx +++ b/web/src/pages/nodes/[id].tsx @@ -46,7 +46,7 @@ interface DeploymentConfig { } function isFabricNode(node: HttpNodeResponse): node is HttpNodeResponse & { deploymentConfig: DeploymentConfig } { - return node.platform === 'FABRIC' && node.fabricPeer !== undefined && node.fabricOrderer !== undefined + return node.platform === 'FABRIC' && (node.fabricPeer !== undefined || node.fabricOrderer !== undefined) } function isBesuNode(node: HttpNodeResponse): node is HttpNodeResponse { From d8170269ac39d2aa0d548abb71f5d23ca37deef3 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 19:48:48 +0200 Subject: [PATCH 04/31] Switch from fabric-sdk-go to fabric-admin-sdk Signed-off-by: dviejokfs --- cmd/fabric/fabric.go | 2 +- cmd/fabric/install/install.go | 311 +++++---- cmd/fabric/invoke/invoke.go | 180 +++++- cmd/fabric/query/query.go | 147 ++++- docs/docs.go | 80 ++- docs/swagger.json | 78 +++ docs/swagger.yaml | 51 ++ go.mod | 12 +- go.sum | 213 ++++++ internal/configtxgen/encoder/encoder.go | 612 ++++++++++++++++++ internal/configtxgen/genesisconfig/config.go | 517 +++++++++++++++ internal/configtxgen/metadata/metadata.go | 25 + internal/configtxgen/viperutil/config_util.go | 505 +++++++++++++++ internal/protoutil/protoutil.go | 114 ++++ internal/protoutil/txtutils.go | 382 +++++++++++ internal/protoutil/unmarshalers.go | 44 ++ pkg/fabric/channel/channel.go | 19 +- pkg/fabric/networkconfig/parser.go | 58 ++ pkg/fabric/networkconfig/parser_test.go | 105 +++ pkg/fabric/networkconfig/types.go | 98 +++ pkg/fabric/policydsl/policydsl.go | 384 +++++++++++ pkg/networks/service/fabric/deployer.go | 45 +- pkg/networks/service/fabric/org/org.go | 567 +++++++--------- pkg/networks/service/service.go | 12 +- pkg/nodes/http/handler.go | 146 ++--- pkg/nodes/orderer/orderer.go | 174 ++++- pkg/nodes/peer/peer.go | 497 ++++++++------ pkg/nodes/service/service.go | 70 ++ pkg/nodes/types/channel.go | 10 + pkg/nodes/types/deployment.go | 4 + .../components/nodes/FabricOrdererConfig.tsx | 5 +- web/src/components/nodes/FabricPeerConfig.tsx | 15 +- 32 files changed, 4634 insertions(+), 848 deletions(-) create mode 100644 internal/configtxgen/encoder/encoder.go create mode 100644 internal/configtxgen/genesisconfig/config.go create mode 100644 internal/configtxgen/metadata/metadata.go create mode 100644 internal/configtxgen/viperutil/config_util.go create mode 100644 internal/protoutil/protoutil.go create mode 100644 internal/protoutil/txtutils.go create mode 100644 internal/protoutil/unmarshalers.go create mode 100644 pkg/fabric/networkconfig/parser.go create mode 100644 pkg/fabric/networkconfig/parser_test.go create mode 100644 pkg/fabric/networkconfig/types.go create mode 100644 pkg/fabric/policydsl/policydsl.go create mode 100644 pkg/nodes/types/channel.go diff --git a/cmd/fabric/fabric.go b/cmd/fabric/fabric.go index ecf5785..9d9b9e2 100644 --- a/cmd/fabric/fabric.go +++ b/cmd/fabric/fabric.go @@ -21,7 +21,7 @@ func NewFabricCmd(logger *logger.Logger) *cobra.Command { rootCmd.AddCommand( install.NewInstallCmd(logger), create.NewCreateCmd(logger), - query.NewQueryChaincodeCMD(os.Stdout, os.Stderr), + query.NewQueryChaincodeCMD(os.Stdout, os.Stderr, logger), invoke.NewInvokeChaincodeCMD(os.Stdout, os.Stderr, logger), nc.NewNCCmd(logger), ) diff --git a/cmd/fabric/install/install.go b/cmd/fabric/install/install.go index 371a384..401ae95 100644 --- a/cmd/fabric/install/install.go +++ b/cmd/fabric/install/install.go @@ -4,26 +4,29 @@ import ( "archive/tar" "bytes" "compress/gzip" + "context" + "crypto/x509" "encoding/json" + "encoding/pem" "fmt" "io/ioutil" "os" "path/filepath" "github.com/chainlaunch/chainlaunch/pkg/logger" + "github.com/golang/protobuf/proto" "github.com/pkg/errors" + "google.golang.org/grpc" "io" "strings" - "time" - - pb "github.com/hyperledger/fabric-protos-go/peer" - "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" - "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/lifecycle" - "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" - "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl" + + "github.com/chainlaunch/chainlaunch/pkg/fabric/networkconfig" + "github.com/chainlaunch/chainlaunch/pkg/fabric/policydsl" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/spf13/cobra" ) @@ -45,9 +48,75 @@ type installCmd struct { logger *logger.Logger } +func (c *installCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkConfig, org string, peerID string, userID string) (*grpc.ClientConn, identity.SigningIdentity, error) { + peerConfig, ok := nc.Peers[peerID] + if !ok { + return nil, nil, fmt.Errorf("peer %s not found in network config", peerID) + } + conn, err := c.getPeerConnection(peerConfig.URL, peerConfig.TLSCACerts.PEM) + if err != nil { + return nil, nil, err + } + orgConfig, ok := nc.Organizations[org] + if !ok { + return nil, nil, fmt.Errorf("organization %s not found in network config", org) + } + user, ok := orgConfig.Users[userID] + if !ok { + return nil, nil, fmt.Errorf("user %s not found in network config", userID) + } + userCert, err := identity.ReadCertificate(user.Cert.PEM) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to read user certificate for user %s and org %s", userID, org) + } + userPrivateKey, err := identity.ReadPrivateKey(user.Key.PEM) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to read user private key for user %s and org %s", userID, org) + } + userIdentity, err := identity.NewPrivateKeySigningIdentity(user.Cert.PEM, userCert, userPrivateKey) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) + } + return conn, userIdentity, nil +} + +func (c *installCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { + // Parse the TLS CA certificate + if tlsCACert == "" { + return nil, fmt.Errorf("TLS CA certificate is required") + } + // Read the certificate file + certBytes, err := os.ReadFile(tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) + } + + // Decode the PEM block + block, _ := pem.Decode(certBytes) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") + } + // Parse the certificate + _, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) + } + + networkNode := network.Node{ + Addr: address, + TLSCACert: tlsCACert, + } + conn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial connection: %w", err) + } + return conn, nil + +} + func (c installCmd) start() error { var chaincodeEndpoint string - + ctx := context.Background() if c.local { // Use local chaincode address directly chaincodeEndpoint = c.chaincodeAddress @@ -71,50 +140,85 @@ func (c installCmd) start() error { return err } _ = pkg - packageID := lifecycle.ComputePackageID(label, pkg) + packageID := chaincode.GetPackageID(label, pkg) c.logger.Infof("packageID: %s", packageID) - - // install chaincode in peers - configBackend := config.FromFile(c.networkConfig) - - clientsMap := map[string]*resmgmt.Client{} - sdk, err := fabsdk.New(configBackend) + nc, err := networkconfig.LoadFromFile(c.networkConfig) if err != nil { return err } - for idx, mspID := range c.organizations { - clientContext := sdk.Context(fabsdk.WithUser(c.users[idx]), fabsdk.WithOrg(mspID)) - clientsMap[mspID], err = resmgmt.New(clientContext) - if err != nil { - return err + + // // install chaincode in peers + // configBackend := config.FromFile(c.networkConfig) + + // clientsMap := map[string]*resmgmt.Client{} + // sdk, err := fabsdk.New(configBackend) + // if err != nil { + // return err + // } + // for idx, mspID := range c.organizations { + // clientContext := sdk.Context(fabsdk.WithUser(c.users[idx]), fabsdk.WithOrg(mspID)) + // clientsMap[mspID], err = resmgmt.New(clientContext) + // if err != nil { + // return err + // } + // } + for idx, org := range c.organizations { + orgConfig, ok := nc.Organizations[org] + if !ok { + return fmt.Errorf("organization %s not found in network config", org) } - } - for mspID, resmgmtClient := range clientsMap { - _, err = resmgmtClient.LifecycleInstallCC( - resmgmt.LifecycleInstallCCRequest{ - Label: label, - Package: pkg, - }, - resmgmt.WithTimeout(fab.ResMgmt, 20*time.Minute), - resmgmt.WithTimeout(fab.PeerResponse, 20*time.Minute), - ) - if err != nil { - return err + for _, peerID := range orgConfig.Peers { + peerConfig, ok := nc.Peers[peerID] + if !ok { + return fmt.Errorf("peer %s not found in network config", peerID) + } + conn, userIdentity, err := c.getPeerAndIdentityForOrg(nc, org, peerID, c.users[idx]) + if err != nil { + return err + } + defer conn.Close() + peerClient := chaincode.NewPeer(conn, userIdentity) + result, err := peerClient.Install(ctx, bytes.NewReader(pkg)) + if err != nil { + return errors.Wrapf(err, "failed to install chaincode for user %s and org %s", c.users[idx], org) + } + c.logger.Infof("Chaincode installed %s in %s", result.PackageId, peerConfig.URL) } - c.logger.Infof("Chaincode installed in %s", mspID) } - sp, err := policydsl.FromString(c.signaturePolicy) + + // sp, err := policydsl.FromString(c.signaturePolicy) + // if err != nil { + // return err + // } + applicationPolicy, err := chaincode.NewApplicationPolicy(c.signaturePolicy, "") if err != nil { return err } version := "1" sequence := 1 - resmgmtClient := clientsMap[c.organizations[0]] - committedCCs, err := resmgmtClient.LifecycleQueryCommittedCC( + allOrgGateways := []*chaincode.Gateway{} + for idx, org := range c.organizations { + orgConfig, ok := nc.Organizations[org] + if !ok { + return fmt.Errorf("organization %s not found in network config", org) + } + if len(orgConfig.Peers) == 0 { + return fmt.Errorf("organization %s has no peers", org) + } + conn, userIdentity, err := c.getPeerAndIdentityForOrg(nc, org, orgConfig.Peers[0], c.users[idx]) + if err != nil { + return err + } + defer conn.Close() + gateway := chaincode.NewGateway(conn, userIdentity) + allOrgGateways = append(allOrgGateways, gateway) + } + firstGateway := allOrgGateways[0] + committedCC, err := firstGateway.QueryCommittedWithName( + ctx, c.channel, - resmgmt.LifecycleQueryCommittedCCRequest{Name: c.chaincode}, - resmgmt.WithTargetFilter(&multipleMSPFilter{mspIDs: c.organizations}), + c.chaincode, ) if err != nil { c.logger.Warnf("Error when getting commited chaincodes: %v", err) @@ -132,12 +236,22 @@ func (c installCmd) start() error { return err } } - c.logger.Infof("Commited CCs=%d", len(committedCCs)) - shouldCommit := len(committedCCs) == 0 - if len(committedCCs) > 0 { - firstCommittedCC := committedCCs[0] - signaturePolicyString := firstCommittedCC.SignaturePolicy.String() - newSignaturePolicyString := sp.String() + c.logger.Infof("Commited CC=%v", committedCC) + shouldCommit := committedCC == nil + if committedCC != nil { + appPolicy := pb.ApplicationPolicy{} + err = proto.Unmarshal(committedCC.GetValidationParameter(), &appPolicy) + if err != nil { + return err + } + var signaturePolicyString string + switch policy := appPolicy.Type.(type) { + case *pb.ApplicationPolicy_SignaturePolicy: + signaturePolicyString = policy.SignaturePolicy.String() + default: + return errors.Errorf("unsupported policy type %T", policy) + } + newSignaturePolicyString := applicationPolicy.String() if signaturePolicyString != newSignaturePolicyString { c.logger.Infof("Signature policy changed, old=%s new=%s", signaturePolicyString, newSignaturePolicyString) shouldCommit = true @@ -145,7 +259,7 @@ func (c installCmd) start() error { c.logger.Infof("Signature policy not changed, signaturePolicy=%s", signaturePolicyString) } // compare collections - oldCollections := firstCommittedCC.CollectionConfig + oldCollections := committedCC.GetCollections().GetConfig() newCollections := collections if len(oldCollections) != len(newCollections) { c.logger.Infof("Collection config changed, old=%d new=%d", len(oldCollections), len(newCollections)) @@ -170,68 +284,69 @@ func (c installCmd) start() error { } } } - if len(committedCCs) > 0 { + if committedCC != nil { if shouldCommit { - version = committedCCs[len(committedCCs)-1].Version - sequence = int(committedCCs[len(committedCCs)-1].Sequence) + 1 + version = committedCC.GetVersion() + sequence = int(committedCC.GetSequence()) + 1 } else { - version = committedCCs[len(committedCCs)-1].Version - sequence = int(committedCCs[len(committedCCs)-1].Sequence) + version = committedCC.GetVersion() + sequence = int(committedCC.GetSequence()) } c.logger.Infof("Chaincode already committed, version=%s sequence=%d", version, sequence) } c.logger.Infof("Should commit=%v", shouldCommit) - // approve chaincode in orgs - approveCCRequest := resmgmt.LifecycleApproveCCRequest{ - Name: label, - Version: version, + // // approve chaincode in orgs + // approveCCRequest := resmgmt.LifecycleApproveCCRequest{ + // Name: label, + // Version: version, + // PackageID: packageID, + // Sequence: int64(sequence), + // CollectionConfig: collections, + // EndorsementPlugin: "escc", + // ValidationPlugin: "vscc", + // SignaturePolicy: sp, + // InitRequired: false, + // } + + chaincodeDef := &chaincode.Definition{ + ChannelName: c.channel, PackageID: packageID, - Sequence: int64(sequence), - CollectionConfig: collections, + Name: c.chaincode, + Version: version, EndorsementPlugin: "escc", ValidationPlugin: "vscc", - SignaturePolicy: sp, + Sequence: int64(sequence), + ApplicationPolicy: applicationPolicy, InitRequired: false, + Collections: nil, } - for mspID, resmgmtClient := range clientsMap { - - txID, err := resmgmtClient.LifecycleApproveCC( - c.channel, - approveCCRequest, - resmgmt.WithTargetFilter(&mspFilter{mspID: mspID}), - resmgmt.WithTimeout(fab.ResMgmt, 20*time.Minute), - resmgmt.WithTimeout(fab.PeerResponse, 20*time.Minute), - ) + for idx, gateway := range allOrgGateways { + err := gateway.Approve(ctx, chaincodeDef) + if err != nil { + c.logger.Errorf("Error when approving chaincode: %v", err) + return err + } + c.logger.Infof("Chaincode approved, org=%s", c.organizations[idx]) if err != nil && !strings.Contains(err.Error(), "redefine uncommitted") { c.logger.Errorf("Error when approving chaincode: %v", err) return err } - c.logger.Infof("Chaincode approved, org=%s tx=%s", mspID, txID) + c.logger.Infof("Chaincode approved, org=%s", c.organizations[idx]) } if shouldCommit { + // commit chaincode in orgs - txID, err := resmgmtClient.LifecycleCommitCC( - c.channel, - resmgmt.LifecycleCommitCCRequest{ - Name: label, - Version: version, - Sequence: int64(sequence), - CollectionConfig: collections, - EndorsementPlugin: "escc", - ValidationPlugin: "vscc", - SignaturePolicy: sp, - InitRequired: false, - }, - resmgmt.WithTimeout(fab.ResMgmt, 2*time.Minute), - resmgmt.WithTimeout(fab.PeerResponse, 2*time.Minute), - resmgmt.WithTargetFilter(&multipleMSPFilter{mspIDs: c.organizations}), + err := firstGateway.Commit( + ctx, + chaincodeDef, ) if err != nil { + c.logger.Errorf("Error when committing chaincode: %v", err) return err } - c.logger.Infof("Chaincode committed, tx=%s", txID) + c.logger.Infof("Chaincode committed") + } - sdk.Close() if c.envFile != "" { err = os.WriteFile(c.envFile, []byte(fmt.Sprintf(` @@ -247,30 +362,6 @@ CORE_PEER_TLS_ENABLED=false return nil } -type multipleMSPFilter struct { - mspIDs []string -} - -// Accept returns true if this peer is to be included in the target list -func (f *multipleMSPFilter) Accept(peer fab.Peer) bool { - // check if its of one of the mspIDs - for _, mspID := range f.mspIDs { - if peer.MSPID() == mspID { - return true - } - } - return false -} - -type mspFilter struct { - mspID string -} - -// Accept returns true if this peer is to be included in the target list -func (f *mspFilter) Accept(peer fab.Peer) bool { - return peer.MSPID() == f.mspID -} - func (c *installCmd) getChaincodePackage(label string, codeTarGz []byte) ([]byte, error) { var err error metadataJson := fmt.Sprintf(` diff --git a/cmd/fabric/invoke/invoke.go b/cmd/fabric/invoke/invoke.go index 4ac3ebb..653355e 100644 --- a/cmd/fabric/invoke/invoke.go +++ b/cmd/fabric/invoke/invoke.go @@ -1,14 +1,21 @@ package invoke import ( + "crypto/x509" + "encoding/pem" "fmt" "io" + "math/rand/v2" + "os" + "github.com/chainlaunch/chainlaunch/pkg/fabric/networkconfig" "github.com/chainlaunch/chainlaunch/pkg/logger" - "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + "github.com/hyperledger/fabric-gateway/pkg/client" + "github.com/hyperledger/fabric-gateway/pkg/identity" + "github.com/pkg/errors" "github.com/spf13/cobra" + "google.golang.org/grpc" ) type invokeChaincodeCmd struct { @@ -25,44 +32,167 @@ type invokeChaincodeCmd struct { func (c *invokeChaincodeCmd) validate() error { return nil } + +func (c *invokeChaincodeCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkConfig, org string, peerID string, userID string) (*grpc.ClientConn, identity.Sign, *identity.X509Identity, error) { + peerConfig, ok := nc.Peers[peerID] + if !ok { + return nil, nil, nil, fmt.Errorf("peer %s not found in network config", peerID) + } + conn, err := c.getPeerConnection(peerConfig.URL, peerConfig.TLSCACerts.PEM) + if err != nil { + return nil, nil, nil, err + } + orgConfig, ok := nc.Organizations[org] + if !ok { + return nil, nil, nil, fmt.Errorf("organization %s not found in network config", org) + } + user, ok := orgConfig.Users[userID] + if !ok { + return nil, nil, nil, fmt.Errorf("user %s not found in network config", userID) + } + userCert, err := identity.CertificateFromPEM([]byte(user.Cert.PEM)) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to read user certificate for user %s and org %s", userID, org) + } + userPrivateKey, err := identity.PrivateKeyFromPEM([]byte(user.Key.PEM)) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to read user private key for user %s and org %s", userID, org) + } + userPK, err := identity.NewPrivateKeySign(userPrivateKey) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) + } + userIdentity, err := identity.NewX509Identity(c.mspID, userCert) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) + } + return conn, userPK, userIdentity, nil +} + +func (c *invokeChaincodeCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { + // Parse the TLS CA certificate + if tlsCACert == "" { + return nil, fmt.Errorf("TLS CA certificate is required") + } + // Read the certificate file + certBytes, err := os.ReadFile(tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) + } + + // Decode the PEM block + block, _ := pem.Decode(certBytes) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") + } + // Parse the certificate + _, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) + } + + networkNode := network.Node{ + Addr: address, + TLSCACert: tlsCACert, + } + conn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial connection: %w", err) + } + return conn, nil + +} + func (c *invokeChaincodeCmd) run(out io.Writer) error { - configBackend := config.FromFile(c.configPath) - sdk, err := fabsdk.New(configBackend) + networkConfig, err := networkconfig.LoadFromFile(c.configPath) if err != nil { return err } - chContext := sdk.ChannelContext( - c.channel, - fabsdk.WithUser(c.userName), - fabsdk.WithOrg(c.mspID), - ) - ch, err := channel.New(chContext) + + orgConfig, ok := networkConfig.Organizations[c.mspID] + if !ok { + return fmt.Errorf("organization %s not found", c.mspID) + } + _, ok = orgConfig.Users[c.userName] + if !ok { + return fmt.Errorf("user %s not found", c.userName) + } + peers := orgConfig.Peers + if len(peers) == 0 { + return fmt.Errorf("no peers found for organization %s", c.mspID) + } + // Get a random peer from the organization's peers + // If no specific peer ID is provided, select a random one + // Generate a random index + randomIndex := rand.Int() % len(peers) + + peerID := peers[randomIndex] + c.logger.Infof("Randomly selected peer: %s", peerID) + + conn, userPK, userIdentity, err := c.getPeerAndIdentityForOrg(networkConfig, c.mspID, peerID, c.userName) if err != nil { return err } - var args [][]byte + defer conn.Close() + gateway, err := client.Connect(userIdentity, client.WithSign(userPK), client.WithClientConnection(conn)) + if err != nil { + return err + } + defer gateway.Close() + network := gateway.GetNetwork(c.channel) + contract := network.GetContract(c.chaincode) + args := [][]byte{} for _, arg := range c.args { args = append(args, []byte(arg)) } - response, err := ch.Execute( - channel.Request{ - ChaincodeID: c.chaincode, - Fcn: c.fcn, - Args: args, - TransientMap: nil, - InvocationChain: nil, - IsInit: false, - }, - ) + + response, err := contract.NewProposal(c.fcn, client.WithBytesArguments(args...)) if err != nil { - return err + return errors.Wrapf(err, "failed to create proposal") + } + endorseResponse, err := response.Endorse() + if err != nil { + return errors.Wrapf(err, "failed to endorse proposal") } - _, err = fmt.Fprint(out, string(response.Payload)) + submitResponse, err := endorseResponse.Submit() + if err != nil { + return errors.Wrapf(err, "failed to submit proposal") + } + responseBytes, err := submitResponse.Bytes() + if err != nil { + return errors.Wrapf(err, "failed to get response bytes") + } + + _, err = fmt.Fprint(out, string(responseBytes)) if err != nil { return err } - c.logger.Infof("txid=%s", response.TransactionID) + c.logger.Infof("txid=%s", submitResponse.TransactionID) return nil + + // var args [][]byte + // for _, arg := range c.args { + // args = append(args, []byte(arg)) + // } + // response, err := ch.Execute( + // channel.Request{ + // ChaincodeID: c.chaincode, + // Fcn: c.fcn, + // Args: args, + // TransientMap: nil, + // InvocationChain: nil, + // IsInit: false, + // }, + // ) + // if err != nil { + // return err + // } + // _, err = fmt.Fprint(out, string(response.Payload)) + // if err != nil { + // return err + // } + // c.logger.Infof("txid=%s", response.TransactionID) + // return nil } func NewInvokeChaincodeCMD(out io.Writer, errOut io.Writer, logger *logger.Logger) *cobra.Command { diff --git a/cmd/fabric/query/query.go b/cmd/fabric/query/query.go index faa399c..f8d9829 100644 --- a/cmd/fabric/query/query.go +++ b/cmd/fabric/query/query.go @@ -1,12 +1,21 @@ package query import ( + "crypto/x509" + "encoding/pem" "fmt" - "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" - "github.com/spf13/cobra" "io" + "math/rand/v2" + "os" + + "github.com/chainlaunch/chainlaunch/pkg/fabric/networkconfig" + "github.com/chainlaunch/chainlaunch/pkg/logger" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + "github.com/hyperledger/fabric-gateway/pkg/client" + "github.com/hyperledger/fabric-gateway/pkg/identity" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "google.golang.org/grpc" ) type queryChaincodeCmd struct { @@ -17,52 +26,132 @@ type queryChaincodeCmd struct { chaincode string fcn string args []string + logger *logger.Logger } func (c *queryChaincodeCmd) validate() error { return nil } + +func (c *queryChaincodeCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkConfig, org string, peerID string, userID string) (*grpc.ClientConn, identity.Sign, *identity.X509Identity, error) { + peerConfig, ok := nc.Peers[peerID] + if !ok { + return nil, nil, nil, fmt.Errorf("peer %s not found in network config", peerID) + } + conn, err := c.getPeerConnection(peerConfig.URL, peerConfig.TLSCACerts.PEM) + if err != nil { + return nil, nil, nil, err + } + orgConfig, ok := nc.Organizations[org] + if !ok { + return nil, nil, nil, fmt.Errorf("organization %s not found in network config", org) + } + user, ok := orgConfig.Users[userID] + if !ok { + return nil, nil, nil, fmt.Errorf("user %s not found in network config", userID) + } + userCert, err := identity.CertificateFromPEM([]byte(user.Cert.PEM)) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to read user certificate for user %s and org %s", userID, org) + } + userPrivateKey, err := identity.PrivateKeyFromPEM([]byte(user.Key.PEM)) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to read user private key for user %s and org %s", userID, org) + } + userPK, err := identity.NewPrivateKeySign(userPrivateKey) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) + } + userIdentity, err := identity.NewX509Identity(c.mspID, userCert) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) + } + return conn, userPK, userIdentity, nil +} + +func (c *queryChaincodeCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { + if tlsCACert == "" { + return nil, fmt.Errorf("TLS CA certificate is required") + } + certBytes, err := os.ReadFile(tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) + } + + block, _ := pem.Decode(certBytes) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") + } + _, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) + } + + networkNode := network.Node{ + Addr: address, + TLSCACert: tlsCACert, + } + conn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial connection: %w", err) + } + return conn, nil +} + func (c *queryChaincodeCmd) run(out io.Writer) error { - configBackend := config.FromFile(c.configPath) - sdk, err := fabsdk.New(configBackend) + networkConfig, err := networkconfig.LoadFromFile(c.configPath) if err != nil { return err } - chContext := sdk.ChannelContext( - c.channel, - fabsdk.WithUser(c.userName), - fabsdk.WithOrg(c.mspID), - ) - ch, err := channel.New(chContext) + + orgConfig, ok := networkConfig.Organizations[c.mspID] + if !ok { + return fmt.Errorf("organization %s not found", c.mspID) + } + _, ok = orgConfig.Users[c.userName] + if !ok { + return fmt.Errorf("user %s not found", c.userName) + } + peers := orgConfig.Peers + if len(peers) == 0 { + return fmt.Errorf("no peers found for organization %s", c.mspID) + } + + randomIndex := rand.Int() % len(peers) + peerID := peers[randomIndex] + c.logger.Infof("Randomly selected peer: %s", peerID) + + conn, userPK, userIdentity, err := c.getPeerAndIdentityForOrg(networkConfig, c.mspID, peerID, c.userName) if err != nil { return err } - var args [][]byte - for _, arg := range c.args { - args = append(args, []byte(arg)) - } - response, err := ch.Query( - channel.Request{ - ChaincodeID: c.chaincode, - Fcn: c.fcn, - Args: args, - TransientMap: nil, - InvocationChain: nil, - IsInit: false, - }, - ) + defer conn.Close() + + gateway, err := client.Connect(userIdentity, client.WithSign(userPK), client.WithClientConnection(conn)) if err != nil { return err } - _, err = fmt.Fprint(out, string(response.Payload)) + defer gateway.Close() + + network := gateway.GetNetwork(c.channel) + contract := network.GetContract(c.chaincode) + + result, err := contract.EvaluateTransaction(c.fcn, c.args...) + if err != nil { + return errors.Wrapf(err, "failed to evaluate transaction") + } + + _, err = fmt.Fprint(out, string(result)) if err != nil { return err } return nil } -func NewQueryChaincodeCMD(out io.Writer, errOut io.Writer) *cobra.Command { - c := &queryChaincodeCmd{} +func NewQueryChaincodeCMD(out io.Writer, errOut io.Writer, logger *logger.Logger) *cobra.Command { + c := &queryChaincodeCmd{ + logger: logger, + } cmd := &cobra.Command{ Use: "query", RunE: func(cmd *cobra.Command, args []string) error { diff --git a/docs/docs.go b/docs/docs.go index 3ea376a..05800be 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Package docs Code generated by swaggo/swag at 2025-03-17 22:16:01.61695 +0100 CET m=+1.512738168. DO NOT EDIT +// Package docs Code generated by swaggo/swag at 2025-04-04 10:59:35.206745 +0200 CEST m=+1.975204501. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -3145,6 +3145,56 @@ const docTemplate = `{ } } }, + "/nodes/{id}/channels": { + "get": { + "description": "Retrieves all channels for a specific Fabric node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Get channels for a Fabric node", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeChannelsResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/nodes/{id}/events": { "get": { "description": "Get a paginated list of events for a specific node", @@ -4448,6 +4498,20 @@ const docTemplate = `{ } } }, + "http.ChannelResponse": { + "type": "object", + "properties": { + "blockNum": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "http.ConsenterConfig": { "type": "object", "required": [ @@ -4973,6 +5037,20 @@ const docTemplate = `{ } } }, + "http.NodeChannelsResponse": { + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/http.ChannelResponse" + } + }, + "nodeId": { + "type": "integer" + } + } + }, "http.NodeEventResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 7fec2f3..ce44d39 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3143,6 +3143,56 @@ } } }, + "/nodes/{id}/channels": { + "get": { + "description": "Retrieves all channels for a specific Fabric node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Get channels for a Fabric node", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeChannelsResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/nodes/{id}/events": { "get": { "description": "Get a paginated list of events for a specific node", @@ -4446,6 +4496,20 @@ } } }, + "http.ChannelResponse": { + "type": "object", + "properties": { + "blockNum": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "http.ConsenterConfig": { "type": "object", "required": [ @@ -4971,6 +5035,20 @@ } } }, + "http.NodeChannelsResponse": { + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/http.ChannelResponse" + } + }, + "nodeId": { + "type": "integer" + } + } + }, "http.NodeEventResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 65454f0..e2596fe 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -323,6 +323,15 @@ definitions: name: type: string type: object + http.ChannelResponse: + properties: + blockNum: + type: integer + createdAt: + type: string + name: + type: string + type: object http.ConsenterConfig: properties: id: @@ -729,6 +738,15 @@ definitions: updatedAt: type: string type: object + http.NodeChannelsResponse: + properties: + channels: + items: + $ref: '#/definitions/http.ChannelResponse' + type: array + nodeId: + type: integer + type: object http.NodeEventResponse: properties: created_at: @@ -3739,6 +3757,39 @@ paths: summary: Get a node tags: - nodes + /nodes/{id}/channels: + get: + consumes: + - application/json + description: Retrieves all channels for a specific Fabric node + parameters: + - description: Node ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.NodeChannelsResponse' + "400": + description: Validation error + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Node not found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Get channels for a Fabric node + tags: + - nodes /nodes/{id}/events: get: consumes: diff --git a/go.mod b/go.mod index 4ad4c47..219f437 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang/protobuf v1.5.4 github.com/google/uuid v1.6.0 github.com/hyperledger/fabric v2.1.1+incompatible - github.com/hyperledger/fabric-config v0.1.0 + github.com/hyperledger/fabric-config v0.3.0 github.com/hyperledger/fabric-protos-go v0.3.0 github.com/mattn/go-sqlite3 v1.14.24 github.com/nuts-foundation/go-did v0.15.0 @@ -57,11 +57,14 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.4.4 // indirect github.com/google/certificate-transparency-go v1.0.21 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect + github.com/hyperledger/fabric-gateway v1.5.0 // indirect github.com/hyperledger/fabric-lib-go v1.0.0 // indirect + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -71,8 +74,9 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/mr-tron/base58 v1.1.0 // indirect @@ -81,6 +85,7 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.0 // indirect @@ -127,6 +132,7 @@ require ( github.com/go-playground/validator/v10 v10.24.0 github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hyperledger/fabric-admin-sdk v0.1.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect @@ -139,4 +145,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/hyperledger/fabric-sdk-go => github.com/kfsoftware/fabric-sdk-go v0.0.0-20250318193343-db7cb6f42306 +replace github.com/hyperledger/fabric-admin-sdk => /Users/davidviejo/github-libs/fabric-admin-sdk \ No newline at end of file diff --git a/go.sum b/go.sum index 9ca548a..79c11a9 100644 --- a/go.sum +++ b/go.sum @@ -34,12 +34,15 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/IBM/sarama v1.45.1 h1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0= +github.com/IBM/sarama v1.45.1/go.mod h1:qifDhA3VWSrQ1TjSMyxDl3nYL3oX2C83u+G6L79sq4w= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -76,6 +79,7 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -100,6 +104,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw= github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo= +github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7ZeI= +github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4= github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -112,6 +118,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= @@ -120,13 +127,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= +github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -134,8 +146,12 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= +github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= @@ -146,6 +162,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.15.1 h1:ZR5hh6NXem4hNnhMIrdPFMTGHo6USTwWn47hbs6gRj4= github.com/ethereum/go-ethereum v1.15.1/go.mod h1:wGQINJKEVUunCeoaA9C9qKMQ9GEOsEIunzzqTUO2F6Y= +github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= +github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -156,12 +174,16 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= @@ -173,11 +195,17 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -186,13 +214,21 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -201,12 +237,18 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -215,6 +257,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -228,6 +272,8 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -249,10 +295,14 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= +github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -264,6 +314,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -284,11 +335,16 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -307,8 +363,13 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -326,26 +387,55 @@ github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hyperledger/fabric v2.1.1+incompatible h1:cYYRv3vVg4kA6DmrixLxwn1nwBEUuYda8DsMwlaMKbY= github.com/hyperledger/fabric v2.1.1+incompatible/go.mod h1:tGFAOCT696D3rG0Vofd2dyWYLySHlh0aQjf7Q1HAju0= +github.com/hyperledger/fabric-admin-sdk v0.1.0 h1:MwlUySAEEKlTMK5ztzxsr8eVxoUtqSrPGr0RTUPgOys= +github.com/hyperledger/fabric-admin-sdk v0.1.0/go.mod h1:DWOavy5E4F7ADmZZHROUj/RSAtvnoCQ4vDBMPZ7ydng= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8= github.com/hyperledger/fabric-config v0.1.0 h1:TsR3y5xEoUmXWfp8tcDycjJhVvXEHiV5kfZIxuIte08= github.com/hyperledger/fabric-config v0.1.0/go.mod h1:aeDZ0moG/qKvwLjddcqYr8+58/oNaJy3HE0tI01546c= +github.com/hyperledger/fabric-config v0.3.0 h1:FS5/dc9GAniljP6RYxQRG92AaiBVoN2vTvtOvnWqeQs= +github.com/hyperledger/fabric-config v0.3.0/go.mod h1:kSevTn78K83Suc++JsEo7Nt1tYIPqDajW+ORz3OhWlg= +github.com/hyperledger/fabric-gateway v1.5.0 h1:JChlqtJNm2479Q8YWJ6k8wwzOiu2IRrV3K8ErsQmdTU= +github.com/hyperledger/fabric-gateway v1.5.0/go.mod h1:v13OkXAp7pKi4kh6P6epn27SyivRbljr8Gkfy8JlbtM= +github.com/hyperledger/fabric-gateway v1.7.1 h1:bHpQNuvXHlQ11X/vzUbj/0YWm2q+L5cMkIQGvlp47Ac= +github.com/hyperledger/fabric-gateway v1.7.1/go.mod h1:A9ORxKMXB3vNgL0woWv17pMDdJGrWGtCbTV3FQLMS/Y= github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324= github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc= +github.com/hyperledger/fabric-lib-go v1.1.2 h1:3eHwudGZC5Ex7go5UAzVKhpF34gypPZGfSZksBKLWvE= +github.com/hyperledger/fabric-lib-go v1.1.2/go.mod h1:SHNCq8AB0VpHAmvJEtdbzabv6NNV1F48JdmDihasBjc= github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.3.0 h1:MXxy44WTMENOh5TI8+PCK2x6pMj47Go2vFRKDHB2PZs= github.com/hyperledger/fabric-protos-go v0.3.0/go.mod h1:WWnyWP40P2roPmmvxsUXSvVI/CF6vwY1K1UFidnKBys= +github.com/hyperledger/fabric-protos-go v0.3.7 h1:4Dp6esioyrbHaRZY8HcQG/ZN6ABPXcVEmGZWJlKc9mE= +github.com/hyperledger/fabric-protos-go v0.3.7/go.mod h1:F+MmFQ9mnJzxB9Gus13XMoXrSJbIK/2QJOanEUZ5zoo= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 h1:sQ5qv8vQQfwewa1JlCiSCC8dLElmaU2/frLolpgibEY= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7/go.mod h1:bJnwzfv03oZQeCc863pdGTDgf5nmCy6Za3RAE7d2XsQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -370,6 +460,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -404,23 +496,34 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= +github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -433,6 +536,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -455,6 +560,8 @@ github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ8 github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= @@ -482,10 +589,14 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= @@ -495,10 +606,16 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -516,6 +633,8 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -524,6 +643,8 @@ github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= @@ -531,6 +652,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -539,8 +662,12 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= +github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -550,6 +677,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= @@ -569,26 +698,38 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.1.1 h1:/8JBRFO4eoHu1TmpsLgNBq1CQgRUg4GolYlEFieqJgo= github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -596,10 +737,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= @@ -614,18 +761,29 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU= github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.40.3-0.20250311103038-7794c8c0723b h1:PFOWooJRLwIuZk9i3ihzKzZffPrAVyOCzPInvLbn140= +github.com/weppos/publicsuffix-go v0.40.3-0.20250311103038-7794c8c0723b/go.mod h1:EACzvcFHnxqmDapI/oqMjtpXz+mtjNzJe7r1zhRczZ0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw= github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8= +github.com/zmap/zcrypto v0.0.0-20191112190257-7f2fe6faf8cf/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8= +github.com/zmap/zcrypto v0.0.0-20250324021606-4f0ea0eaccac h1:FWk36AHMBeDuQ3fl76mZj6LQjzAgnJVAErhMsTgqHps= +github.com/zmap/zcrypto v0.0.0-20250324021606-4f0ea0eaccac/go.mod h1:7xhokKx8eQnfBRgAz75bGTvgc8yIW+crCtP3nvADosk= github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg= github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY= +github.com/zmap/zlint v1.1.0 h1:Vyh2GmprXw5TLmKmkTa2BgFvvYAFBValBFesqkKsszM= +github.com/zmap/zlint v1.1.0/go.mod h1:3MvSF/QhEftzpxKhh3jkBIOvugsSDYMCofl+UaIv0ww= +github.com/zmap/zlint/v3 v3.6.5 h1:SLKtIQWeRKgz+e2iMZmJNXZQp64zgGE5Q0dBlvp4oR8= +github.com/zmap/zlint/v3 v3.6.5/go.mod h1:ohpDnLlp+dTyY0FvDEaCqbUxX7qGipY5JUG+/8c+BNg= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -639,6 +797,8 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= @@ -659,16 +819,23 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -681,8 +848,12 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -713,6 +884,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -750,10 +923,17 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -770,6 +950,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -813,28 +995,44 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -855,6 +1053,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -883,8 +1082,12 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -935,6 +1138,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= @@ -944,10 +1148,13 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0 h1:0K7wTWyzxZ7J+L47+LbFogJW1nn/gnnMCN0vGXNYtTI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -967,6 +1174,8 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -981,6 +1190,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -1004,11 +1215,13 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= diff --git a/internal/configtxgen/encoder/encoder.go b/internal/configtxgen/encoder/encoder.go new file mode 100644 index 0000000..0f6153c --- /dev/null +++ b/internal/configtxgen/encoder/encoder.go @@ -0,0 +1,612 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package encoder + +import ( + "errors" + "fmt" + "os" + + icc "github.com/hyperledger/fabric-admin-sdk/internal/channelconfig" + "github.com/hyperledger/fabric-admin-sdk/internal/configtxgen/genesisconfig" + "github.com/hyperledger/fabric-admin-sdk/internal/configtxlator/update" + "github.com/hyperledger/fabric-admin-sdk/internal/genesis" + "github.com/hyperledger/fabric-admin-sdk/internal/msp" + ipc "github.com/hyperledger/fabric-admin-sdk/internal/policies" + ipd "github.com/hyperledger/fabric-admin-sdk/internal/policydsl" + "github.com/hyperledger/fabric-admin-sdk/internal/protoutil" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" + pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/protobuf/proto" +) + +const ( + ordererAdminsPolicyName = "/Channel/Orderer/Admins" +) + +const ( + // ConsensusTypeSolo identifies the solo consensus implementation. + ConsensusTypeSolo = "solo" + // ConsensusTypeKafka identifies the Kafka-based consensus implementation. + ConsensusTypeKafka = "kafka" + // ConsensusTypeKafka identifies the Kafka-based consensus implementation. + ConsensusTypeEtcdRaft = "etcdraft" + ConsensusTypeBFT = "BFT" + + // BlockValidationPolicyKey TODO + BlockValidationPolicyKey = "BlockValidation" + + // OrdererAdminsPolicy is the absolute path to the orderer admins policy + OrdererAdminsPolicy = "/Channel/Orderer/Admins" + + // SignaturePolicyType is the 'Type' string for signature policies + SignaturePolicyType = "Signature" + + // ImplicitMetaPolicyType is the 'Type' string for implicit meta policies + ImplicitMetaPolicyType = "ImplicitMeta" +) + +func addValue(cg *cb.ConfigGroup, value icc.ConfigValue, modPolicy string) { + cg.Values[value.Key()] = &cb.ConfigValue{ + Value: protoutil.MarshalOrPanic(value.Value()), + ModPolicy: modPolicy, + } +} + +func addPolicy(cg *cb.ConfigGroup, policy ipc.ConfigPolicy, modPolicy string) { + cg.Policies[policy.Key()] = &cb.ConfigPolicy{ + Policy: policy.Value(), + ModPolicy: modPolicy, + } +} + +func AddOrdererPolicies(cg *cb.ConfigGroup, policyMap map[string]*genesisconfig.Policy, modPolicy string) error { + switch { + case policyMap == nil: + return errors.New("no policies defined") + case policyMap[BlockValidationPolicyKey] == nil: + return errors.New("no BlockValidation policy defined") + } + + return AddPolicies(cg, policyMap, modPolicy) +} + +func AddPolicies(cg *cb.ConfigGroup, policyMap map[string]*genesisconfig.Policy, modPolicy string) error { + switch { + case policyMap == nil: + return errors.New("no policies defined") + case policyMap[icc.AdminsPolicyKey] == nil: + return errors.New("no Admins policy defined") + case policyMap[icc.ReadersPolicyKey] == nil: + return errors.New("no Readers policy defined") + case policyMap[icc.WritersPolicyKey] == nil: + return errors.New("no Writers policy defined") + } + + for policyName, policy := range policyMap { + configPolicy, err := newConfigPolicy(policy, modPolicy) + if err != nil { + return err + } + + cg.Policies[policyName] = configPolicy + } + return nil +} + +func newConfigPolicy(policy *genesisconfig.Policy, modPolicy string) (*cb.ConfigPolicy, error) { + switch policy.Type { + case ImplicitMetaPolicyType: + imp, err := ipc.ImplicitMetaFromString(policy.Rule) + if err != nil { + return nil, fmt.Errorf("invalid implicit meta policy rule '%s' %w", policy.Rule, err) + } + result := &cb.ConfigPolicy{ + ModPolicy: modPolicy, + Policy: &cb.Policy{ + Type: int32(cb.Policy_IMPLICIT_META), + Value: protoutil.MarshalOrPanic(imp), + }, + } + return result, nil + case SignaturePolicyType: + sp, err := ipd.FromString(policy.Rule) + if err != nil { + return nil, fmt.Errorf("invalid signature policy rule '%s' %w", policy.Rule, err) + } + result := &cb.ConfigPolicy{ + ModPolicy: modPolicy, + Policy: &cb.Policy{ + Type: int32(cb.Policy_SIGNATURE), + Value: protoutil.MarshalOrPanic(sp), + }, + } + return result, nil + default: + return nil, fmt.Errorf("unknown policy type: %s", policy.Type) + } +} + +func NewConfigGroup() *cb.ConfigGroup { + return &cb.ConfigGroup{ + Groups: make(map[string]*cb.ConfigGroup), + Values: make(map[string]*cb.ConfigValue), + Policies: make(map[string]*cb.ConfigPolicy), + } +} + +// NewChannelGroup defines the root of the channel configuration. It defines basic operating principles like the hashing +// algorithm used for the blocks, as well as the location of the ordering service. It will recursively call into the +// NewOrdererGroup, NewConsortiumsGroup, and NewApplicationGroup depending on whether these sub-elements are set in the +// configuration. All mod_policy values are set to "Admins" for this group, with the exception of the OrdererAddresses +// value which is set to "/Channel/Orderer/Admins". +// +//nolint:cyclop +func NewChannelGroup(conf *genesisconfig.Profile) (*cb.ConfigGroup, error) { + channelGroup := NewConfigGroup() + if err := AddPolicies(channelGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to channel group %w", err) + } + + addValue(channelGroup, icc.HashingAlgorithmValue(), icc.AdminsPolicyKey) + addValue(channelGroup, icc.BlockDataHashingStructureValue(), icc.AdminsPolicyKey) + if conf.Orderer != nil && len(conf.Orderer.Addresses) > 0 { + addValue(channelGroup, icc.OrdererAddressesValue(conf.Orderer.Addresses), ordererAdminsPolicyName) + } + + if conf.Consortium != "" { + addValue(channelGroup, icc.ConsortiumValue(conf.Consortium), icc.AdminsPolicyKey) + } + + if len(conf.Capabilities) > 0 { + addValue(channelGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) + } + + var err error + if conf.Orderer != nil { + channelGroup.Groups[icc.OrdererGroupKey], err = NewOrdererGroup(conf.Orderer) + if err != nil { + return nil, fmt.Errorf("could not create orderer group %w", err) + } + } + + if conf.Application != nil { + channelGroup.Groups[icc.ApplicationGroupKey], err = NewApplicationGroup(conf.Application) + if err != nil { + return nil, fmt.Errorf("could not create application group %w", err) + } + } + + if conf.Consortiums != nil { + channelGroup.Groups[icc.ConsortiumsGroupKey], err = NewConsortiumsGroup(conf.Consortiums) + if err != nil { + return nil, fmt.Errorf("could not create consortiums group %w", err) + } + } + + channelGroup.ModPolicy = icc.AdminsPolicyKey + return channelGroup, nil +} + +// NewOrdererGroup returns the orderer component of the channel configuration. It defines parameters of the ordering service +// about how large blocks should be, how frequently they should be emitted, etc. as well as the organizations of the ordering network. +// It sets the mod_policy of all elements to "Admins". This group is always present in any channel configuration. +// +//nolint:cyclop +func NewOrdererGroup(conf *genesisconfig.Orderer) (*cb.ConfigGroup, error) { + ordererGroup := NewConfigGroup() + if err := AddOrdererPolicies(ordererGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to orderer group %w", err) + } + addValue(ordererGroup, icc.BatchSizeValue( + conf.BatchSize.MaxMessageCount, + conf.BatchSize.AbsoluteMaxBytes, + conf.BatchSize.PreferredMaxBytes, + ), icc.AdminsPolicyKey) + addValue(ordererGroup, icc.BatchTimeoutValue(conf.BatchTimeout.String()), icc.AdminsPolicyKey) + addValue(ordererGroup, icc.ChannelRestrictionsValue(conf.MaxChannels), icc.AdminsPolicyKey) + + if len(conf.Capabilities) > 0 { + addValue(ordererGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) + } + + var consensusMetadata []byte + var err error + + switch conf.OrdererType { + case ConsensusTypeSolo: + case ConsensusTypeEtcdRaft: + if consensusMetadata, err = icc.MarshalEtcdRaftMetadata(conf.EtcdRaft); err != nil { + return nil, fmt.Errorf("cannot marshal metadata for orderer type %s: %w", ConsensusTypeEtcdRaft, err) + } + case ConsensusTypeBFT: + consenterProtos, err := consenterProtosFromConfig(conf.ConsenterMapping) + if err != nil { + return nil, fmt.Errorf("cannot load consenter config for orderer type %s: %w", ConsensusTypeBFT, err) + } + addValue(ordererGroup, icc.OrderersValue(consenterProtos), icc.AdminsPolicyKey) + if consensusMetadata, err = icc.MarshalBFTOptions(conf.SmartBFT); err != nil { + return nil, fmt.Errorf("consenter options read failed with error %w for orderer type %s", err, ConsensusTypeBFT) + } + // Overwrite policy manually by computing it from the consenters + ipc.EncodeBFTBlockVerificationPolicy(consenterProtos, ordererGroup) + default: + return nil, fmt.Errorf("unknown orderer type: %s", conf.OrdererType) + } + + addValue(ordererGroup, icc.ConsensusTypeValue(conf.OrdererType, consensusMetadata), icc.AdminsPolicyKey) + + for _, org := range conf.Organizations { + var err error + ordererGroup.Groups[org.Name], err = NewOrdererOrgGroup(org) + if err != nil { + return nil, fmt.Errorf("failed to create orderer org %w", err) + } + } + + ordererGroup.ModPolicy = icc.AdminsPolicyKey + return ordererGroup, nil +} + +// NewConsortiumOrgGroup returns an org component of the channel configuration. It defines the crypto material for the +// organization (its MSP). It sets the mod_policy of all elements to "Admins". +func NewConsortiumOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { + consortiumsOrgGroup := NewConfigGroup() + consortiumsOrgGroup.ModPolicy = icc.AdminsPolicyKey + + if conf.SkipAsForeign { + return consortiumsOrgGroup, nil + } + + mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) + if err != nil { + return nil, fmt.Errorf("1 - Error loading MSP configuration for org: %s %w", conf.Name, err) + } + + if err := AddPolicies(consortiumsOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to consortiums org group '%s' %w", conf.Name, err) + } + + addValue(consortiumsOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) + + return consortiumsOrgGroup, nil +} + +// NewOrdererOrgGroup returns an orderer org component of the channel configuration. It defines the crypto material for the +// organization (its MSP). It sets the mod_policy of all elements to "Admins". +func NewOrdererOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { + ordererOrgGroup := NewConfigGroup() + ordererOrgGroup.ModPolicy = icc.AdminsPolicyKey + + if conf.SkipAsForeign { + return ordererOrgGroup, nil + } + + mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) + if err != nil { + return nil, fmt.Errorf("1 - Error loading MSP configuration for org: %s %w", conf.Name, err) + } + + if err := AddPolicies(ordererOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to orderer org group '%s' %w", conf.Name, err) + } + + addValue(ordererOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) + + if len(conf.OrdererEndpoints) > 0 { + addValue(ordererOrgGroup, icc.EndpointsValue(conf.OrdererEndpoints), icc.AdminsPolicyKey) + } + + return ordererOrgGroup, nil +} + +// NewApplicationGroup returns the application component of the channel configuration. It defines the organizations which are involved +// in application logic like chaincodes, and how these members may interact with the orderer. It sets the mod_policy of all elements to "Admins". +func NewApplicationGroup(conf *genesisconfig.Application) (*cb.ConfigGroup, error) { + applicationGroup := NewConfigGroup() + if err := AddPolicies(applicationGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to application group %w ", err) + } + + if len(conf.ACLs) > 0 { + addValue(applicationGroup, icc.ACLValues(conf.ACLs), icc.AdminsPolicyKey) + } + + if len(conf.Capabilities) > 0 { + addValue(applicationGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) + } + + for _, org := range conf.Organizations { + var err error + applicationGroup.Groups[org.Name], err = NewApplicationOrgGroup(org) + if err != nil { + return nil, fmt.Errorf("failed to create application org %w", err) + } + } + + applicationGroup.ModPolicy = icc.AdminsPolicyKey + return applicationGroup, nil +} + +// NewApplicationOrgGroup returns an application org component of the channel configuration. It defines the crypto material for the organization +// (its MSP) as well as its anchor peers for use by the gossip network. It sets the mod_policy of all elements to "Admins". +func NewApplicationOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { + applicationOrgGroup := NewConfigGroup() + applicationOrgGroup.ModPolicy = icc.AdminsPolicyKey + + if conf.SkipAsForeign { + return applicationOrgGroup, nil + } + + mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) + if err != nil { + return nil, fmt.Errorf("1 - Error loading MSP configuration for org %s %w", conf.Name, err) + } + + if err := AddPolicies(applicationOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { + return nil, fmt.Errorf("error adding policies to application org group %s %w", conf.Name, err) + } + addValue(applicationOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) + + var anchorProtos []*pb.AnchorPeer + for _, anchorPeer := range conf.AnchorPeers { + anchorProtos = append(anchorProtos, &pb.AnchorPeer{ + Host: anchorPeer.Host, + Port: anchorPeer.Port, + }) + } + + // Avoid adding an unnecessary anchor peers element when one is not required. This helps + // prevent a delta from the orderer system channel when computing more complex channel + // creation transactions + if len(anchorProtos) > 0 { + addValue(applicationOrgGroup, icc.AnchorPeersValue(anchorProtos), icc.AdminsPolicyKey) + } + + return applicationOrgGroup, nil +} + +// NewConsortiumsGroup returns the consortiums component of the channel configuration. This element is only defined for the ordering system channel. +// It sets the mod_policy for all elements to "/Channel/Orderer/Admins". +func NewConsortiumsGroup(conf map[string]*genesisconfig.Consortium) (*cb.ConfigGroup, error) { + consortiumsGroup := NewConfigGroup() + // This policy is not referenced anywhere, it is only used as part of the implicit meta policy rule at the channel level, so this setting + // effectively degrades control of the ordering system channel to the ordering admins + addPolicy(consortiumsGroup, ipc.SignaturePolicy(icc.AdminsPolicyKey, ipd.AcceptAllPolicy), ordererAdminsPolicyName) + + for consortiumName, consortium := range conf { + var err error + consortiumsGroup.Groups[consortiumName], err = NewConsortiumGroup(consortium) + if err != nil { + return nil, fmt.Errorf("failed to create consortium %s %w", consortiumName, err) + } + } + + consortiumsGroup.ModPolicy = ordererAdminsPolicyName + return consortiumsGroup, nil +} + +// NewConsortiumGroup returns a consortiums component of the channel configuration. Each consortium defines the organizations which may be involved in channel +// creation, as well as the channel creation policy the orderer checks at channel creation time to authorize the action. It sets the mod_policy of all +// elements to "/Channel/Orderer/Admins". +func NewConsortiumGroup(conf *genesisconfig.Consortium) (*cb.ConfigGroup, error) { + consortiumGroup := NewConfigGroup() + + for _, org := range conf.Organizations { + var err error + consortiumGroup.Groups[org.Name], err = NewConsortiumOrgGroup(org) + if err != nil { + return nil, fmt.Errorf("failed to create consortium org %w", err) + } + } + + addValue(consortiumGroup, icc.ChannelCreationPolicyValue(ipc.ImplicitMetaAnyPolicy(icc.AdminsPolicyKey).Value()), ordererAdminsPolicyName) + + consortiumGroup.ModPolicy = ordererAdminsPolicyName + return consortiumGroup, nil +} + +// NewChannelCreateConfigUpdate generates a ConfigUpdate which can be sent to the orderer to create a new channel. Optionally, the channel group of the +// ordering system channel may be passed in, and the resulting ConfigUpdate will extract the appropriate versions from this file. +func NewChannelCreateConfigUpdate(channelID string, conf *genesisconfig.Profile, templateConfig *cb.ConfigGroup) (*cb.ConfigUpdate, error) { + if conf.Application == nil { + return nil, errors.New("cannot define a new channel with no Application section") + } + + if conf.Consortium == "" { + return nil, errors.New("cannot define a new channel with no Consortium value") + } + + newChannelGroup, err := NewChannelGroup(conf) + if err != nil { + return nil, fmt.Errorf("could not turn parse profile into channel group %w", err) + } + + updt, err := update.Compute(&cb.Config{ChannelGroup: templateConfig}, &cb.Config{ChannelGroup: newChannelGroup}) + if err != nil { + return nil, fmt.Errorf("could not compute update %w", err) + } + + // Add the consortium name to create the channel for into the write set as required. + updt.ChannelId = channelID + updt.ReadSet.Values[icc.ConsortiumKey] = &cb.ConfigValue{Version: 0} + updt.WriteSet.Values[icc.ConsortiumKey] = &cb.ConfigValue{ + Version: 0, + Value: protoutil.MarshalOrPanic(&cb.Consortium{ + Name: conf.Consortium, + }), + } + + return updt, nil +} + +// DefaultConfigTemplate generates a config template based on the assumption that +// the input profile is a channel creation template and no system channel context +// is available. +func DefaultConfigTemplate(conf *genesisconfig.Profile) (*cb.ConfigGroup, error) { + channelGroup, err := NewChannelGroup(conf) + if err != nil { + return nil, fmt.Errorf("error parsing configuration %w", err) + } + + if _, ok := channelGroup.Groups[icc.ApplicationGroupKey]; !ok { + return nil, errors.New("channel template configs must contain an application section") + } + + channelGroup.Groups[icc.ApplicationGroupKey].Values = nil + channelGroup.Groups[icc.ApplicationGroupKey].Policies = nil + + return channelGroup, nil +} + +func ConfigTemplateFromGroup(conf *genesisconfig.Profile, cg *cb.ConfigGroup) (*cb.ConfigGroup, error) { + template := proto.Clone(cg).(*cb.ConfigGroup) + if template.Groups == nil { + return nil, errors.New("supplied system channel group has no sub-groups") + } + + template.Groups[icc.ApplicationGroupKey] = &cb.ConfigGroup{ + Groups: map[string]*cb.ConfigGroup{}, + Policies: map[string]*cb.ConfigPolicy{ + icc.AdminsPolicyKey: {}, + }, + } + + consortiums, ok := template.Groups[icc.ConsortiumsGroupKey] + if !ok { + return nil, errors.New("supplied system channel group does not appear to be system channel (missing consortiums group)") + } + + if consortiums.Groups == nil { + return nil, errors.New("system channel consortiums group appears to have no consortiums defined") + } + + consortium, ok := consortiums.Groups[conf.Consortium] + if !ok { + return nil, fmt.Errorf("supplied system channel group is missing '%s' consortium", conf.Consortium) + } + + if conf.Application == nil { + return nil, errors.New("supplied channel creation profile does not contain an application section") + } + + for _, organization := range conf.Application.Organizations { + var ok bool + template.Groups[icc.ApplicationGroupKey].Groups[organization.Name], ok = consortium.Groups[organization.Name] + if !ok { + return nil, fmt.Errorf("consortium %s does not contain member org %s", conf.Consortium, organization.Name) + } + } + delete(template.Groups, icc.ConsortiumsGroupKey) + + addValue(template, icc.ConsortiumValue(conf.Consortium), icc.AdminsPolicyKey) + + return template, nil +} + +// HasSkippedForeignOrgs is used to detect whether a configuration includes +// org definitions which should not be parsed because this tool is being +// run in a context where the user does not have access to that org's info +func HasSkippedForeignOrgs(conf *genesisconfig.Profile) error { + var organizations []*genesisconfig.Organization + + if conf.Orderer != nil { + organizations = append(organizations, conf.Orderer.Organizations...) + } + + if conf.Application != nil { + organizations = append(organizations, conf.Application.Organizations...) + } + + for _, consortium := range conf.Consortiums { + organizations = append(organizations, consortium.Organizations...) + } + + for _, org := range organizations { + if org.SkipAsForeign { + return fmt.Errorf("organization '%s' is marked to be skipped as foreign", org.Name) + } + } + + return nil +} + +// Bootstrapper is a wrapper around NewChannelConfigGroup which can produce genesis blocks +type Bootstrapper struct { + channelGroup *cb.ConfigGroup +} + +// NewBootstrapper creates a bootstrapper but returns an error instead of panic-ing +func NewBootstrapper(config *genesisconfig.Profile) (*Bootstrapper, error) { + if err := HasSkippedForeignOrgs(config); err != nil { + return nil, fmt.Errorf("all org definitions must be local during bootstrapping %w", err) + } + + channelGroup, err := NewChannelGroup(config) + if err != nil { + return nil, fmt.Errorf("could not create channel group %w", err) + } + + return &Bootstrapper{ + channelGroup: channelGroup, + }, nil +} + +// New creates a new Bootstrapper for generating genesis blocks +func New(config *genesisconfig.Profile) *Bootstrapper { + bs, err := NewBootstrapper(config) + if err != nil { + panic(err) + } + return bs +} + +// GenesisBlockForChannel produces a genesis block for a given channel ID +func (bs *Bootstrapper) GenesisBlockForChannel(channelID string) *cb.Block { + return genesis.NewFactoryImpl(bs.channelGroup).Block(channelID) +} + +//nolint:gocognit +func consenterProtosFromConfig(consenterMapping []*genesisconfig.Consenter) ([]*cb.Consenter, error) { + var consenterProtos []*cb.Consenter + for _, consenter := range consenterMapping { + c := &cb.Consenter{ + Id: consenter.ID, + Host: consenter.Host, + Port: consenter.Port, + MspId: consenter.MSPID, + } + // Expect the user to set the config value for client/server certs or identity to the + // path where they are persisted locally, then load these files to memory. + if consenter.ClientTLSCert != "" { + clientCert, err := os.ReadFile(consenter.ClientTLSCert) + if err != nil { + return nil, fmt.Errorf("cannot load client cert for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) + } + c.ClientTlsCert = clientCert + } + + if consenter.ServerTLSCert != "" { + serverCert, err := os.ReadFile(consenter.ServerTLSCert) + if err != nil { + return nil, fmt.Errorf("cannot load server cert for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) + } + c.ServerTlsCert = serverCert + } + + if consenter.Identity != "" { + identity, err := os.ReadFile(consenter.Identity) + if err != nil { + return nil, fmt.Errorf("cannot load identity for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) + } + c.Identity = identity + } + + consenterProtos = append(consenterProtos, c) + } + return consenterProtos, nil +} diff --git a/internal/configtxgen/genesisconfig/config.go b/internal/configtxgen/genesisconfig/config.go new file mode 100644 index 0000000..e436693 --- /dev/null +++ b/internal/configtxgen/genesisconfig/config.go @@ -0,0 +1,517 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package genesisconfig + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "path/filepath" + "sync" + "time" + + "github.com/hyperledger/fabric-admin-sdk/internal/configtxgen/viperutil" + "github.com/hyperledger/fabric-admin-sdk/internal/msp" + "github.com/hyperledger/fabric-protos-go-apiv2/orderer/smartbft" + + "github.com/hyperledger/fabric-protos-go-apiv2/orderer/etcdraft" +) + +const ( + // The type key for etcd based RAFT consensus. + EtcdRaft = "etcdraft" + BFT = "BFT" +) + +const ( + // SampleInsecureSoloProfile references the sample profile which does not + // include any MSPs and uses solo for ordering. + SampleInsecureSoloProfile = "SampleInsecureSolo" + // SampleDevModeSoloProfile references the sample profile which requires + // only basic membership for admin privileges and uses solo for ordering. + SampleDevModeSoloProfile = "SampleDevModeSolo" + // SampleSingleMSPSoloProfile references the sample profile which includes + // only the sample MSP and uses solo for ordering. + SampleSingleMSPSoloProfile = "SampleSingleMSPSolo" + + // SampleInsecureKafkaProfile references the sample profile which does not + // include any MSPs and uses Kafka for ordering. + SampleInsecureKafkaProfile = "SampleInsecureKafka" + // SampleDevModeKafkaProfile references the sample profile which requires only + // basic membership for admin privileges and uses Kafka for ordering. + SampleDevModeKafkaProfile = "SampleDevModeKafka" + // SampleSingleMSPKafkaProfile references the sample profile which includes + // only the sample MSP and uses Kafka for ordering. + SampleSingleMSPKafkaProfile = "SampleSingleMSPKafka" + + // SampleDevModeEtcdRaftProfile references the sample profile used for testing + // the etcd/raft-based ordering service. + SampleDevModeEtcdRaftProfile = "SampleDevModeEtcdRaft" + + // SampleAppChannelInsecureSoloProfile references the sample profile which + // does not include any MSPs and uses solo for ordering. + SampleAppChannelInsecureSoloProfile = "SampleAppChannelInsecureSolo" + // SampleApppChannelEtcdRaftProfile references the sample profile used for + // testing the etcd/raft-based ordering service using the channel + // participation API. + SampleAppChannelEtcdRaftProfile = "SampleAppChannelEtcdRaft" + + // SampleSingleMSPChannelProfile references the sample profile which + // includes only the sample MSP and is used to create a channel + SampleSingleMSPChannelProfile = "SampleSingleMSPChannel" + + // SampleConsortiumName is the sample consortium from the + // sample configtx.yaml + SampleConsortiumName = "SampleConsortium" + // SampleOrgName is the name of the sample org in the sample profiles + SampleOrgName = "SampleOrg" + + // AdminRoleAdminPrincipal is set as AdminRole to cause the MSP role of + // type Admin to be used as the admin principal default + AdminRoleAdminPrincipal = "Role.ADMIN" +) + +// TopLevel consists of the structs used by the configtxgen tool. +type TopLevel struct { + Profiles map[string]*Profile `yaml:"Profiles"` + Organizations []*Organization `yaml:"Organizations"` + Channel *Profile `yaml:"Channel"` + Application *Application `yaml:"Application"` + Orderer *Orderer `yaml:"Orderer"` + Capabilities map[string]map[string]bool `yaml:"Capabilities"` +} + +// Profile encodes orderer/application configuration combinations for the +// configtxgen tool. +type Profile struct { + Consortium string `yaml:"Consortium"` + Application *Application `yaml:"Application"` + Orderer *Orderer `yaml:"Orderer"` + Consortiums map[string]*Consortium `yaml:"Consortiums"` + Capabilities map[string]bool `yaml:"Capabilities"` + Policies map[string]*Policy `yaml:"Policies"` +} + +// Policy encodes a channel config policy +type Policy struct { + Type string `yaml:"Type"` + Rule string `yaml:"Rule"` +} + +// Consortium represents a group of organizations which may create channels +// with each other +type Consortium struct { + Organizations []*Organization `yaml:"Organizations"` +} + +// Application encodes the application-level configuration needed in config +// transactions. +type Application struct { + Organizations []*Organization `yaml:"Organizations"` + Capabilities map[string]bool `yaml:"Capabilities"` + Policies map[string]*Policy `yaml:"Policies"` + ACLs map[string]string `yaml:"ACLs"` +} + +// Organization encodes the organization-level configuration needed in +// config transactions. +type Organization struct { + Name string `yaml:"Name"` + ID string `yaml:"ID"` + MSPDir string `yaml:"MSPDir"` + MSPType string `yaml:"MSPType"` + Policies map[string]*Policy `yaml:"Policies"` + + // Note: Viper deserialization does not seem to care for + // embedding of types, so we use one organization struct + // for both orderers and applications. + AnchorPeers []*AnchorPeer `yaml:"AnchorPeers"` + OrdererEndpoints []string `yaml:"OrdererEndpoints"` + + // AdminPrincipal is deprecated and may be removed in a future release + // it was used for modifying the default policy generation, but policies + // may now be specified explicitly so it is redundant and unnecessary + AdminPrincipal string `yaml:"AdminPrincipal"` + + // SkipAsForeign indicates that this org definition is actually unknown to this + // instance of the tool, so, parsing of this org's parameters should be ignored. + SkipAsForeign bool +} + +// AnchorPeer encodes the necessary fields to identify an anchor peer. +type AnchorPeer struct { + Host string `yaml:"Host"` + Port int32 `yaml:"Port"` +} + +// Orderer contains configuration associated to a channel. +type Orderer struct { + OrdererType string `yaml:"OrdererType"` + Addresses []string `yaml:"Addresses"` + BatchTimeout time.Duration `yaml:"BatchTimeout"` + BatchSize BatchSize `yaml:"BatchSize"` + Kafka Kafka `yaml:"Kafka"` + ConsenterMapping []*Consenter `yaml:"ConsenterMapping"` + EtcdRaft *etcdraft.ConfigMetadata `yaml:"EtcdRaft"` + SmartBFT *smartbft.Options `yaml:"SmartBFT"` + Organizations []*Organization `yaml:"Organizations"` + MaxChannels uint64 `yaml:"MaxChannels"` + Capabilities map[string]bool `yaml:"Capabilities"` + Policies map[string]*Policy `yaml:"Policies"` +} + +type Consenter struct { + ID uint32 `yaml:"ID"` + Host string `yaml:"Host"` + Port uint32 `yaml:"Port"` + MSPID string `yaml:"MSPID"` + Identity string `yaml:"Identity"` + ClientTLSCert string `yaml:"ClientTLSCert"` + ServerTLSCert string `yaml:"ServerTLSCert"` +} + +// BatchSize contains configuration affecting the size of batches. +type BatchSize struct { + MaxMessageCount uint32 `yaml:"MaxMessageCount"` + AbsoluteMaxBytes uint32 `yaml:"AbsoluteMaxBytes"` + PreferredMaxBytes uint32 `yaml:"PreferredMaxBytes"` +} + +// Kafka contains configuration for the Kafka-based orderer. +type Kafka struct { + Brokers []string `yaml:"Brokers"` +} + +var genesisDefaults = TopLevel{ + Orderer: &Orderer{ + OrdererType: "solo", + BatchTimeout: 2 * time.Second, + BatchSize: BatchSize{ + MaxMessageCount: 500, + AbsoluteMaxBytes: 10 * 1024 * 1024, + PreferredMaxBytes: 2 * 1024 * 1024, + }, + Kafka: Kafka{ + Brokers: []string{"127.0.0.1:9092"}, + }, + EtcdRaft: &etcdraft.ConfigMetadata{ + Options: &etcdraft.Options{ + TickInterval: "500ms", + ElectionTick: 10, + HeartbeatTick: 1, + MaxInflightBlocks: 5, + SnapshotIntervalSize: 16 * 1024 * 1024, // 16 MB + }, + }, + }, +} + +// LoadTopLevel simply loads the configtx.yaml file into the structs above and +// completes their initialization. Config paths may optionally be provided and +// will be used in place of the FABRIC_CFG_PATH env variable. +// +// Note, for environment overrides to work properly within a profile, Load +// should be used instead. +func LoadTopLevel(configPaths ...string) *TopLevel { + config := viperutil.New() + config.AddConfigPaths(configPaths...) + config.SetConfigName("configtx") + + err := config.ReadInConfig() + if err != nil { + panic(fmt.Errorf("error reading configuration: %w", err)) + } + log.Printf("Using config file: %s", config.ConfigFileUsed()) + + uconf, err := cache.load(config, config.ConfigFileUsed()) + if err != nil { + panic(fmt.Errorf("error reading configuration: %w", err)) + } + uconf.completeInitialization(filepath.Dir(config.ConfigFileUsed())) + fmt.Printf("Loaded configuration: %s", config.ConfigFileUsed()) + + return uconf +} + +// Load returns the orderer/application config combination that corresponds to +// a given profile. Config paths may optionally be provided and will be used +// in place of the FABRIC_CFG_PATH env variable. +func Load(profile string, configPaths ...string) (*Profile, error) { + config := viperutil.New() + config.AddConfigPaths(configPaths...) + config.SetConfigName("configtx") + + fmt.Println(configPaths) + err := config.ReadInConfig() + if err != nil { + return nil, errors.New("Error reading configuration: " + err.Error()) + } + log.Printf("Using config file: %s", config.ConfigFileUsed()) + + uconf, err := cache.load(config, config.ConfigFileUsed()) + if err != nil { + panic(fmt.Errorf("error loading config from config cache: %w", err)) + } + + result, ok := uconf.Profiles[profile] + if !ok { + panic(fmt.Errorf("could not find profile: %s", profile)) + } + + result.completeInitialization(filepath.Dir(config.ConfigFileUsed())) + + log.Printf("Loaded configuration: %s", config.ConfigFileUsed()) + + return result, nil +} + +func (t *TopLevel) completeInitialization(configDir string) { + for _, org := range t.Organizations { + org.completeInitialization(configDir) + } + + if t.Orderer != nil { + t.Orderer.completeInitialization(configDir) + } +} + +func (p *Profile) completeInitialization(configDir string) { + if p.Application != nil { + for _, org := range p.Application.Organizations { + org.completeInitialization(configDir) + } + } + + if p.Consortiums != nil { + for _, consortium := range p.Consortiums { + for _, org := range consortium.Organizations { + org.completeInitialization(configDir) + } + } + } + + if p.Orderer != nil { + for _, org := range p.Orderer.Organizations { + org.completeInitialization(configDir) + } + // Some profiles will not define orderer parameters + p.Orderer.completeInitialization(configDir) + } +} + +func (org *Organization) completeInitialization(configDir string) { + // set the MSP type; if none is specified we assume BCCSP + if org.MSPType == "" { + org.MSPType = msp.ProviderTypeToString(msp.FABRIC) + } + + if org.AdminPrincipal == "" { + org.AdminPrincipal = AdminRoleAdminPrincipal + } + translatePaths(configDir, org) +} + +//nolint:cyclop,gocognit +func (ord *Orderer) completeInitialization(configDir string) { +loop: + for { + switch { + case ord.OrdererType == "": + log.Printf("Orderer.OrdererType unset, setting to %v", genesisDefaults.Orderer.OrdererType) + ord.OrdererType = genesisDefaults.Orderer.OrdererType + case ord.BatchTimeout == 0: + log.Printf("Orderer.BatchTimeout unset, setting to %s", genesisDefaults.Orderer.BatchTimeout) + ord.BatchTimeout = genesisDefaults.Orderer.BatchTimeout + case ord.BatchSize.MaxMessageCount == 0: + log.Printf("Orderer.BatchSize.MaxMessageCount unset, setting to %v", genesisDefaults.Orderer.BatchSize.MaxMessageCount) + ord.BatchSize.MaxMessageCount = genesisDefaults.Orderer.BatchSize.MaxMessageCount + case ord.BatchSize.AbsoluteMaxBytes == 0: + log.Printf("Orderer.BatchSize.AbsoluteMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes) + ord.BatchSize.AbsoluteMaxBytes = genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes + case ord.BatchSize.PreferredMaxBytes == 0: + log.Printf("Orderer.BatchSize.PreferredMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.PreferredMaxBytes) + ord.BatchSize.PreferredMaxBytes = genesisDefaults.Orderer.BatchSize.PreferredMaxBytes + default: + break loop + } + } + + log.Printf("orderer type: %s", ord.OrdererType) + // Additional, consensus type-dependent initialization goes here + // Also using this to panic on unknown orderer type. + switch ord.OrdererType { + case "solo": + // nothing to be done here + case "kafka": + if ord.Kafka.Brokers == nil { + log.Printf("Orderer.Kafka unset, setting to %v", genesisDefaults.Orderer.Kafka.Brokers) + ord.Kafka.Brokers = genesisDefaults.Orderer.Kafka.Brokers + } + case EtcdRaft: + if ord.EtcdRaft == nil { + log.Panicf("%s configuration missing", EtcdRaft) + } + if ord.EtcdRaft.Options == nil { + log.Printf("Orderer.EtcdRaft.Options unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options) + ord.EtcdRaft.Options = genesisDefaults.Orderer.EtcdRaft.Options + } + second_loop: + for { + switch { + case ord.EtcdRaft.Options.TickInterval == "": + log.Printf("Orderer.EtcdRaft.Options.TickInterval unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.TickInterval) + ord.EtcdRaft.Options.TickInterval = genesisDefaults.Orderer.EtcdRaft.Options.TickInterval + + case ord.EtcdRaft.Options.ElectionTick == 0: + log.Printf("Orderer.EtcdRaft.Options.ElectionTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick) + ord.EtcdRaft.Options.ElectionTick = genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick + + case ord.EtcdRaft.Options.HeartbeatTick == 0: + log.Printf("Orderer.EtcdRaft.Options.HeartbeatTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick) + ord.EtcdRaft.Options.HeartbeatTick = genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick + + case ord.EtcdRaft.Options.MaxInflightBlocks == 0: + log.Printf("Orderer.EtcdRaft.Options.MaxInflightBlocks unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks) + ord.EtcdRaft.Options.MaxInflightBlocks = genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks + + case ord.EtcdRaft.Options.SnapshotIntervalSize == 0: + log.Printf("Orderer.EtcdRaft.Options.SnapshotIntervalSize unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize) + ord.EtcdRaft.Options.SnapshotIntervalSize = genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize + + case len(ord.EtcdRaft.Consenters) == 0: + log.Panicf("%s configuration did not specify any consenter", EtcdRaft) + + default: + break second_loop + } + } + + if _, err := time.ParseDuration(ord.EtcdRaft.Options.TickInterval); err != nil { + log.Panicf("Etcdraft TickInterval (%s) must be in time duration format", ord.EtcdRaft.Options.TickInterval) + } + + // validate the specified members for Options + if ord.EtcdRaft.Options.ElectionTick <= ord.EtcdRaft.Options.HeartbeatTick { + log.Panicf("election tick must be greater than heartbeat tick") + } + + for _, c := range ord.EtcdRaft.GetConsenters() { + if c.Host == "" { + log.Panicf("consenter info in %s configuration did not specify host", EtcdRaft) + } + if c.Port == 0 { + log.Panicf("consenter info in %s configuration did not specify port", EtcdRaft) + } + if c.ClientTlsCert == nil { + log.Panicf("consenter info in %s configuration did not specify client TLS cert", EtcdRaft) + } + if c.ServerTlsCert == nil { + log.Panicf("consenter info in %s configuration did not specify server TLS cert", EtcdRaft) + } + clientCertPath := string(c.GetClientTlsCert()) + TranslatePathInPlace(configDir, &clientCertPath) + c.ClientTlsCert = []byte(clientCertPath) + serverCertPath := string(c.GetServerTlsCert()) + TranslatePathInPlace(configDir, &serverCertPath) + c.ServerTlsCert = []byte(serverCertPath) + } + case BFT: + if ord.SmartBFT == nil { + log.Printf("Orderer.SmartBFT.Options unset, setting to %v", genesisDefaults.Orderer.SmartBFT) + ord.SmartBFT = genesisDefaults.Orderer.SmartBFT + } + + if len(ord.ConsenterMapping) == 0 { + log.Panicf("%s configuration did not specify any consenter", BFT) + } + + for _, c := range ord.ConsenterMapping { + if c.Host == "" { + log.Panicf("consenter info in %s configuration did not specify host", BFT) + } + if c.Port == 0 { + log.Panicf("consenter info in %s configuration did not specify port", BFT) + } + if c.ClientTLSCert == "" { + log.Panicf("consenter info in %s configuration did not specify client TLS cert", BFT) + } + if c.ServerTLSCert == "" { + log.Panicf("consenter info in %s configuration did not specify server TLS cert", BFT) + } + if len(c.MSPID) == 0 { + log.Panicf("consenter info in %s configuration did not specify MSP ID", BFT) + } + if len(c.Identity) == 0 { + log.Panicf("consenter info in %s configuration did not specify identity certificate", BFT) + } + + c.ClientTLSCert = TranslatePath(configDir, c.ClientTLSCert) + c.ServerTLSCert = TranslatePath(configDir, c.ServerTLSCert) + c.Identity = TranslatePath(configDir, c.Identity) + } + default: + log.Panicf("unknown orderer type: %s", ord.OrdererType) + } +} + +func TranslatePathInPlace(base string, p *string) { + *p = TranslatePath(base, *p) +} + +func TranslatePath(base, p string) string { + if filepath.IsAbs(p) { + return p + } + + return filepath.Join(base, p) +} + +func translatePaths(configDir string, org *Organization) { + TranslatePathInPlace(configDir, &org.MSPDir) +} + +// configCache stores marshalled bytes of config structures that produced from +// EnhancedExactUnmarshal. Cache key is the path of the configuration file that was used. +type configCache struct { + mutex sync.Mutex + cache map[string][]byte +} + +var cache = &configCache{ + cache: make(map[string][]byte), +} + +// load loads the TopLevel config structure from configCache. +// if not successful, it unmarshal a config file, and populate configCache +// with marshaled TopLevel struct. +func (c *configCache) load(config *viperutil.ConfigParser, configPath string) (*TopLevel, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + conf := &TopLevel{} + serializedConf, ok := c.cache[configPath] + log.Printf("Loading configuration from cache: %t", ok) + if !ok { + err := config.EnhancedExactUnmarshal(conf) + if err != nil { + return nil, fmt.Errorf("error unmarshalling config into struct: %w", err) + } + + serializedConf, err = json.Marshal(conf) + if err != nil { + return nil, err + } + c.cache[configPath] = serializedConf + } + + err := json.Unmarshal(serializedConf, conf) + if err != nil { + return nil, err + } + return conf, nil +} diff --git a/internal/configtxgen/metadata/metadata.go b/internal/configtxgen/metadata/metadata.go new file mode 100644 index 0000000..c2f9431 --- /dev/null +++ b/internal/configtxgen/metadata/metadata.go @@ -0,0 +1,25 @@ +/* +Copyright 2017 Hitachi America + +SPDX-License-Identifier: Apache-2.0 +*/ + +package metadata + +import ( + "fmt" + "runtime" +) + +const ProgramName = "configtxgen" + +var ( + CommitSHA = "development build" + Version = "latest" +) + +func GetVersionInfo() string { + return fmt.Sprintf("%s:\n Version: %s\n Commit SHA: %s\n Go version: %s\n OS/Arch: %s", + ProgramName, Version, CommitSHA, runtime.Version(), + fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)) +} diff --git a/internal/configtxgen/viperutil/config_util.go b/internal/configtxgen/viperutil/config_util.go new file mode 100644 index 0000000..c5d37c1 --- /dev/null +++ b/internal/configtxgen/viperutil/config_util.go @@ -0,0 +1,505 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package viperutil + +import ( + "encoding/pem" + "fmt" + "io" + "log" + "math" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/IBM/sarama" + version "github.com/hashicorp/go-version" + "github.com/mitchellh/mapstructure" + "gopkg.in/yaml.v2" +) + +// ConfigPaths returns the paths from environment and +// defaults which are CWD and /etc/hyperledger/fabric. +func ConfigPaths() []string { + var paths []string + if p := os.Getenv("FABRIC_CFG_PATH"); p != "" { + paths = append(paths, p) + } + return append(paths, ".", "/etc/hyperledger/fabric") +} + +// ConfigParser holds the configuration file locations. +// It keeps the config file directory locations and env variables. +// From the file the config is unmarshalled and stored. +// Currently "yaml" is supported. +type ConfigParser struct { + // configuration file to process + configPaths []string + configName string + configFile string + + // parsed config + config map[string]interface{} +} + +// New creates a ConfigParser instance +func New() *ConfigParser { + return &ConfigParser{ + config: map[string]interface{}{}, + } +} + +// AddConfigPaths keeps a list of path to search the relevant +// config file. Multiple paths can be provided. +func (c *ConfigParser) AddConfigPaths(cfgPaths ...string) { + c.configPaths = append(c.configPaths, cfgPaths...) +} + +// SetConfigName provides the configuration file name stem. The upper-cased +// version of this value also serves as the environment variable override +// prefix. +func (c *ConfigParser) SetConfigName(in string) { + c.configName = in +} + +// ConfigFileUsed returns the used configFile. +func (c *ConfigParser) ConfigFileUsed() string { + return c.configFile +} + +// Search for the existence of filename for all supported extensions +func (c *ConfigParser) searchInPath(in string) (filename string) { + supportedExts := []string{"yaml", "yml"} + for _, ext := range supportedExts { + fullPath := filepath.Join(in, c.configName+"."+ext) + _, err := os.Stat(fullPath) + if err == nil { + return fullPath + } + } + return "" +} + +// Search for the configName in all configPaths +func (c *ConfigParser) findConfigFile() string { + paths := c.configPaths + if len(paths) == 0 { + paths = ConfigPaths() + } + for _, cp := range paths { + file := c.searchInPath(cp) + if file != "" { + return file + } + } + return "" +} + +// Get the valid and present config file +func (c *ConfigParser) getConfigFile() string { + // if explicitly set, then use it + if c.configFile != "" { + return c.configFile + } + + c.configFile = c.findConfigFile() + return c.configFile +} + +// ReadInConfig reads and unmarshals the config file. +func (c *ConfigParser) ReadInConfig() error { + cf := c.getConfigFile() + log.Printf("Attempting to open the config file: %s", cf) + file, err := os.Open(cf) + if err != nil { + log.Printf("Unable to open the config file: %s", cf) + return err + } + defer file.Close() + + return c.ReadConfig(file) +} + +// ReadConfig parses the buffer and initializes the config. +func (c *ConfigParser) ReadConfig(in io.Reader) error { + return yaml.NewDecoder(in).Decode(c.config) +} + +// Get value for the key by searching environment variables. +func (c *ConfigParser) getFromEnv(key string) string { + envKey := key + if c.configName != "" { + envKey = c.configName + "_" + envKey + } + envKey = strings.ToUpper(envKey) + envKey = strings.ReplaceAll(envKey, ".", "_") + return os.Getenv(envKey) +} + +// Prototype declaration for getFromEnv function. +type envGetter func(key string) string + +//nolint:cyclop,gocognit +func getKeysRecursively(base string, getenv envGetter, nodeKeys map[string]interface{}, oType reflect.Type) map[string]interface{} { + subTypes := map[string]reflect.Type{} + + if oType != nil && oType.Kind() == reflect.Struct { + outer: + for i := 0; i < oType.NumField(); i++ { + fieldName := oType.Field(i).Name + fieldType := oType.Field(i).Type + + for key := range nodeKeys { + if strings.EqualFold(fieldName, key) { + subTypes[key] = fieldType + continue outer + } + } + + subTypes[fieldName] = fieldType + nodeKeys[fieldName] = nil + } + } + + result := make(map[string]interface{}) + for key, val := range nodeKeys { + fqKey := base + key + + // overwrite val, if an environment is available + if override := getenv(fqKey); override != "" { + val = override + } + + switch val := val.(type) { + case map[string]interface{}: + log.Printf("Found map[string]interface{} value for %s", fqKey) + result[key] = getKeysRecursively(fqKey+".", getenv, val, subTypes[key]) + + case map[interface{}]interface{}: + log.Printf("Found map[interface{}]interface{} value for %s", fqKey) + result[key] = getKeysRecursively(fqKey+".", getenv, toMapStringInterface(val), subTypes[key]) + + case nil: + if override := getenv(fqKey + ".File"); override != "" { + result[key] = map[string]interface{}{"File": override} + } + + default: + result[key] = val + } + } + return result +} + +func toMapStringInterface(m map[interface{}]interface{}) map[string]interface{} { + result := map[string]interface{}{} + for k, v := range m { + k, ok := k.(string) + if !ok { + panic(fmt.Sprintf("Non string %v, %v: key-entry: %v", k, v, k)) + } + result[k] = v + } + return result +} + +// customDecodeHook parses strings of the format "[thing1, thing2, thing3]" +// into string slices. Note that whitespace around slice elements is removed. +func customDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + + raw := data.(string) + l := len(raw) + if l > 1 && raw[0] == '[' && raw[l-1] == ']' { + slice := strings.Split(raw[1:l-1], ",") + for i, v := range slice { + slice[i] = strings.TrimSpace(v) + } + return slice, nil + } + + return data, nil +} + +func byteSizeDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { + if f != reflect.String || t != reflect.Uint32 { + return data, nil + } + raw := data.(string) + if raw == "" { + return data, nil + } + re := regexp.MustCompile(`^(?P[0-9]+)\s*(?i)(?P(k|m|g))b?$`) + if re.MatchString(raw) { + size, err := strconv.ParseUint(re.ReplaceAllString(raw, "${size}"), 0, 64) + if err != nil { + return data, nil + } + unit := re.ReplaceAllString(raw, "${unit}") + switch strings.ToLower(unit) { + case "g": + size = size << 10 + fallthrough + case "m": + size = size << 10 + fallthrough + case "k": + size = size << 10 + } + if size > math.MaxUint32 { + return size, fmt.Errorf("value '%s' overflows uint32", raw) + } + return size, nil + } + return data, nil +} + +func stringFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { + // "to" type should be string + if t != reflect.String { + return data, nil + } + // "from" type should be map + if f != reflect.Map { + return data, nil + } + v := reflect.ValueOf(data) + switch v.Kind() { + case reflect.String: + return data, nil + case reflect.Map: + d := data.(map[string]interface{}) + fileName, ok := d["File"] + if !ok { + fileName, ok = d["file"] + } + switch { + case ok && fileName != nil: + bytes, err := os.ReadFile(fileName.(string)) + if err != nil { + return data, err + } + return string(bytes), nil + case ok: + // fileName was nil + return nil, fmt.Errorf("value of File: was nil") + } + } + return data, nil +} + +//nolint:cyclop,gocognit +func pemBlocksFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { + // "to" type should be string + if t != reflect.Slice { + return data, nil + } + // "from" type should be map + if f != reflect.Map { + return data, nil + } + v := reflect.ValueOf(data) + switch v.Kind() { + case reflect.String: + return data, nil + case reflect.Map: + var fileName string + var ok bool + switch d := data.(type) { + case map[string]string: + fileName, ok = d["File"] + if !ok { + fileName, ok = d["file"] + } + case map[string]interface{}: + var fileI interface{} + fileI, ok = d["File"] + if !ok { + fileI = d["file"] + } + fileName, ok = fileI.(string) + } + + switch { + case ok && fileName != "": + var result []string + bytes, err := os.ReadFile(fileName) + if err != nil { + return data, err + } + for len(bytes) > 0 { + var block *pem.Block + block, bytes = pem.Decode(bytes) + if block == nil { + break + } + if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { + continue + } + result = append(result, string(pem.EncodeToMemory(block))) + } + return result, nil + case ok: + // fileName was nil + return nil, fmt.Errorf("value of File: was nil") + } + } + return data, nil +} + +var kafkaVersionConstraints map[sarama.KafkaVersion]version.Constraints + +func init() { + kafkaVersionConstraints = make(map[sarama.KafkaVersion]version.Constraints) + kafkaVersionConstraints[sarama.V0_8_2_0], _ = version.NewConstraint(">=0.8.2,<0.8.2.1") + kafkaVersionConstraints[sarama.V0_8_2_1], _ = version.NewConstraint(">=0.8.2.1,<0.8.2.2") + kafkaVersionConstraints[sarama.V0_8_2_2], _ = version.NewConstraint(">=0.8.2.2,<0.9.0.0") + kafkaVersionConstraints[sarama.V0_9_0_0], _ = version.NewConstraint(">=0.9.0.0,<0.9.0.1") + kafkaVersionConstraints[sarama.V0_9_0_1], _ = version.NewConstraint(">=0.9.0.1,<0.10.0.0") + kafkaVersionConstraints[sarama.V0_10_0_0], _ = version.NewConstraint(">=0.10.0.0,<0.10.0.1") + kafkaVersionConstraints[sarama.V0_10_0_1], _ = version.NewConstraint(">=0.10.0.1,<0.10.1.0") + kafkaVersionConstraints[sarama.V0_10_1_0], _ = version.NewConstraint(">=0.10.1.0,<0.10.2.0") + kafkaVersionConstraints[sarama.V0_10_2_0], _ = version.NewConstraint(">=0.10.2.0,<0.11.0.0") + kafkaVersionConstraints[sarama.V0_11_0_0], _ = version.NewConstraint(">=0.11.0.0,<1.0.0") + kafkaVersionConstraints[sarama.V1_0_0_0], _ = version.NewConstraint(">=1.0.0") +} + +func kafkaVersionDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t != reflect.TypeOf(sarama.KafkaVersion{}) { + return data, nil + } + + v, err := version.NewVersion(data.(string)) + if err != nil { + return nil, fmt.Errorf("unable to parse Kafka version: %w", err) + } + + for kafkaVersion, constraints := range kafkaVersionConstraints { + if constraints.Check(v) { + return kafkaVersion, nil + } + } + + return nil, fmt.Errorf("unsupported Kafka version: '%s'", data) +} + +// FactoryOpts holds configuration information used to initialize factory implementations +type FactoryOpts struct { + ProviderName string `mapstructure:"default" json:"default" yaml:"Default"` + SwOpts *SwOpts `mapstructure:"SW,omitempty" json:"SW,omitempty" yaml:"SwOpts"` +} + +// SwOpts contains options for the SWFactory +type SwOpts struct { + // Default algorithms when not specified (Deprecated?) + SecLevel int `mapstructure:"security" json:"security" yaml:"Security"` + HashFamily string `mapstructure:"hash" json:"hash" yaml:"Hash"` + + // Keystore Options + Ephemeral bool `mapstructure:"tempkeys,omitempty" json:"tempkeys,omitempty"` + FileKeystore *FileKeystoreOpts `mapstructure:"filekeystore,omitempty" json:"filekeystore,omitempty" yaml:"FileKeyStore"` + DummyKeystore *DummyKeystoreOpts `mapstructure:"dummykeystore,omitempty" json:"dummykeystore,omitempty"` + InmemKeystore *InmemKeystoreOpts `mapstructure:"inmemkeystore,omitempty" json:"inmemkeystore,omitempty"` +} + +type FileKeystoreOpts struct { + KeyStorePath string `mapstructure:"keystore" yaml:"KeyStore"` +} + +type DummyKeystoreOpts struct{} + +// InmemKeystoreOpts - empty, as there is no config for the in-memory keystore +type InmemKeystoreOpts struct{} + +// GetDefaultOpts offers a default implementation for Opts +// returns a new instance every time +func GetDefaultOpts() *FactoryOpts { + return &FactoryOpts{ + ProviderName: "SW", + SwOpts: &SwOpts{ + HashFamily: "SHA2", + SecLevel: 256, + Ephemeral: true, + }, + } +} + +func bccspHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if t != reflect.TypeOf(&FactoryOpts{}) { + return data, nil + } + + config := GetDefaultOpts() + + err := mapstructure.WeakDecode(data, config) + if err != nil { + return nil, fmt.Errorf("could not decode bccsp type %w", err) + } + + return config, nil +} + +// EnhancedExactUnmarshal is intended to unmarshal a config file into a structure +// producing error when extraneous variables are introduced and supporting +// the time.Duration type +func (c *ConfigParser) EnhancedExactUnmarshal(output interface{}) error { + oType := reflect.TypeOf(output) + if oType.Kind() != reflect.Ptr { + return fmt.Errorf("supplied output argument must be a pointer to a struct but is not pointer") + } + eType := oType.Elem() + if eType.Kind() != reflect.Struct { + return fmt.Errorf("supplied output argument must be a pointer to a struct, but it is pointer to something else") + } + + baseKeys := c.config + leafKeys := getKeysRecursively("", c.getFromEnv, baseKeys, eType) + + log.Printf("%+v", leafKeys) + config := &mapstructure.DecoderConfig{ + ErrorUnused: true, + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + bccspHook, + mapstructure.StringToTimeDurationHookFunc(), + customDecodeHook, + byteSizeDecodeHook, + stringFromFileDecodeHook, + pemBlocksFromFileDecodeHook, + kafkaVersionDecodeHook, + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + return decoder.Decode(leafKeys) +} + +// YamlStringToStructHook is a hook for viper(viper.Unmarshal(*,*, here)), it is able to parse a string of minified yaml into a slice of structs +func YamlStringToStructHook(m interface{}) func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { + return func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { + if rf != reflect.String || rt != reflect.Slice { + return data, nil + } + + raw := data.(string) + if raw == "" { + return m, nil + } + + return m, yaml.UnmarshalStrict([]byte(raw), &m) + } +} diff --git a/internal/protoutil/protoutil.go b/internal/protoutil/protoutil.go new file mode 100644 index 0000000..a271cc1 --- /dev/null +++ b/internal/protoutil/protoutil.go @@ -0,0 +1,114 @@ +package protoutil + +import ( + "bytes" + "errors" + "fmt" + + "github.com/hyperledger/fabric-gateway/pkg/identity" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" +) + +// CreateSignedTx assembles an Envelope message from proposal, endorsements, +// and a signer. This function should be called by a client when it has +// collected enough endorsements for a proposal to create a transaction and +// submit it to peers for ordering +func CreateSignedTx( + proposal *peer.Proposal, + signer identity.Sign, + resps ...*peer.ProposalResponse, +) (*common.Envelope, error) { + if err := ensureValidResponses(resps); err != nil { + return nil, err + } + + // the original header + hdr, err := UnmarshalHeader(proposal.Header) + if err != nil { + return nil, err + } + + // the original payload + pPayl, err := UnmarshalChaincodeProposalPayload(proposal.Payload) + if err != nil { + return nil, err + } + + endorsements := fillEndorsements(resps) + + // create ChaincodeEndorsedAction + cea := &peer.ChaincodeEndorsedAction{ProposalResponsePayload: resps[0].Payload, Endorsements: endorsements} + + // obtain the bytes of the proposal payload that will go to the transaction + propPayloadBytes, err := GetBytesProposalPayloadForTx(pPayl) + if err != nil { + return nil, err + } + + // serialize the chaincode action payload + cap := &peer.ChaincodeActionPayload{ChaincodeProposalPayload: propPayloadBytes, Action: cea} + capBytes, err := GetBytesChaincodeActionPayload(cap) + if err != nil { + return nil, err + } + + // create a transaction + taa := &peer.TransactionAction{Header: hdr.SignatureHeader, Payload: capBytes} + taas := make([]*peer.TransactionAction, 1) + taas[0] = taa + tx := &peer.Transaction{Actions: taas} + + // serialize the tx + txBytes, err := GetBytesTransaction(tx) + if err != nil { + return nil, err + } + + // create the payload + payl := &common.Payload{Header: hdr, Data: txBytes} + paylBytes, err := GetBytesPayload(payl) + if err != nil { + return nil, err + } + + // sign the payload + sig, err := signer(paylBytes) + if err != nil { + return nil, err + } + + // here's the envelope + return &common.Envelope{Payload: paylBytes, Signature: sig}, nil +} + +// ensureValidResponses checks that all actions are bitwise equal and that they are successful. +func ensureValidResponses(responses []*peer.ProposalResponse) error { + if len(responses) == 0 { + return errors.New("at least one proposal response is required") + } + + var firstResponse []byte + for n, r := range responses { + if r.Response.Status < 200 || r.Response.Status >= 400 { + return fmt.Errorf("proposal response was not successful, error code %d, msg %s", r.Response.Status, r.Response.Message) + } + + if n == 0 { + firstResponse = r.Payload + } else if !bytes.Equal(firstResponse, r.Payload) { + return errors.New("ProposalResponsePayloads do not match") + } + } + + return nil +} + +func fillEndorsements(responses []*peer.ProposalResponse) []*peer.Endorsement { + endorsements := make([]*peer.Endorsement, len(responses)) + for n, r := range responses { + endorsements[n] = r.Endorsement + } + return endorsements +} diff --git a/internal/protoutil/txtutils.go b/internal/protoutil/txtutils.go new file mode 100644 index 0000000..cb8a928 --- /dev/null +++ b/internal/protoutil/txtutils.go @@ -0,0 +1,382 @@ +package protoutil + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// ComputeTxID computes TxID as the Hash computed +// over the concatenation of nonce and creator. +func ComputeTxID(nonce, creator []byte) string { + // TODO: Get the Hash function to be used from + // channel configuration + hasher := sha256.New() + hasher.Write(nonce) + hasher.Write(creator) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func GetRandomNonce() ([]byte, error) { + return getRandomNonce() +} + +func getRandomNonce() ([]byte, error) { + key := make([]byte, 24) + + _, err := rand.Read(key) + if err != nil { + return nil, fmt.Errorf("error getting random bytes %w", err) + } + return key, nil +} + +// MarshalOrPanic serializes a protobuf message and panics if this +// operation fails +func MarshalOrPanic(pb proto.Message) []byte { + data, err := proto.Marshal(pb) + if err != nil { + panic(err) + } + return data +} + +// ExtractConfigFromBlock extracts the config from a block +func ExtractConfigFromBlock(block *common.Block) (*common.Config, error) { + if block == nil { + return nil, errors.New("nil block") + } + + envelope, err := GetEnvelopeFromBlock(block.Data.Data[0]) + if err != nil { + return nil, err + } + + payload, err := UnmarshalPayload(envelope.Payload) + if err != nil { + return nil, err + } + + if payload.Header == nil || payload.Header.ChannelHeader == nil { + return nil, errors.New("bad header") + } + + chdr, err := UnmarshalChannelHeader(payload.Header.ChannelHeader) + if err != nil { + return nil, err + } + + if common.HeaderType(chdr.Type) != common.HeaderType_CONFIG { + return nil, errors.New("not a config block") + } + + configEnvelope := &common.ConfigEnvelope{} + if err := proto.Unmarshal(payload.Data, configEnvelope); err != nil { + return nil, err + } + + return configEnvelope.Config, nil +} + +// GetEnvelopeFromBlock gets an envelope from a block's data +func GetEnvelopeFromBlock(data []byte) (*common.Envelope, error) { + envelope := &common.Envelope{} + if err := proto.Unmarshal(data, envelope); err != nil { + return nil, err + } + return envelope, nil +} + +// FormSignedEnvelope creates a signed envelope from pre-existing signatures +func FormSignedEnvelope( + txType common.HeaderType, + channelID string, + dataMsg proto.Message, + signatures [][]byte, + msgVersion int32, + epoch uint64, +) (*common.Envelope, error) { + return FormSignedEnvelopeWithTLSBinding(txType, channelID, dataMsg, signatures, msgVersion, epoch, nil) +} + +// FormSignedEnvelopeWithTLSBinding creates a signed envelope from pre-existing signatures with TLS binding +func FormSignedEnvelopeWithTLSBinding( + txType common.HeaderType, + channelID string, + dataMsg proto.Message, + signatures [][]byte, + msgVersion int32, + epoch uint64, + tlsCertHash []byte, +) (*common.Envelope, error) { + // Create channel header + payloadChannelHeader := MakeChannelHeader(txType, msgVersion, channelID, epoch) + payloadChannelHeader.TlsCertHash = tlsCertHash + + // Marshal the data message + data, err := proto.Marshal(dataMsg) + if err != nil { + return nil, fmt.Errorf("error marshaling data message: %w", err) + } + + // Create a payload without signature header since we'll use pre-existing signatures + payload := &common.Payload{ + Header: &common.Header{ + ChannelHeader: MarshalOrPanic(payloadChannelHeader), + // No SignatureHeader here as we're using pre-existing signatures + }, + Data: data, + } + + payloadBytes := MarshalOrPanic(payload) + + // Create envelope with the payload + envelope := &common.Envelope{ + Payload: payloadBytes, + } + + // If signatures are provided, use the first one as the envelope signature + if len(signatures) > 0 { + envelope.Signature = signatures[0] + } + + return envelope, nil +} + +// CreateSignedEnvelope creates a signed envelope of the desired type, with +// marshaled dataMsg and signs it +func CreateSignedEnvelope( + txType common.HeaderType, + channelID string, + signer identity.SigningIdentity, + dataMsg proto.Message, + msgVersion int32, + epoch uint64, +) (*common.Envelope, error) { + return CreateSignedEnvelopeWithTLSBinding(txType, channelID, signer, dataMsg, msgVersion, epoch, nil) +} + +// CreateSignedEnvelopeWithTLSBinding creates a signed envelope of the desired +// type, with marshaled dataMsg and signs it. It also includes a TLS cert hash +// into the channel header +func CreateSignedEnvelopeWithTLSBinding( + txType common.HeaderType, + channelID string, + signer identity.SigningIdentity, + dataMsg proto.Message, + msgVersion int32, + epoch uint64, + tlsCertHash []byte, +) (*common.Envelope, error) { + payloadChannelHeader := MakeChannelHeader(txType, msgVersion, channelID, epoch) + payloadChannelHeader.TlsCertHash = tlsCertHash + var err error + payloadSignatureHeader := &common.SignatureHeader{} + + if signer != nil { + payloadSignatureHeader, err = NewSignatureHeader(signer) + if err != nil { + return nil, err + } + } + + data, err := proto.Marshal(dataMsg) + if err != nil { + return nil, fmt.Errorf("error marshaling %w", err) + } + + paylBytes := MarshalOrPanic( + &common.Payload{ + Header: MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader), + Data: data, + }, + ) + + var sig []byte + if signer != nil { + sig, err = signer.Sign(paylBytes) + if err != nil { + return nil, err + } + } + + env := &common.Envelope{ + Payload: paylBytes, + Signature: sig, + } + + return env, nil +} + +// MakeChannelHeader creates a ChannelHeader. +func MakeChannelHeader(headerType common.HeaderType, version int32, chainID string, epoch uint64) *common.ChannelHeader { + return &common.ChannelHeader{ + Type: int32(headerType), + Version: version, + Timestamp: timestamppb.Now(), + ChannelId: chainID, + Epoch: epoch, + } +} + +// NewSignatureHeader returns a SignatureHeader with a valid nonce. +func NewSignatureHeader(id identity.Identity) (*common.SignatureHeader, error) { + serializedIdentity := &msp.SerializedIdentity{ + Mspid: id.MspID(), + IdBytes: id.Credentials(), + } + creator, err := proto.Marshal(serializedIdentity) + if err != nil { + return nil, err + } + nonce, err := CreateNonce() + if err != nil { + return nil, err + } + + return &common.SignatureHeader{ + Creator: creator, + Nonce: nonce, + }, nil +} + +func BlockDataHash(b *common.BlockData) []byte { + sum := sha256.Sum256(bytes.Join(b.Data, nil)) + return sum[:] +} + +// NewBlock constructs a block with no data and no metadata. +func NewBlock(seqNum uint64, previousHash []byte) *common.Block { + block := &common.Block{} + block.Header = &common.BlockHeader{} + block.Header.Number = seqNum + block.Header.PreviousHash = previousHash + block.Header.DataHash = []byte{} + block.Data = &common.BlockData{} + + var metadataContents [][]byte + for i := 0; i < len(common.BlockMetadataIndex_name); i++ { + metadataContents = append(metadataContents, []byte{}) + } + block.Metadata = &common.BlockMetadata{Metadata: metadataContents} + + return block +} + +// MakeSignatureHeader creates a SignatureHeader. +func MakeSignatureHeader(serializedCreatorCertChain []byte, nonce []byte) *common.SignatureHeader { + return &common.SignatureHeader{ + Creator: serializedCreatorCertChain, + Nonce: nonce, + } +} + +// SetTxID generates a transaction id based on the provided signature header +// and sets the TxId field in the channel header +func SetTxID(channelHeader *common.ChannelHeader, signatureHeader *common.SignatureHeader) { + channelHeader.TxId = ComputeTxID( + signatureHeader.Nonce, + signatureHeader.Creator, + ) +} + +// CreateNonceOrPanic generates a nonce using the common/crypto package +// and panics if this operation fails. +func CreateNonceOrPanic() []byte { + nonce, err := CreateNonce() + if err != nil { + panic(err) + } + return nonce +} + +// CreateNonce generates a nonce using the common/crypto package. +func CreateNonce() ([]byte, error) { + nonce, err := getRandomNonce() + return nonce, errors.Unwrap(fmt.Errorf("error generating random nonce %w", err)) +} + +// MakePayloadHeader creates a Payload Header. +func MakePayloadHeader(ch *common.ChannelHeader, sh *common.SignatureHeader) *common.Header { + return &common.Header{ + ChannelHeader: MarshalOrPanic(ch), + SignatureHeader: MarshalOrPanic(sh), + } +} + +// UnmarshalHeader unmarshals bytes to a Header +func UnmarshalHeader(bytes []byte) (*common.Header, error) { + hdr := &common.Header{} + err := proto.Unmarshal(bytes, hdr) + return hdr, errors.Unwrap(fmt.Errorf("error unmarshaling Header %w", err)) +} + +// UnmarshalChaincodeProposalPayload unmarshals bytes to a ChaincodeProposalPayload +func UnmarshalChaincodeProposalPayload(bytes []byte) (*peer.ChaincodeProposalPayload, error) { + cpp := &peer.ChaincodeProposalPayload{} + err := proto.Unmarshal(bytes, cpp) + return cpp, errors.Unwrap(fmt.Errorf("error unmarshaling ChaincodeProposalPayload %w", err)) +} + +// UnmarshalSignatureHeader unmarshals bytes to a SignatureHeader +func UnmarshalSignatureHeader(bytes []byte) (*common.SignatureHeader, error) { + sh := &common.SignatureHeader{} + err := proto.Unmarshal(bytes, sh) + return sh, errors.Unwrap(fmt.Errorf("error unmarshaling SignatureHeader %w", err)) +} + +// GetBytesProposalPayloadForTx takes a ChaincodeProposalPayload and returns +// its serialized version according to the visibility field +func GetBytesProposalPayloadForTx( + payload *peer.ChaincodeProposalPayload, +) ([]byte, error) { + // check for nil argument + if payload == nil { + return nil, errors.New("nil arguments") + } + + // strip the transient bytes off the payload + cppNoTransient := &peer.ChaincodeProposalPayload{Input: payload.Input, TransientMap: nil} + cppBytes, err := GetBytesChaincodeProposalPayload(cppNoTransient) + if err != nil { + return nil, err + } + + return cppBytes, nil +} + +// GetBytesChaincodeProposalPayload gets the chaincode proposal payload +func GetBytesChaincodeProposalPayload(cpp *peer.ChaincodeProposalPayload) ([]byte, error) { + cppBytes, err := proto.Marshal(cpp) + return cppBytes, errors.Unwrap(fmt.Errorf("error marshaling ChaincodeProposalPayload %w", err)) +} + +// GetBytesChaincodeActionPayload get the bytes of ChaincodeActionPayload from +// the message +func GetBytesChaincodeActionPayload(cap *peer.ChaincodeActionPayload) ([]byte, error) { + capBytes, err := proto.Marshal(cap) + return capBytes, errors.Unwrap(fmt.Errorf("error marshaling ChaincodeActionPayload %w", err)) +} + +// GetBytesTransaction get the bytes of Transaction from the message +func GetBytesTransaction(tx *peer.Transaction) ([]byte, error) { + bytes, err := proto.Marshal(tx) + return bytes, errors.Unwrap(fmt.Errorf("error unmarshaling Transaction %w", err)) +} + +// GetBytesPayload get the bytes of Payload from the message +func GetBytesPayload(payl *common.Payload) ([]byte, error) { + bytes, err := proto.Marshal(payl) + return bytes, errors.Unwrap(fmt.Errorf("error marshaling Payload %w", err)) +} diff --git a/internal/protoutil/unmarshalers.go b/internal/protoutil/unmarshalers.go new file mode 100644 index 0000000..92d9a4a --- /dev/null +++ b/internal/protoutil/unmarshalers.go @@ -0,0 +1,44 @@ +package protoutil + +import ( + "fmt" + + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" + "google.golang.org/protobuf/proto" +) + +// UnmarshalEnvelope unmarshals bytes to a Envelope +func UnmarshalEnvelope(encoded []byte) (*cb.Envelope, error) { + envelope := &cb.Envelope{} + if err := proto.Unmarshal(encoded, envelope); err != nil { + return nil, fmt.Errorf("error unmarshaling Envelope: %w", err) + } + return envelope, nil +} + +// UnmarshalPayload unmarshals bytes to a Payload +func UnmarshalPayload(encoded []byte) (*cb.Payload, error) { + payload := &cb.Payload{} + if err := proto.Unmarshal(encoded, payload); err != nil { + return nil, fmt.Errorf("error unmarshaling Payload: %w", err) + } + return payload, nil +} + +// UnmarshalChannelHeader unmarshals bytes to a ChannelHeader +func UnmarshalChannelHeader(bytes []byte) (*cb.ChannelHeader, error) { + chdr := &cb.ChannelHeader{} + if err := proto.Unmarshal(bytes, chdr); err != nil { + return nil, fmt.Errorf("error unmarshaling ChannelHeader: %w", err) + } + return chdr, nil +} + +// UnmarshalConfigUpdateEnvelope attempts to unmarshal bytes to a *cb.ConfigUpdate +func UnmarshalConfigUpdateEnvelope(data []byte) (*cb.ConfigUpdateEnvelope, error) { + configUpdateEnvelope := &cb.ConfigUpdateEnvelope{} + if err := proto.Unmarshal(data, configUpdateEnvelope); err != nil { + return nil, fmt.Errorf("error unmarshaling ConfigUpdateEnvelope: %w", err) + } + return configUpdateEnvelope, nil +} diff --git a/pkg/fabric/channel/channel.go b/pkg/fabric/channel/channel.go index 1151cec..20926f1 100644 --- a/pkg/fabric/channel/channel.go +++ b/pkg/fabric/channel/channel.go @@ -14,10 +14,10 @@ import ( "github.com/hyperledger/fabric-config/configtx/membership" "github.com/hyperledger/fabric-config/configtx/orderer" "github.com/hyperledger/fabric-config/protolator" - cb "github.com/hyperledger/fabric-protos-go/common" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/chainlaunch/chainlaunch/internal/protoutil" "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric/protoutil" ) // ChannelService handles channel operations @@ -88,7 +88,7 @@ func (s *ChannelService) CreateChannel(input CreateChannelInput) (*CreateChannel } // SetAnchorPeers updates the anchor peers for an organization in a channel -func (s *ChannelService) SetAnchorPeers(input *SetAnchorPeersInput) ([]byte, error) { +func (s *ChannelService) SetAnchorPeers(input *SetAnchorPeersInput) (*cb.Envelope, error) { // Create config manager and update anchor peers cftxGen := configtx.New(input.CurrentConfig) app := cftxGen.Application().Organization(input.MSPID) @@ -130,14 +130,14 @@ func (s *ChannelService) SetAnchorPeers(input *SetAnchorPeersInput) ([]byte, err } // Create envelope - envelopeBytes, err := s.createConfigUpdateEnvelope(input.ChannelName, configUpdate) + configEnvelope, err := s.createConfigUpdateEnvelope(input.ChannelName, configUpdate) if err != nil { return nil, fmt.Errorf("failed to create config update envelope: %w", err) } - return envelopeBytes, nil + return configEnvelope, nil } -func (s *ChannelService) createConfigUpdateEnvelope(channelID string, configUpdate *cb.ConfigUpdate) ([]byte, error) { +func (s *ChannelService) createConfigUpdateEnvelope(channelID string, configUpdate *cb.ConfigUpdate) (*cb.Envelope, error) { configUpdate.ChannelId = channelID configUpdateData, err := proto.Marshal(configUpdate) if err != nil { @@ -149,11 +149,8 @@ func (s *ChannelService) createConfigUpdateEnvelope(channelID string, configUpda if err != nil { return nil, err } - envelopeData, err := proto.Marshal(envelope) - if err != nil { - return nil, err - } - return envelopeData, nil + + return envelope, nil } // DecodeBlock decodes a base64 encoded block into JSON diff --git a/pkg/fabric/networkconfig/parser.go b/pkg/fabric/networkconfig/parser.go new file mode 100644 index 0000000..14e80a1 --- /dev/null +++ b/pkg/fabric/networkconfig/parser.go @@ -0,0 +1,58 @@ +package networkconfig + +import ( + "io" + "os" + + "gopkg.in/yaml.v3" +) + +// LoadFromFile loads a network configuration from a YAML file +func LoadFromFile(path string) (*NetworkConfig, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + return LoadFromReader(file) +} + +// LoadFromReader loads a network configuration from an io.Reader +func LoadFromReader(reader io.Reader) (*NetworkConfig, error) { + var config NetworkConfig + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// LoadFromBytes loads a network configuration from a byte slice +func LoadFromBytes(data []byte) (*NetworkConfig, error) { + var config NetworkConfig + if err := yaml.Unmarshal(data, &config); err != nil { + return nil, err + } + return &config, nil +} + +// SaveToFile saves a network configuration to a YAML file +func (c *NetworkConfig) SaveToFile(path string) error { + data, err := yaml.Marshal(c) + if err != nil { + return err + } + return os.WriteFile(path, data, 0644) +} + +// SaveToWriter saves a network configuration to an io.Writer +func (c *NetworkConfig) SaveToWriter(writer io.Writer) error { + encoder := yaml.NewEncoder(writer) + return encoder.Encode(c) +} + +// SaveToBytes converts a network configuration to a byte slice +func (c *NetworkConfig) SaveToBytes() ([]byte, error) { + return yaml.Marshal(c) +} diff --git a/pkg/fabric/networkconfig/parser_test.go b/pkg/fabric/networkconfig/parser_test.go new file mode 100644 index 0000000..b81c87f --- /dev/null +++ b/pkg/fabric/networkconfig/parser_test.go @@ -0,0 +1,105 @@ +package networkconfig + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadFromFile(t *testing.T) { + // Create a temporary test file + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test-config.yaml") + + // Write test YAML content + testYAML := ` +name: test-network +version: "1.0" +client: + organization: Org1 +organizations: + Org1: + mspid: Org1MSP + cryptoPath: /tmp/crypto + users: {} + peers: [] + orderers: [] +orderers: {} +peers: {} +certificateAuthorities: {} +channels: {} +` + err := os.WriteFile(testFile, []byte(testYAML), 0644) + assert.NoError(t, err) + + // Test loading from file + config, err := LoadFromFile(testFile) + assert.NoError(t, err) + assert.NotNil(t, config) + assert.Equal(t, "test-network", config.Name) + assert.Equal(t, "1.0", config.Version) + assert.Equal(t, "Org1", config.Client.Organization) +} + +func TestLoadFromBytes(t *testing.T) { + testYAML := ` +name: test-network +version: "1.0" +client: + organization: Org1 +organizations: + Org1: + mspid: Org1MSP + cryptoPath: /tmp/crypto + users: {} + peers: [] + orderers: [] +orderers: {} +peers: {} +certificateAuthorities: {} +channels: {} +` + + config, err := LoadFromBytes([]byte(testYAML)) + assert.NoError(t, err) + assert.NotNil(t, config) + assert.Equal(t, "test-network", config.Name) + assert.Equal(t, "1.0", config.Version) + assert.Equal(t, "Org1", config.Client.Organization) +} + +func TestSaveToFile(t *testing.T) { + // Create a test configuration + config := &NetworkConfig{ + Name: "test-network", + Version: "1.0", + Client: ClientConfig{ + Organization: "Org1", + }, + Organizations: make(map[string]Organization), + Orderers: make(map[string]Orderer), + Peers: make(map[string]Peer), + CertificateAuthorities: make(map[string]CertificateAuthority), + Channels: make(map[string]Channel), + } + + // Save to a temporary file + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test-save.yaml") + err := config.SaveToFile(testFile) + assert.NoError(t, err) + + // Verify the file exists + _, err = os.Stat(testFile) + assert.NoError(t, err) + + // Load the saved file and verify contents + loadedConfig, err := LoadFromFile(testFile) + assert.NoError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, config.Name, loadedConfig.Name) + assert.Equal(t, config.Version, loadedConfig.Version) + assert.Equal(t, config.Client.Organization, loadedConfig.Client.Organization) +} diff --git a/pkg/fabric/networkconfig/types.go b/pkg/fabric/networkconfig/types.go new file mode 100644 index 0000000..1d3e6d6 --- /dev/null +++ b/pkg/fabric/networkconfig/types.go @@ -0,0 +1,98 @@ +package networkconfig + +// NetworkConfig represents the root structure of the network configuration +type NetworkConfig struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Client ClientConfig `yaml:"client"` + Organizations map[string]Organization `yaml:"organizations"` + Orderers map[string]Orderer `yaml:"orderers"` + Peers map[string]Peer `yaml:"peers"` + CertificateAuthorities map[string]CertificateAuthority `yaml:"certificateAuthorities"` + Channels map[string]Channel `yaml:"channels"` +} + +// ClientConfig represents the client configuration +type ClientConfig struct { + Organization string `yaml:"organization"` +} + +// Organization represents an organization in the network +type Organization struct { + MSPID string `yaml:"mspid"` + CryptoPath string `yaml:"cryptoPath"` + Users map[string]User `yaml:"users"` + Peers []string `yaml:"peers"` + Orderers []string `yaml:"orderers"` +} + +// User represents a user in an organization +type User struct { + Cert UserCert `yaml:"cert"` + Key UserKey `yaml:"key"` +} + +// UserCert represents a user's certificate +type UserCert struct { + PEM string `yaml:"pem"` +} + +// UserKey represents a user's private key +type UserKey struct { + PEM string `yaml:"pem"` +} + +// Orderer represents an orderer node +type Orderer struct { + URL string `yaml:"url"` + AdminURL string `yaml:"adminUrl"` + AdminTLSCert string `yaml:"adminTlsCert"` + GRPCOptions GRPCOptions `yaml:"grpcOptions"` + TLSCACerts TLSCACerts `yaml:"tlsCACerts"` +} + +// Peer represents a peer node +type Peer struct { + URL string `yaml:"url"` + GRPCOptions GRPCOptions `yaml:"grpcOptions"` + TLSCACerts TLSCACerts `yaml:"tlsCACerts"` +} + +// GRPCOptions represents gRPC options +type GRPCOptions struct { + AllowInsecure bool `yaml:"allow-insecure"` +} + +// TLSCACerts represents TLS CA certificates +type TLSCACerts struct { + PEM string `yaml:"pem"` +} + +// CertificateAuthority represents a CA server +type CertificateAuthority struct { + URL string `yaml:"url"` + Registrar Registrar `yaml:"registrar"` + CAName string `yaml:"caName"` + TLSCACerts []TLSCACerts `yaml:"tlsCACerts"` +} + +// Registrar represents CA registrar information +type Registrar struct { + EnrollID string `yaml:"enrollId"` + EnrollSecret string `yaml:"enrollSecret"` +} + +// Channel represents a channel configuration +type Channel struct { + Orderers []string `yaml:"orderers"` + Peers map[string]PeerConfig `yaml:"peers"` +} + +// PeerConfig represents peer configuration within a channel +type PeerConfig struct { + Discover bool `yaml:"discover"` + EndorsingPeer bool `yaml:"endorsingPeer"` + ChaincodeQuery bool `yaml:"chaincodeQuery"` + LedgerQuery bool `yaml:"ledgerQuery"` + EventSource bool `yaml:"eventSource"` +} diff --git a/pkg/fabric/policydsl/policydsl.go b/pkg/fabric/policydsl/policydsl.go new file mode 100644 index 0000000..a22c09a --- /dev/null +++ b/pkg/fabric/policydsl/policydsl.go @@ -0,0 +1,384 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package policydsl + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/Knetic/govaluate" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" + mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "google.golang.org/protobuf/proto" +) + +// Gate values +const ( + GateAnd = "And" + GateOr = "Or" + GateOutOf = "OutOf" +) + +// Role values for principals +const ( + RoleAdmin = "admin" + RoleMember = "member" + RoleClient = "client" + RolePeer = "peer" + RoleOrderer = "orderer" +) + +var ( + regex = regexp.MustCompile( + fmt.Sprintf("^([[:alnum:].-]+)([.])(%s|%s|%s|%s|%s)$", + RoleAdmin, RoleMember, RoleClient, RolePeer, RoleOrderer), + ) + regexErr = regexp.MustCompile("^No parameter '([^']+)' found[.]$") +) + +// SignedBy creates a SignaturePolicy requiring a given signer's signature +func SignedBy(index int32) *cb.SignaturePolicy { + return &cb.SignaturePolicy{ + Type: &cb.SignaturePolicy_SignedBy{ + SignedBy: index, + }, + } +} + +// And is a convenience method which utilizes NOutOf to produce And equivalent behavior +func And(lhs, rhs *cb.SignaturePolicy) *cb.SignaturePolicy { + return NOutOf(2, []*cb.SignaturePolicy{lhs, rhs}) +} + +// Or is a convenience method which utilizes NOutOf to produce Or equivalent behavior +func Or(lhs, rhs *cb.SignaturePolicy) *cb.SignaturePolicy { + return NOutOf(1, []*cb.SignaturePolicy{lhs, rhs}) +} + +// NOutOf creates a policy which requires N out of the slice of policies to evaluate to true +func NOutOf(n int32, policies []*cb.SignaturePolicy) *cb.SignaturePolicy { + return &cb.SignaturePolicy{ + Type: &cb.SignaturePolicy_NOutOf_{ + NOutOf: &cb.SignaturePolicy_NOutOf{ + N: n, + Rules: policies, + }, + }, + } +} + +// a stub function - it returns the same string as it's passed. +// This will be evaluated by second/third passes to convert to a proto policy +func outof(args ...interface{}) (interface{}, error) { + toret := "outof(" + + if len(args) < 2 { + return nil, fmt.Errorf("expected at least two arguments to NOutOf. Given %d", len(args)) + } + + arg0 := args[0] + // govaluate treats all numbers as float64 only. But and/or may pass int/string. Allowing int/string for flexibility of caller + if n, ok := arg0.(float64); ok { + toret += strconv.Itoa(int(n)) + } else if n, ok := arg0.(int); ok { + toret += strconv.Itoa(n) + } else if n, ok := arg0.(string); ok { + toret += n + } else { + return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg0)) + } + + for _, arg := range args[1:] { + toret += ", " + + switch t := arg.(type) { + case string: + if regex.MatchString(t) { + toret += "'" + t + "'" + } else { + toret += t + } + default: + return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg)) + } + } + + return toret + ")", nil +} + +func and(args ...interface{}) (interface{}, error) { + args = append([]interface{}{len(args)}, args...) + return outof(args...) +} + +func or(args ...interface{}) (interface{}, error) { + args = append([]interface{}{1}, args...) + return outof(args...) +} + +func firstPass(args ...interface{}) (interface{}, error) { + toret := "outof(ID" + for _, arg := range args { + toret += ", " + + switch t := arg.(type) { + case string: + if regex.MatchString(t) { + toret += "'" + t + "'" + } else { + toret += t + } + case float32: + case float64: + toret += strconv.Itoa(int(t)) + default: + return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg)) + } + } + + return toret + ")", nil +} + +func secondPass(args ...interface{}) (interface{}, error) { + /* general sanity check, we expect at least 3 args */ + if len(args) < 3 { + return nil, fmt.Errorf("at least 3 arguments expected, got %d", len(args)) + } + + /* get the first argument, we expect it to be the context */ + var ctx *context + switch v := args[0].(type) { + case *context: + ctx = v + default: + return nil, fmt.Errorf("unrecognized type, expected the context, got %s", reflect.TypeOf(args[0])) + } + + /* get the second argument, we expect an integer telling us + how many of the remaining we expect to have*/ + var t int + switch arg := args[1].(type) { + case float64: + t = int(arg) + default: + return nil, fmt.Errorf("unrecognized type, expected a number, got %s", reflect.TypeOf(args[1])) + } + + /* get the n in the t out of n */ + n := len(args) - 2 + + /* sanity check - t should be positive, permit equal to n+1, but disallow over n+1 */ + if t < 0 || t > n+1 { + return nil, fmt.Errorf("invalid t-out-of-n predicate, t %d, n %d", t, n) + } + + policies := make([]*cb.SignaturePolicy, 0) + + /* handle the rest of the arguments */ + for _, principal := range args[2:] { + switch t := principal.(type) { + /* if it's a string, we expect it to be formed as + . , where MSP_ID is the MSP identifier + and ROLE is either a member, an admin, a client, a peer or an orderer*/ + case string: + /* split the string */ + subm := regex.FindAllStringSubmatch(t, -1) + if subm == nil || len(subm) != 1 || len(subm[0]) != 4 { + return nil, fmt.Errorf("error parsing principal %s", t) + } + + /* get the right role */ + var r mb.MSPRole_MSPRoleType + + switch subm[0][3] { + case RoleMember: + r = mb.MSPRole_MEMBER + case RoleAdmin: + r = mb.MSPRole_ADMIN + case RoleClient: + r = mb.MSPRole_CLIENT + case RolePeer: + r = mb.MSPRole_PEER + case RoleOrderer: + r = mb.MSPRole_ORDERER + default: + return nil, fmt.Errorf("error parsing role %s", t) + } + + /* build the principal we've been told */ + mspRole, err := proto.Marshal(&mb.MSPRole{MspIdentifier: subm[0][1], Role: r}) + if err != nil { + return nil, fmt.Errorf("error marshalling msp role: %s", err) + } + + p := &mb.MSPPrincipal{ + PrincipalClassification: mb.MSPPrincipal_ROLE, + Principal: mspRole, + } + ctx.principals = append(ctx.principals, p) + + /* create a SignaturePolicy that requires a signature from + the principal we've just built*/ + dapolicy := SignedBy(int32(ctx.IDNum)) + policies = append(policies, dapolicy) + + /* increment the identity counter. Note that this is + suboptimal as we are not reusing identities. We + can deduplicate them easily and make this puppy + smaller. For now it's fine though */ + // TODO: deduplicate principals + ctx.IDNum++ + + /* if we've already got a policy we're good, just append it */ + case *cb.SignaturePolicy: + policies = append(policies, t) + + default: + return nil, fmt.Errorf("unrecognized type, expected a principal or a policy, got %s", reflect.TypeOf(principal)) + } + } + + return NOutOf(int32(t), policies), nil +} + +type context struct { + IDNum int + principals []*mb.MSPPrincipal +} + +func newContext() *context { + return &context{IDNum: 0, principals: make([]*mb.MSPPrincipal, 0)} +} + +// FromString takes a string representation of the policy, +// parses it and returns a SignaturePolicyEnvelope that +// implements that policy. The supported language is as follows: +// +// GATE(P[, P]) +// +// where: +// - GATE is either "and" or "or" +// - P is either a principal or another nested call to GATE +// +// A principal is defined as: +// +// # ORG.ROLE +// +// where: +// - ORG is a string (representing the MSP identifier) +// - ROLE takes the value of any of the RoleXXX constants representing +// the required role +func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { + // first we translate the and/or business into outof gates + intermediate, err := govaluate.NewEvaluableExpressionWithFunctions( + policy, map[string]govaluate.ExpressionFunction{ + GateAnd: and, + strings.ToLower(GateAnd): and, + strings.ToUpper(GateAnd): and, + GateOr: or, + strings.ToLower(GateOr): or, + strings.ToUpper(GateOr): or, + GateOutOf: outof, + strings.ToLower(GateOutOf): outof, + strings.ToUpper(GateOutOf): outof, + }, + ) + if err != nil { + return nil, err + } + + intermediateRes, err := intermediate.Evaluate(map[string]interface{}{}) + if err != nil { + // attempt to produce a meaningful error + if regexErr.MatchString(err.Error()) { + sm := regexErr.FindStringSubmatch(err.Error()) + if len(sm) == 2 { + return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1]) + } + } + + return nil, err + } + + resStr, ok := intermediateRes.(string) + if !ok { + return nil, fmt.Errorf("invalid policy string '%s'", policy) + } + + // we still need two passes. The first pass just adds an extra + // argument ID to each of the outof calls. This is + // required because govaluate has no means of giving context + // to user-implemented functions other than via arguments. + // We need this argument because we need a global place where + // we put the identities that the policy requires + exp, err := govaluate.NewEvaluableExpressionWithFunctions( + resStr, + map[string]govaluate.ExpressionFunction{"outof": firstPass}, + ) + if err != nil { + return nil, err + } + + res, err := exp.Evaluate(map[string]interface{}{}) + if err != nil { + // attempt to produce a meaningful error + if regexErr.MatchString(err.Error()) { + sm := regexErr.FindStringSubmatch(err.Error()) + if len(sm) == 2 { + return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1]) + } + } + + return nil, err + } + + resStr, ok = res.(string) + if !ok { + return nil, fmt.Errorf("invalid policy string '%s'", policy) + } + + ctx := newContext() + parameters := make(map[string]interface{}, 1) + parameters["ID"] = ctx + + exp, err = govaluate.NewEvaluableExpressionWithFunctions( + resStr, + map[string]govaluate.ExpressionFunction{"outof": secondPass}, + ) + if err != nil { + return nil, err + } + + res, err = exp.Evaluate(parameters) + if err != nil { + // attempt to produce a meaningful error + if regexErr.MatchString(err.Error()) { + sm := regexErr.FindStringSubmatch(err.Error()) + if len(sm) == 2 { + return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1]) + } + } + + return nil, err + } + + rule, ok := res.(*cb.SignaturePolicy) + if !ok { + return nil, fmt.Errorf("invalid policy string '%s'", policy) + } + + p := &cb.SignaturePolicyEnvelope{ + Identities: ctx.principals, + Version: 0, + Rule: rule, + } + + return p, nil +} diff --git a/pkg/networks/service/fabric/deployer.go b/pkg/networks/service/fabric/deployer.go index 89f667b..d82097c 100644 --- a/pkg/networks/service/fabric/deployer.go +++ b/pkg/networks/service/fabric/deployer.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "encoding/pem" + "errors" "fmt" "net" "strconv" @@ -18,6 +19,7 @@ import ( "text/template" "github.com/Masterminds/sprig/v3" + "github.com/chainlaunch/chainlaunch/internal/protoutil" "github.com/chainlaunch/chainlaunch/pkg/certutils" "github.com/chainlaunch/chainlaunch/pkg/db" "github.com/chainlaunch/chainlaunch/pkg/fabric/channel" @@ -33,9 +35,7 @@ import ( "github.com/hyperledger/fabric-config/configtx" "github.com/hyperledger/fabric-config/configtx/orderer" "github.com/hyperledger/fabric-config/protolator" - cb "github.com/hyperledger/fabric-protos-go/common" - "github.com/hyperledger/fabric-sdk-go/pkg/fab/resource" - "github.com/hyperledger/fabric/protoutil" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" ) // ConfigUpdateOperationType represents the type of configuration update operation @@ -622,7 +622,7 @@ func (d *FabricDeployer) PrepareConfigUpdate(ctx context.Context, networkID int6 return nil, fmt.Errorf("failed to unmarshal config block: %w", err) } - config, err := resource.ExtractConfigFromBlock(block) + config, err := ExtractConfigFromBlock(block) if err != nil { return nil, fmt.Errorf("failed to extract config from block: %w", err) } @@ -1363,7 +1363,7 @@ func (d *FabricDeployer) SetAnchorPeers(ctx context.Context, networkID int64, or if ordererTLSKey.Certificate == nil { return "", fmt.Errorf("orderer TLS certificate not found") } - ordererURL := ordererConfig.GetURL() + ordererURL := ordererConfig.GetAddress() ordererCert := *ordererTLSKey.Certificate p, err := d.nodes.GetFabricPeer(ctx, peer.ID) @@ -1412,7 +1412,7 @@ func (d *FabricDeployer) SetAnchorPeers(ctx context.Context, networkID int64, or fabricConfig.ChannelName, ordererURL, *ordererTLSKey.Certificate, - []byte(channelUpdate), + channelUpdate, ) if err != nil { return "", fmt.Errorf("failed to save channel config: %w", err) @@ -1662,7 +1662,7 @@ func (d *FabricDeployer) FetchCurrentChannelConfig(ctx context.Context, networkI fabricOrgItem := fabricorg.NewOrganizationService(d.orgService, d.keyMgmt, d.logger, fabricOrg.MspID) // First try to get orderer from active nodes - var ordererURL, ordererTLSCert string + var ordererURL, ordererAddress, ordererTLSCert string for _, node := range networkNodes { if node.NodeType.String == string(nodetypes.NodeTypeFabricOrderer) && node.Status == "joined" { ordererNode, err := d.nodes.GetNodeByID(ctx, node.NodeID) @@ -1671,7 +1671,7 @@ func (d *FabricDeployer) FetchCurrentChannelConfig(ctx context.Context, networkI } ordererConfig := ordererNode.FabricOrderer ordererURL = fmt.Sprintf("grpcs://%s", ordererConfig.ExternalEndpoint) - + ordererAddress = ordererConfig.ExternalEndpoint // Get orderer TLS cert ordererTLSKey, err := d.keyMgmt.GetKey(ctx, int(ordererConfig.TLSKeyID)) if err != nil || ordererTLSKey.Certificate == nil { @@ -1697,7 +1697,7 @@ func (d *FabricDeployer) FetchCurrentChannelConfig(ctx context.Context, networkI } // Fetch channel config from peer - channelConfig, err := fabricOrgItem.GetConfigBlockWithNetworkConfig(ctx, network.Name, ordererURL, ordererTLSCert) + channelConfig, err := fabricOrgItem.GetConfigBlockWithNetworkConfig(ctx, network.Name, ordererAddress, ordererTLSCert) if err != nil { return nil, fmt.Errorf("failed to get channel config from peer: %w", err) } @@ -1880,7 +1880,7 @@ func (d *FabricDeployer) GetOrderersFromConfigBlock(ctx context.Context, blockBy return nil, fmt.Errorf("failed to unmarshal block: %w", err) } - cmnConfig, err := resource.ExtractConfigFromBlock(block) + cmnConfig, err := ExtractConfigFromBlock(block) if err != nil { return nil, fmt.Errorf("failed to extract config from block: %w", err) } @@ -1934,7 +1934,7 @@ func (d *FabricDeployer) GetOrderersFromGenesisBlock(ctx context.Context, networ return nil, fmt.Errorf("failed to unmarshal genesis block: %w", err) } - cmnConfig, err := resource.ExtractConfigFromBlock(block) + cmnConfig, err := ExtractConfigFromBlock(block) if err != nil { return nil, fmt.Errorf("failed to extract config from block: %w", err) } @@ -2114,3 +2114,26 @@ func CreateConfigUpdateEnvelope(channelID string, configUpdate *cb.ConfigUpdate) } return envelopeData, nil } + +// ExtractConfigFromBlock extracts channel configuration from block +func ExtractConfigFromBlock(block *cb.Block) (*cb.Config, error) { + if block == nil || block.Data == nil || len(block.Data.Data) == 0 { + return nil, errors.New("invalid block") + } + blockPayload := block.Data.Data[0] + + envelope := &cb.Envelope{} + if err := proto.Unmarshal(blockPayload, envelope); err != nil { + return nil, err + } + payload := &cb.Payload{} + if err := proto.Unmarshal(envelope.Payload, payload); err != nil { + return nil, err + } + + cfgEnv := &cb.ConfigEnvelope{} + if err := proto.Unmarshal(payload.Data, cfgEnv); err != nil { + return nil, err + } + return cfgEnv.Config, nil +} diff --git a/pkg/networks/service/fabric/org/org.go b/pkg/networks/service/fabric/org/org.go index d55e8ac..3a31c5a 100644 --- a/pkg/networks/service/fabric/org/org.go +++ b/pkg/networks/service/fabric/org/org.go @@ -1,186 +1,25 @@ package org import ( - "bytes" "context" + "crypto/tls" + "errors" "fmt" - "strings" - "text/template" - "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + "github.com/chainlaunch/chainlaunch/internal/protoutil" "github.com/chainlaunch/chainlaunch/pkg/fabric/service" keymanagement "github.com/chainlaunch/chainlaunch/pkg/keymanagement/service" "github.com/chainlaunch/chainlaunch/pkg/logger" - "github.com/hyperledger/fabric-protos-go/common" - "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" - "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp" - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite" - "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite/bccsp/sw" - "github.com/hyperledger/fabric-sdk-go/pkg/fab" - "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" - mspimpl "github.com/hyperledger/fabric-sdk-go/pkg/msp" + "github.com/hyperledger/fabric-admin-sdk/pkg/channel" + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + gwidentity "github.com/hyperledger/fabric-gateway/pkg/identity" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" ) -const tmplGoConfig = ` -name: hlf-network -version: 1.0.0 -client: - organization: "{{ .Organization }}" -{{- if not .Organizations }} -organizations: {} -{{- else }} -organizations: - {{ range $org := .Organizations }} - {{ $org.MSPID }}: - mspid: {{ $org.MSPID }} - cryptoPath: /tmp/cryptopath -{{ if not $org.Users }} - users: {} -{{- else }} - users: - {{- range $user := $org.Users }} - {{ $user.Name }}: - cert: - pem: | -{{ $user.Cert | indent 12 }} - key: - pem: | -{{ $user.Key | indent 12 }} -{{- end }} -{{- end }} -{{- if not $org.CertAuths }} - certificateAuthorities: [] -{{- else }} - certificateAuthorities: - {{- range $ca := $org.CertAuths }} - - {{ $ca.Name }} - {{- end }} -{{- end }} -{{- if not $org.Peers }} - peers: [] -{{- else }} - peers: - {{- range $peer := $org.Peers }} - - {{ $peer }} - {{- end }} -{{- end }} -{{- if not $org.Orderers }} - orderers: [] -{{- else }} - orderers: - {{- range $orderer := $org.Orderers }} - - {{ $orderer }} - {{- end }} - - {{- end }} -{{- end }} -{{- end }} - -{{- if not .Orderers }} -{{- else }} -orderers: -{{- range $orderer := .Orderers }} - {{$orderer.Name}}: - url: {{ $orderer.URL }} - grpcOptions: - allow-insecure: false - tlsCACerts: - pem: | -{{ $orderer.TLSCACert | indent 8 }} -{{- end }} -{{- end }} - -{{- if not .Peers }} -{{- else }} -peers: - {{- range $peer := .Peers }} - {{$peer.Name}}: - url: {{ $peer.URL }} - tlsCACerts: - pem: | -{{ $peer.TLSCACert | indent 8 }} -{{- end }} -{{- end }} - -{{- if not .CertAuths }} -{{- else }} -certificateAuthorities: -{{- range $ca := .CertAuths }} - {{ $ca.Name }}: - url: https://{{ $ca.URL }} -{{if $ca.EnrollID }} - registrar: - enrollId: {{ $ca.EnrollID }} - enrollSecret: "{{ $ca.EnrollSecret }}" -{{ end }} - caName: {{ $ca.CAName }} - tlsCACerts: - pem: - - | -{{ $ca.TLSCert | indent 12 }} - -{{- end }} -{{- end }} - -channels: - _default: -{{- if not .Orderers }} - orderers: [] -{{- else }} - orderers: -{{- range $orderer := .Orderers }} - - {{$orderer.Name}} -{{- end }} -{{- end }} -{{- if not .Peers }} - peers: {} -{{- else }} - peers: -{{- range $peer := .Peers }} - {{$peer.Name}}: - discover: true - endorsingPeer: true - chaincodeQuery: true - ledgerQuery: true - eventSource: true -{{- end }} -{{- end }} - -` - -type OrgUser struct { - Name string - Cert string - Key string -} -type Org struct { - MSPID string - CertAuths []string - Peers []string - Orderers []string - Users []OrgUser -} -type Peer struct { - Name string - URL string - TLSCACert string -} -type CA struct { - Name string - URL string - TLSCert string - EnrollID string - EnrollSecret string -} - -type Orderer struct { - URL string - Name string - TLSCACert string -} - type FabricOrg struct { orgService *service.OrganizationService keyMgmtService *keymanagement.KeyManagementService @@ -202,234 +41,247 @@ func NewOrganizationService( } } -// GenerateNetworkConfig generates a network configuration for connecting to Fabric network -func (s *FabricOrg) GenerateNetworkConfig(ctx context.Context, channelID, ordererURL, ordererTLSCert string) (string, error) { - s.logger.Info("Generating network config", +// GetConfigBlockWithNetworkConfig retrieves a config block using a generated network config +func (s *FabricOrg) GetConfigBlockWithNetworkConfig(ctx context.Context, channelID, ordererURL, ordererTLSCert string) (*cb.Block, error) { + s.logger.Info("Fetching channel config with network config", "mspID", s.mspID, "channel", channelID, - "ordererUrl", ordererURL) - + "ordererUrl", ordererURL, + ) + ordererNode := network.Node{ + Addr: ordererURL, + TLSCACertByte: []byte(ordererTLSCert), + } + ordererConn, err := network.DialConnection(ordererNode) + if err != nil { + return nil, fmt.Errorf("failed to dial orderer: %w", err) + } + defer ordererConn.Close() // Get organization details org, err := s.orgService.GetOrganizationByMspID(ctx, s.mspID) if err != nil { - return "", fmt.Errorf("failed to get organization: %w", err) + return nil, fmt.Errorf("failed to get organization: %w", err) + } + + // Get signing key + if !org.AdminSignKeyID.Valid { + return nil, fmt.Errorf("organization has no signing key") } // Get signing key var privateKeyPEM string if !org.AdminSignKeyID.Valid { - return "", fmt.Errorf("organization has no admin sign key") + return nil, fmt.Errorf("organization has no admin sign key") } adminSignKey, err := s.keyMgmtService.GetKey(ctx, int(org.AdminSignKeyID.Int64)) if err != nil { - return "", fmt.Errorf("failed to get admin sign key: %w", err) + return nil, fmt.Errorf("failed to get admin sign key: %w", err) } if adminSignKey.Certificate == nil { - return "", fmt.Errorf("admin sign key has no certificate") + return nil, fmt.Errorf("admin sign key has no certificate") } // Get private key from key management service privateKeyPEM, err = s.keyMgmtService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) if err != nil { - return "", fmt.Errorf("failed to get private key: %w", err) - } - - // Create template data - orgs := []*Org{} - var peers []*Peer - var certAuths []*CA - var ordererNodes []*Orderer - - // Add organization with user - fabricOrg := &Org{ - MSPID: org.MspID, - CertAuths: []string{}, - Peers: []string{}, - Orderers: []string{}, - } - - // Add admin user if signing certificate is available - if org.SignKeyID.Valid && org.SignCertificate != "" { - adminUser := OrgUser{ - Name: "Admin", - Cert: *adminSignKey.Certificate, - Key: privateKeyPEM, - } - fabricOrg.Users = []OrgUser{adminUser} - } - - orgs = append(orgs, fabricOrg) - if ordererURL != "" && ordererTLSCert != "" { - fabricOrg.Orderers = []string{"orderer0"} - // Add orderer - orderer := &Orderer{ - URL: ordererURL, - Name: "orderer0", - TLSCACert: ordererTLSCert, - } - ordererNodes = append(ordererNodes, orderer) - } - - // Parse template - tmpl, err := template.New("networkConfig").Funcs(template.FuncMap{ - "indent": func(spaces int, v string) string { - pad := strings.Repeat(" ", spaces) - return pad + strings.Replace(v, "\n", "\n"+pad, -1) - }, - }).Parse(tmplGoConfig) - if err != nil { - return "", fmt.Errorf("failed to parse network config template: %w", err) - } - - // Execute template - var buf bytes.Buffer - err = tmpl.Execute(&buf, map[string]interface{}{ - "Peers": peers, - "Orderers": ordererNodes, - "Organizations": orgs, - "CertAuths": certAuths, - "Organization": s.mspID, - "Internal": false, - }) + return nil, fmt.Errorf("failed to get private key: %w", err) + } + + cert, err := gwidentity.CertificateFromPEM([]byte(*adminSignKey.Certificate)) if err != nil { - return "", fmt.Errorf("failed to execute network config template: %w", err) + return nil, fmt.Errorf("failed to read certificate: %w", err) } - return buf.String(), nil -} + priv, err := gwidentity.PrivateKeyFromPEM([]byte(privateKeyPEM)) + if err != nil { + return nil, fmt.Errorf("failed to read private key: %w", err) + } -// GetConfigBlockWithNetworkConfig retrieves a config block using a generated network config -func (s *FabricOrg) GetConfigBlockWithNetworkConfig(ctx context.Context, channelID, ordererURL, ordererTLSCert string) (*common.Block, error) { - s.logger.Info("Fetching channel config with network config", - "mspID", s.mspID, - "channel", channelID, - "ordererUrl", ordererURL) + ordererMSP, err := identity.NewPrivateKeySigningIdentity(s.mspID, cert, priv) + if err != nil { + return nil, fmt.Errorf("failed to create orderer msp: %w", err) + } + // Parse the orderer TLS certificate + ordererTLSCertParsed, err := tls.X509KeyPair([]byte(*adminSignKey.Certificate), []byte(privateKeyPEM)) + if err != nil { + return nil, fmt.Errorf("failed to parse orderer TLS certificate: %w", err) + } + + ordererBlock, err := channel.GetConfigBlockFromOrderer(ctx, ordererConn, ordererMSP, channelID, ordererTLSCertParsed) + if err != nil { + return nil, fmt.Errorf("failed to get config block from orderer: %w", err) + } + return ordererBlock, nil +} + +// getAdminIdentity retrieves the admin identity for the organization +func (s *FabricOrg) getAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { // Get organization details org, err := s.orgService.GetOrganizationByMspID(ctx, s.mspID) if err != nil { return nil, fmt.Errorf("failed to get organization: %w", err) } - // Get signing key if !org.AdminSignKeyID.Valid { return nil, fmt.Errorf("organization has no signing key") } - // Generate network config - networkConfig, err := s.GenerateNetworkConfig(ctx, channelID, ordererURL, ordererTLSCert) + + // Get admin signing key + adminSignKey, err := s.keyMgmtService.GetKey(ctx, int(org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to get admin sign key: %w", err) + } + if adminSignKey.Certificate == nil { + return nil, fmt.Errorf("admin sign key has no certificate") } - // Initialize SDK with network config - configBackend := config.FromRaw([]byte(networkConfig), "yaml") - sdk, err := fabsdk.New(configBackend) + // Get private key from key management service + privateKeyPEM, err := s.keyMgmtService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to create sdk: %w", err) + return nil, fmt.Errorf("failed to get private key: %w", err) } - defer sdk.Close() - // Create SDK context - sdkContext := sdk.Context( - fabsdk.WithOrg(s.mspID), - fabsdk.WithUser("Admin"), - ) + cert, err := gwidentity.CertificateFromPEM([]byte(*adminSignKey.Certificate)) + if err != nil { + return nil, fmt.Errorf("failed to read certificate: %w", err) + } - // Create resource management client - resClient, err := resmgmt.New(sdkContext) + priv, err := gwidentity.PrivateKeyFromPEM([]byte(privateKeyPEM)) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return nil, fmt.Errorf("failed to read private key: %w", err) } - // Fetch channel configuration - configBlock, err := resClient.QueryConfigBlockFromOrderer(channelID) + signingIdentity, err := identity.NewPrivateKeySigningIdentity(s.mspID, cert, priv) if err != nil { - return nil, fmt.Errorf("failed to query channel config: %w", err) + return nil, fmt.Errorf("failed to create signing identity: %w", err) } - return configBlock, nil + return signingIdentity, nil } -// GetGenesisBlock fetches the genesis block for a channel from the orderer -func (s *FabricOrg) GetGenesisBlock(ctx context.Context, channelID string, ordererURL string, ordererTLSCert []byte) ([]byte, error) { - s.logger.Info("Fetching genesis block with network config", - "mspID", s.mspID, - "channel", channelID, - "ordererUrl", ordererURL) - +// getOrdererMSP creates a signing identity for interacting with the orderer +func (s *FabricOrg) getOrdererMSP(ctx context.Context) (identity.SigningIdentity, error) { // Get organization details org, err := s.orgService.GetOrganizationByMspID(ctx, s.mspID) if err != nil { return nil, fmt.Errorf("failed to get organization: %w", err) } - // Get signing key if !org.AdminSignKeyID.Valid { return nil, fmt.Errorf("organization has no signing key") } - // Generate network config - networkConfig, err := s.GenerateNetworkConfig(ctx, channelID, ordererURL, string(ordererTLSCert)) + // Get admin signing key + adminSignKey, err := s.keyMgmtService.GetKey(ctx, int(org.AdminSignKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to get admin sign key: %w", err) + } + if adminSignKey.Certificate == nil { + return nil, fmt.Errorf("admin sign key has no certificate") + } + + // Get private key from key management service + privateKeyPEM, err := s.keyMgmtService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to get private key: %w", err) } - // Initialize SDK with network config - configBackend := config.FromRaw([]byte(networkConfig), "yaml") - sdk, err := fabsdk.New(configBackend) + cert, err := identity.ReadCertificate(*adminSignKey.Certificate) if err != nil { - return nil, fmt.Errorf("failed to create sdk: %w", err) + return nil, fmt.Errorf("failed to read certificate: %w", err) } - defer sdk.Close() - resmClient, err := resmgmt.New(sdk.Context( - fabsdk.WithOrg(s.mspID), - fabsdk.WithUser("Admin"), - )) + + priv, err := identity.ReadPrivateKey(privateKeyPEM) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return nil, fmt.Errorf("failed to read private key: %w", err) } - genesisBlock, err := resmClient.GenesisBlock(channelID) + + ordererMSP, err := identity.NewPrivateKeySigningIdentity(s.mspID, cert, priv) if err != nil { - return nil, fmt.Errorf("failed to query genesis block: %w", err) + return nil, fmt.Errorf("failed to create orderer msp: %w", err) } - genesisBlockBytes, err := proto.Marshal(genesisBlock) + + return ordererMSP, nil +} + +// getOrdererConnection establishes a gRPC connection to the orderer +func (s *FabricOrg) getOrdererConnection(ctx context.Context, ordererURL string, ordererTLSCert string) (*grpc.ClientConn, error) { + + // Create orderer connection + ordererConn, err := network.DialConnection(network.Node{ + Addr: ordererURL, + TLSCACertByte: []byte(ordererTLSCert), + }) if err != nil { - return nil, fmt.Errorf("failed to marshal genesis block: %w", err) + return nil, fmt.Errorf("failed to create orderer connection: %w", err) } - return genesisBlockBytes, nil + + return ordererConn, nil } -// createSigningIdentity creates a signing identity from the organization's admin credentials -func (s *FabricOrg) createSigningIdentity(sdk *fabsdk.FabricSDK, privateKeyPEM string, certPEM string) (msp.SigningIdentity, error) { - sdkConfig, err := sdk.Config() +// getOrdererTLSKeyPair creates a TLS key pair for secure communication with the orderer +func (s *FabricOrg) getOrdererTLSKeyPair(ctx context.Context, ordererTLSCert string) (tls.Certificate, error) { + // Get organization details + org, err := s.orgService.GetOrganizationByMspID(ctx, s.mspID) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to get organization: %w", err) + } + + if !org.AdminSignKeyID.Valid { + return tls.Certificate{}, fmt.Errorf("organization has no admin sign key") + } + + // Get private key from key management service + privateKeyPEM, err := s.keyMgmtService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to get SDK config: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to get private key: %w", err) } - cryptoConfig := cryptosuite.ConfigFromBackend(sdkConfig) - cryptoSuite, err := sw.GetSuiteByConfig(cryptoConfig) + // Parse the orderer TLS certificate + ordererTLSCertParsed, err := tls.X509KeyPair([]byte(ordererTLSCert), []byte(privateKeyPEM)) if err != nil { - return nil, fmt.Errorf("failed to get crypto suite: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to parse orderer TLS certificate: %w", err) } - userStore := mspimpl.NewMemoryUserStore() - endpointConfig, err := fab.ConfigFromBackend(sdkConfig) + return ordererTLSCertParsed, nil +} + +// GetGenesisBlock fetches the genesis block for a channel from the orderer +func (s *FabricOrg) GetGenesisBlock(ctx context.Context, channelID string, ordererURL string, ordererTLSCert []byte) ([]byte, error) { + s.logger.Info("Fetching genesis block with network config", + "mspID", s.mspID, + "channel", channelID, + "ordererUrl", ordererURL) + + ordererConn, err := s.getOrdererConnection(ctx, ordererURL, string(ordererTLSCert)) if err != nil { - return nil, fmt.Errorf("failed to get endpoint config: %w", err) + return nil, fmt.Errorf("failed to get orderer connection: %w", err) } + defer ordererConn.Close() - identityManager, err := mspimpl.NewIdentityManager(s.mspID, userStore, cryptoSuite, endpointConfig) + ordererMSP, err := s.getOrdererMSP(ctx) if err != nil { - return nil, fmt.Errorf("failed to create identity manager: %w", err) + return nil, fmt.Errorf("failed to get orderer msp: %w", err) } - return identityManager.CreateSigningIdentity( - msp.WithPrivateKey([]byte(privateKeyPEM)), - msp.WithCert([]byte(certPEM)), - ) + ordererTLSKeyPair, err := s.getOrdererTLSKeyPair(ctx, string(ordererTLSCert)) + if err != nil { + return nil, fmt.Errorf("failed to get orderer tls key pair: %w", err) + } + genesisBlock, err := channel.GetGenesisBlock(ctx, ordererConn, ordererMSP, channelID, ordererTLSKeyPair) + if err != nil { + return nil, fmt.Errorf("failed to get genesis block: %w", err) + } + genesisBlockBytes, err := proto.Marshal(genesisBlock) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis block: %w", err) + } + + return genesisBlockBytes, nil } // CreateConfigSignature creates a signature for a config update using the organization's admin credentials -func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, configUpdateBytes []byte) (*common.ConfigSignature, error) { +func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, configUpdateBytes []byte) (*cb.ConfigSignature, error) { s.logger.Info("Creating config signature", "mspID", s.mspID, "channel", channelID) @@ -454,49 +306,86 @@ func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, return nil, fmt.Errorf("admin sign key has no certificate") } - // Get private key - privateKeyPEM, err := s.keyMgmtService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) + // Create signing identity + signingIdentity, err := s.getAdminIdentity(ctx) if err != nil { - return nil, fmt.Errorf("failed to get private key: %w", err) + return nil, fmt.Errorf("failed to create signing identity: %w", err) } - // Generate network config for SDK initialization - networkConfig, err := s.GenerateNetworkConfig(ctx, channelID, "", "") // Empty orderer details as they're not needed for signing + var envelope cb.Envelope + err = proto.Unmarshal(configUpdateBytes, &envelope) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to unmarshal envelope: %w", err) } + // Create config signature from the config update bytes + signature, err := SignConfigTx(channelID, &envelope, signingIdentity) + if err != nil { + return nil, fmt.Errorf("failed to create config signature: %w", err) + } + return signature, nil +} - // Initialize SDK - configBackend := config.FromRaw([]byte(networkConfig), "yaml") - sdk, err := fabsdk.New(configBackend) +const ( + msgVersion = int32(0) + epoch = 0 +) + +func SignConfigTx(channelID string, envConfigUpdate *cb.Envelope, signer identity.SigningIdentity) (*cb.ConfigSignature, error) { + payload, err := protoutil.UnmarshalPayload(envConfigUpdate.Payload) if err != nil { - return nil, fmt.Errorf("failed to create sdk: %w", err) + return nil, errors.New("bad payload") } - defer sdk.Close() - // Create signing identity - signingIdentity, err := s.createSigningIdentity(sdk, privateKeyPEM, *adminSignKey.Certificate) + if payload.Header == nil || payload.Header.ChannelHeader == nil { + return nil, errors.New("bad header") + } + + ch, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) if err != nil { - return nil, fmt.Errorf("failed to create signing identity: %w", err) + return nil, errors.New("could not unmarshall channel header") } - // Create SDK context with signing identity - sdkContext := sdk.Context( - fabsdk.WithIdentity(signingIdentity), - fabsdk.WithOrg(s.mspID), - ) + if ch.Type != int32(cb.HeaderType_CONFIG_UPDATE) { + return nil, errors.New("bad type") + } + + if ch.ChannelId == "" { + return nil, errors.New("empty channel id") + } - // Create resource management client - resClient, err := resmgmt.New(sdkContext) + configUpdateEnv, err := protoutil.UnmarshalConfigUpdateEnvelope(payload.Data) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return nil, errors.New("bad config update env") } - // Create config signature from the config update bytes - signature, err := resClient.CreateConfigSignatureFromReader(signingIdentity, bytes.NewReader(configUpdateBytes)) + sigHeader, err := protoutil.NewSignatureHeader(signer) if err != nil { - return nil, fmt.Errorf("failed to create config signature: %w", err) + return nil, err } - return signature, nil + configSig := &cb.ConfigSignature{ + SignatureHeader: protoutil.MarshalOrPanic(sigHeader), + } + + configSig.Signature, err = signer.Sign(Concatenate(configSig.SignatureHeader, configUpdateEnv.ConfigUpdate)) + if err != nil { + return nil, err + } + + return configSig, nil +} +func Concatenate[T any](slices ...[]T) []T { + size := 0 + for _, slice := range slices { + size += len(slice) + } + + result := make([]T, size) + i := 0 + for _, slice := range slices { + copy(result[i:], slice) + i += len(slice) + } + + return result } diff --git a/pkg/networks/service/service.go b/pkg/networks/service/service.go index 76c0ea6..26294b7 100644 --- a/pkg/networks/service/service.go +++ b/pkg/networks/service/service.go @@ -684,7 +684,7 @@ func (s *NetworkService) SetAnchorPeers(ctx context.Context, networkID, organiza return "", fmt.Errorf("failed to get network nodes: %w", err) } - var ordererURL, ordererTLSCert string + var ordererAddress, ordererTLSCert string // Look for orderer in our registry for _, node := range networkNodes { @@ -693,14 +693,14 @@ func (s *NetworkService) SetAnchorPeers(ctx context.Context, networkID, organiza if !ok { continue } - ordererURL = fmt.Sprintf("grpcs://%s", ordererConfig.ExternalEndpoint) + ordererAddress = ordererConfig.ExternalEndpoint ordererTLSCert = ordererConfig.TLSCACert break } } // If no orderer found in registry, try to get from current config block - if ordererURL == "" { + if ordererAddress == "" { // Get current config block configBlock, err := fabricDeployer.GetCurrentChannelConfig(networkID) if err != nil { @@ -715,16 +715,16 @@ func (s *NetworkService) SetAnchorPeers(ctx context.Context, networkID, organiza if len(ordererInfo) == 0 { return "", fmt.Errorf("no orderer found in config block") } - ordererURL = ordererInfo[0].URL + ordererAddress = ordererInfo[0].URL ordererTLSCert = ordererInfo[0].TLSCert } - if ordererURL == "" { + if ordererAddress == "" { return "", fmt.Errorf("no orderer found in network or config block") } // Set anchor peers using deployer with the found orderer info - txID, err := fabricDeployer.SetAnchorPeersWithOrderer(ctx, networkID, organizationID, deployerAnchorPeers, ordererURL, ordererTLSCert) + txID, err := fabricDeployer.SetAnchorPeersWithOrderer(ctx, networkID, organizationID, deployerAnchorPeers, ordererAddress, ordererTLSCert) if err != nil { return "", err } diff --git a/pkg/nodes/http/handler.go b/pkg/nodes/http/handler.go index 4d059bb..75fb0ac 100644 --- a/pkg/nodes/http/handler.go +++ b/pkg/nodes/http/handler.go @@ -59,6 +59,7 @@ func (h *NodeHandler) RegisterRoutes(r chi.Router) { r.Delete("/{id}", response.Middleware(h.DeleteNode)) r.Get("/{id}/logs", h.TailLogs) r.Get("/{id}/events", response.Middleware(h.GetNodeEvents)) + r.Get("/{id}/channels", response.Middleware(h.GetNodeChannels)) }) } @@ -593,99 +594,68 @@ func (h *NodeHandler) GetNodeEvents(w http.ResponseWriter, r *http.Request) erro return response.WriteJSON(w, http.StatusOK, eventsResponse) } -// Mapping functions - -func mapHTTPToServiceFabricPeerConfig(config *types.FabricPeerConfig) *types.FabricPeerConfig { - if config == nil { - return nil - } - return &types.FabricPeerConfig{ - Name: config.Name, - OrganizationID: config.OrganizationID, - ExternalEndpoint: config.ExternalEndpoint, - ListenAddress: config.ListenAddress, - EventsAddress: config.EventsAddress, - OperationsListenAddress: config.OperationsListenAddress, - ChaincodeAddress: config.ChaincodeAddress, - DomainNames: config.DomainNames, - Env: config.Env, - MSPID: config.MSPID, +// GetNodeChannels godoc +// @Summary Get channels for a Fabric node +// @Description Retrieves all channels for a specific Fabric node +// @Tags nodes +// @Accept json +// @Produce json +// @Param id path int true "Node ID" +// @Success 200 {object} NodeChannelsResponse +// @Failure 400 {object} response.ErrorResponse "Validation error" +// @Failure 404 {object} response.ErrorResponse "Node not found" +// @Failure 500 {object} response.ErrorResponse "Internal server error" +// @Router /nodes/{id}/channels [get] +func (h *NodeHandler) GetNodeChannels(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + return errors.NewValidationError("invalid node ID", map[string]interface{}{ + "error": err.Error(), + }) } -} -func mapHTTPToServiceFabricOrdererConfig(config *types.FabricOrdererConfig) *types.FabricOrdererConfig { - if config == nil { - return nil - } - return &types.FabricOrdererConfig{ - Name: config.Name, - OrganizationID: config.OrganizationID, - // Mode: "service", - ExternalEndpoint: config.ExternalEndpoint, - ListenAddress: config.ListenAddress, - AdminAddress: config.AdminAddress, - OperationsListenAddress: config.OperationsListenAddress, - DomainNames: config.DomainNames, - Env: config.Env, - MSPID: config.MSPID, + channels, err := h.service.GetNodeChannels(r.Context(), id) + if err != nil { + if err == service.ErrNotFound { + return errors.NewNotFoundError("node not found", nil) + } + if err == service.ErrInvalidNodeType { + return errors.NewValidationError("node is not a Fabric node", nil) + } + return errors.NewInternalError("failed to get node channels", err, nil) } -} -func mapHTTPToServiceBesuNodeConfig(config *types.BesuNodeConfig) *types.BesuNodeConfig { - if config == nil { - return nil - } - return &types.BesuNodeConfig{ - NetworkID: config.NetworkID, - P2PPort: config.P2PPort, - RPCPort: config.RPCPort, - BaseNodeConfig: types.BaseNodeConfig{ - Type: "besu", - Mode: config.Mode, - }, - KeyID: config.KeyID, - P2PHost: config.P2PHost, - RPCHost: config.RPCHost, - InternalIP: config.InternalIP, - ExternalIP: config.ExternalIP, - Env: config.Env, + channelsResponse := NodeChannelsResponse{ + NodeID: id, + Channels: make([]ChannelResponse, len(channels)), } -} -func mapServiceToHTTPFabricPeerDeploymentConfig(config *types.FabricPeerDeploymentConfig) *types.FabricPeerDeploymentConfig { - if config == nil { - return nil + + for i, channel := range channels { + channelsResponse.Channels[i] = toChannelResponse(channel) } - return config + + return response.WriteJSON(w, http.StatusOK, channelsResponse) } -func mapServiceToHTTPNodeResponse(node *service.NodeResponse) NodeResponse { - return NodeResponse{ - ID: node.ID, - Name: node.Name, - BlockchainPlatform: string(node.Platform), - NodeType: string(node.NodeType), - Status: string(node.Status), - Endpoint: node.Endpoint, - CreatedAt: node.CreatedAt, - UpdatedAt: node.UpdatedAt, - FabricPeer: node.FabricPeer, - FabricOrderer: node.FabricOrderer, - BesuNode: node.BesuNode, - } +// NodeChannelsResponse represents the response for node channels +type NodeChannelsResponse struct { + NodeID int64 `json:"nodeId"` + Channels []ChannelResponse `json:"channels"` } -func mapServiceToHTTPPaginatedResponse(nodes *service.PaginatedNodes) PaginatedNodesResponse { - items := make([]NodeResponse, len(nodes.Items)) - for i, node := range nodes.Items { - items[i] = mapServiceToHTTPNodeResponse(&node) - } +// ChannelResponse represents a Fabric channel in the response +type ChannelResponse struct { + Name string `json:"name"` + BlockNum int64 `json:"blockNum"` + CreatedAt time.Time `json:"createdAt,omitempty"` +} - return PaginatedNodesResponse{ - Items: items, - Total: nodes.Total, - Page: nodes.Page, - PageCount: nodes.PageCount, - HasNextPage: nodes.HasNextPage, +// Helper function to convert service channel to response channel +func toChannelResponse(channel service.Channel) ChannelResponse { + return ChannelResponse{ + Name: channel.Name, + BlockNum: channel.BlockNum, + CreatedAt: channel.CreatedAt, } } @@ -705,18 +675,6 @@ func toNodeResponse(node *service.NodeResponse) NodeResponse { } } -func isValidEventType(eventType service.NodeEventType) bool { - switch eventType { - case service.NodeEventStarting, - service.NodeEventStarted, - service.NodeEventStopping, - service.NodeEventStopped, - service.NodeEventError: - return true - } - return false -} - // Helper function to validate platform func isValidPlatform(platform types.BlockchainPlatform) bool { switch platform { diff --git a/pkg/nodes/orderer/orderer.go b/pkg/nodes/orderer/orderer.go index 17abbea..a3e94c2 100644 --- a/pkg/nodes/orderer/orderer.go +++ b/pkg/nodes/orderer/orderer.go @@ -6,11 +6,8 @@ import ( "context" "crypto/tls" "crypto/x509" - "encoding/json" "fmt" - "io" "net" - "net/http" "os" "os/exec" "path/filepath" @@ -25,8 +22,12 @@ import ( kmodels "github.com/chainlaunch/chainlaunch/pkg/keymanagement/models" keymanagement "github.com/chainlaunch/chainlaunch/pkg/keymanagement/service" "github.com/chainlaunch/chainlaunch/pkg/logger" - "github.com/chainlaunch/chainlaunch/pkg/nodes/orderer/osnadmin" "github.com/chainlaunch/chainlaunch/pkg/nodes/types" + "github.com/hyperledger/fabric-admin-sdk/pkg/channel" + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + gwidentity "github.com/hyperledger/fabric-gateway/pkg/identity" + "google.golang.org/grpc" ) // LocalOrderer represents a local Fabric orderer node @@ -951,26 +952,12 @@ func (o *LocalOrderer) JoinChannel(genesisBlock []byte) error { return fmt.Errorf("couldn't append certs") } ordererAdminUrl := fmt.Sprintf("https://%s", strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1)) - chResponse, err := osnadmin.Join(ordererAdminUrl, genesisBlock, certPool, adminTlsCertX509) - if err != nil { - return err - } - if chResponse.StatusCode == 405 { - return fmt.Errorf("orderer already joined the channel") - } - responseData, err := io.ReadAll(chResponse.Body) - if err != nil { - return err - } - if chResponse.StatusCode != 201 { - return fmt.Errorf("error joining orderer to channel: %d", chResponse.StatusCode) - } - var response osnadmin.ChannelInfo - err = json.Unmarshal(responseData, &response) + channelInfo, err := channel.JoinOrderer(ordererAdminUrl, genesisBlock, certPool, adminTlsCertX509) if err != nil { - return err + return fmt.Errorf("failed to join orderer to channel: %w", err) } + o.logger.Info("Successfully joined orderer to channel", "orderer", o.opts.ID, "channel", channelInfo.Name) return nil } @@ -1025,17 +1012,148 @@ func (o *LocalOrderer) LeaveChannel(channelID string) error { } adminAddress := strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1) // Call osnadmin Remove API - resp, err := osnadmin.Remove(fmt.Sprintf("https://%s", adminAddress), channelID, caCertPool, cert) + err = channel.RemoveChannelFromOrderer(fmt.Sprintf("https://%s", adminAddress), channelID, caCertPool, cert) if err != nil { return fmt.Errorf("failed to remove orderer from channel: %w", err) } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusNoContent { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to remove orderer from channel: status=%d, body=%s", resp.StatusCode, string(body)) - } o.logger.Info("Successfully removed orderer from channel", "orderer", o.opts.ID, "channel", channelID) return nil } + +type OrdererChannel struct { + Name string `json:"name"` + BlockNum int64 `json:"blockNum"` + CreatedAt time.Time `json:"createdAt"` +} + +// GetOrdererAddress returns the orderer's external endpoint +func (o *LocalOrderer) GetOrdererAddress() string { + return o.opts.ExternalEndpoint +} + +// GetTLSRootCACert returns the TLS root CA certificate for the orderer +func (o *LocalOrderer) GetTLSRootCACert(ctx context.Context) (string, error) { + org, err := o.orgService.GetOrganization(ctx, o.organizationID) + if err != nil { + return "", fmt.Errorf("failed to get organization: %w", err) + } + return org.TlsCertificate, nil +} + +// CreateOrdererConnection creates a gRPC connection to an orderer +func (o *LocalOrderer) CreateOrdererConnection(ctx context.Context, ordererUrl string, ordererTlsCACert string) (*grpc.ClientConn, error) { + o.logger.Debug("Creating orderer connection", "url", ordererUrl) + networkNode := network.Node{ + Addr: ordererUrl, + TLSCACertByte: []byte(ordererTlsCACert), + } + conn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to create orderer connection: %w", err) + } + return conn, nil +} + +// GetAdminIdentity returns the admin identity for the orderer +func (o *LocalOrderer) GetAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { + org, err := o.orgService.GetOrganization(ctx, o.organizationID) + if err != nil { + return nil, fmt.Errorf("failed to get organization: %w", err) + } + + // Get admin signing key + adminSignKeyDB, err := o.keyService.GetKey(ctx, int(org.AdminSignKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to get admin signing key: %w", err) + } + adminSignCert := adminSignKeyDB.Certificate + if adminSignCert == nil { + return nil, fmt.Errorf("admin signing certificate is nil") + } + + // Get private key from key management service + privateKeyPEM, err := o.keyService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to get private key: %w", err) + } + + cert, err := gwidentity.CertificateFromPEM([]byte(*adminSignCert)) + if err != nil { + return nil, fmt.Errorf("failed to read certificate: %w", err) + } + + privateKey, err := gwidentity.PrivateKeyFromPEM([]byte(privateKeyPEM)) + if err != nil { + return nil, fmt.Errorf("failed to read private key: %w", err) + } + + id, err := identity.NewPrivateKeySigningIdentity(org.MspID, cert, privateKey) + if err != nil { + return nil, fmt.Errorf("failed to create identity: %w", err) + } + + return id, nil +} + +// GetChannels returns a list of channels the orderer is participating in +func (o *LocalOrderer) GetChannels(ctx context.Context) ([]OrdererChannel, error) { + // Get organization + org, err := o.orgService.GetOrganization(ctx, o.organizationID) + if err != nil { + return nil, fmt.Errorf("failed to get organization: %w", err) + } + + // Get admin TLS credentials + adminTlsKeyDB, err := o.keyService.GetKey(ctx, int(org.AdminTlsKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to get admin TLS key: %w", err) + } + adminTlsCert := adminTlsKeyDB.Certificate + if adminTlsCert == nil { + return nil, fmt.Errorf("admin TLS certificate is nil") + } + if *adminTlsCert == "" { + return nil, fmt.Errorf("admin TLS certificate is empty") + } + adminTlsPK, err := o.keyService.GetDecryptedPrivateKey(int(org.AdminTlsKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to get admin TLS private key: %w", err) + } + + // Create client certificate + cert, err := tls.X509KeyPair([]byte(*adminTlsCert), []byte(adminTlsPK)) + if err != nil { + return nil, fmt.Errorf("failed to load client certificate: %w", err) + } + + // Create CA cert pool + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM([]byte(org.TlsCertificate)) + if !ok { + return nil, fmt.Errorf("failed to append TLS root certificate to CA cert pool") + } + + // Call osnadmin List API + adminAddress := strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1) + channelList, err := channel.ListChannel(fmt.Sprintf("https://%s", adminAddress), certPool, cert) + if err != nil { + return nil, fmt.Errorf("failed to list channels: %w", err) + } + + // Convert to service.Channel format + var channels []OrdererChannel + for _, ch := range channelList.Channels { + blockInfo, err := channel.ListSingleChannel(fmt.Sprintf("https://%s", adminAddress), ch.Name, certPool, cert) + if err != nil { + return nil, fmt.Errorf("failed to get block height for channel: %w", err) + } + channels = append(channels, OrdererChannel{ + Name: ch.Name, + BlockNum: int64(blockInfo.Height), + CreatedAt: time.Now(), // We don't have the actual creation time + }) + } + + return channels, nil +} diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index 657b3ca..c80c446 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -4,7 +4,9 @@ import ( "bufio" "bytes" "context" + "crypto/tls" "crypto/x509" + "errors" "fmt" "net" "os" @@ -18,18 +20,15 @@ import ( // add sprig/v3 "github.com/Masterminds/sprig/v3" "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric-protos-go/common" - cb "github.com/hyperledger/fabric-protos-go/common" - "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" - "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp" - "github.com/hyperledger/fabric-sdk-go/pkg/core/config" - "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite" - "github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite/bccsp/sw" - "github.com/hyperledger/fabric-sdk-go/pkg/fab" - "github.com/hyperledger/fabric-sdk-go/pkg/fab/resource" - "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" - mspimpl "github.com/hyperledger/fabric-sdk-go/pkg/msp" - + "github.com/hyperledger/fabric-admin-sdk/pkg/channel" + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" + gwidentity "github.com/hyperledger/fabric-gateway/pkg/identity" + cb "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/orderer" + "google.golang.org/grpc" + + "github.com/chainlaunch/chainlaunch/internal/protoutil" "github.com/chainlaunch/chainlaunch/pkg/binaries" "github.com/chainlaunch/chainlaunch/pkg/db" fabricservice "github.com/chainlaunch/chainlaunch/pkg/fabric/service" @@ -566,10 +565,11 @@ func (p *LocalPeer) stopLaunchdService() error { return nil } + // execSystemctl executes a systemctl command func (p *LocalPeer) execSystemctl(command string, args ...string) error { cmdArgs := append([]string{command}, args...) - + // Check if sudo is available sudoPath, err := exec.LookPath("sudo") if err == nil { @@ -586,7 +586,7 @@ func (p *LocalPeer) execSystemctl(command string, args ...string) error { return fmt.Errorf("systemctl %s failed: %w", command, err) } } - + return nil } @@ -754,7 +754,7 @@ func (p *LocalPeer) generateNetworkConfigForPeer( org := &Org{ MSPID: peerMspID, CertAuths: []string{}, - Peers: []string{"peer0"}, + Peers: []string{}, Orderers: []string{}, } orgs = append(orgs, org) @@ -764,15 +764,17 @@ func (p *LocalPeer) generateNetworkConfigForPeer( URL: peerUrl, TLSCACert: peerTlsCACert, } + org.Peers = append(org.Peers, "peer0") peers = append(peers, peer) } - - orderer := &Orderer{ - URL: ordererUrl, - Name: "orderer0", - TLSCACert: ordererTlsCACert, + if ordererTlsCACert != "" && ordererUrl != "" { + orderer := &Orderer{ + URL: ordererUrl, + Name: "orderer0", + TLSCACert: ordererTlsCACert, + } + ordererNodes = append(ordererNodes, orderer) } - ordererNodes = append(ordererNodes, orderer) err = tmpl.Execute(&buf, map[string]interface{}{ "Peers": peers, "Orderers": ordererNodes, @@ -784,6 +786,7 @@ func (p *LocalPeer) generateNetworkConfigForPeer( if err != nil { return nil, err } + p.logger.Debugf("Network config: %s", buf.String()) return &NetworkConfigResponse{ NetworkConfig: buf.String(), }, nil @@ -792,55 +795,34 @@ func (p *LocalPeer) generateNetworkConfigForPeer( // JoinChannel joins the peer to a channel func (p *LocalPeer) JoinChannel(genesisBlock []byte) error { p.logger.Info("Joining peer to channel", "peer", p.opts.ID) - - // Create temporary file for genesis block - tmpFile, err := os.CreateTemp("", "genesis-block-*.block") + var genesisBlockProto cb.Block + err := proto.Unmarshal(genesisBlock, &genesisBlockProto) if err != nil { - return fmt.Errorf("failed to create temp file: %w", err) + return fmt.Errorf("failed to unmarshal genesis block: %w", err) } - defer os.Remove(tmpFile.Name()) - - homeDir, err := os.UserHomeDir() + ctx := context.Background() + tlsCACert, err := p.GetTLSRootCACert(ctx) if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) + return fmt.Errorf("failed to get TLS root CA cert: %w", err) } - slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") - peerConfigPath := filepath.Join(homeDir, ".chainlaunch/peers", slugifiedID, "config") - - // Write genesis block to file - if err := os.WriteFile(tmpFile.Name(), genesisBlock, 0644); err != nil { - return fmt.Errorf("failed to write genesis block: %w", err) - } - - // Build peer channel join command - peerBinary, err := p.findPeerBinary() + peerConn, err := p.CreatePeerConnection(ctx, p.opts.ExternalEndpoint, tlsCACert) if err != nil { - return fmt.Errorf("failed to find peer binary: %w", err) + return fmt.Errorf("failed to create peer connection: %w", err) } - mspConfigPath, err := p.PrepareAdminCertMSP(p.org.MspID) + defer peerConn.Close() + + adminIdentity, err := p.GetAdminIdentity(ctx) if err != nil { - return fmt.Errorf("failed to prepare admin cert MSP: %w", err) + return fmt.Errorf("failed to get admin identity: %w", err) } - cmd := exec.Command(peerBinary, "channel", "join", "-b", tmpFile.Name()) - listenAddress := strings.Replace(p.opts.ListenAddress, "0.0.0.0", "localhost", 1) - // Set environment variables - cmd.Env = append(os.Environ(), - fmt.Sprintf("CORE_PEER_MSPCONFIGPATH=%s", mspConfigPath), - fmt.Sprintf("CORE_PEER_ADDRESS=%s", listenAddress), - fmt.Sprintf("CORE_PEER_LOCALMSPID=%s", p.mspID), - "CORE_PEER_TLS_ENABLED=true", - fmt.Sprintf("CORE_PEER_TLS_ROOTCERT_FILE=%s", filepath.Join(mspConfigPath, "tlscacerts", "cacert.pem")), - fmt.Sprintf("FABRIC_CFG_PATH=%s", peerConfigPath), - ) - // Execute command - output, err := cmd.CombinedOutput() + err = channel.JoinChannel(ctx, peerConn, adminIdentity, &genesisBlockProto) if err != nil { - return fmt.Errorf("failed to join channel: %w, output: %s", err, string(output)) + return fmt.Errorf("failed to join channel: %w", err) } - p.logger.Info("Successfully joined channel", "peer", p.opts.ID) return nil + } // writeCertificatesAndKeys writes the certificates and keys to the MSP directory structure @@ -2010,6 +1992,10 @@ func (p *LocalPeer) GetPeerURL() string { return fmt.Sprintf("grpcs://%s", p.opts.ExternalEndpoint) } +func (p *LocalPeer) GetPeerAddress() string { + return p.opts.ExternalEndpoint +} + func (p *LocalPeer) GetTLSRootCACert(ctx context.Context) (string, error) { tlsCAKeyDB, err := p.keyService.GetKey(ctx, int(p.org.TlsRootKeyID.Int64)) if err != nil { @@ -2036,58 +2022,146 @@ type SaveChannelConfigResponse struct { TransactionID string } -func (p *LocalPeer) SaveChannelConfig(ctx context.Context, channelID string, ordererUrl string, ordererTlsCACert string, channelData []byte) (*SaveChannelConfigResponse, error) { - peerUrl := p.GetPeerURL() - tlsCACert, err := p.GetTLSRootCACert(ctx) +func (p *LocalPeer) SaveChannelConfig(ctx context.Context, channelID string, ordererUrl string, ordererTlsCACert string, channelData *cb.Envelope) (*SaveChannelConfigResponse, error) { + adminIdentity, err := p.GetAdminIdentity(ctx) if err != nil { - return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + return nil, fmt.Errorf("failed to get admin identity: %w", err) } - - networkConfig, err := p.generateNetworkConfigForPeer( - peerUrl, - p.mspID, - tlsCACert, - ordererUrl, - ordererTlsCACert, - ) + envelope, err := SignConfigTx(channelID, channelData, adminIdentity) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to set anchor peers: %w", err) } - configBackend := config.FromRaw([]byte(networkConfig.NetworkConfig), "yaml") - sdk, err := fabsdk.New(configBackend) + ordererConn, err := p.CreateOrdererConnection(ctx, ordererUrl, ordererTlsCACert) if err != nil { - return nil, fmt.Errorf("failed to create sdk: %w", err) + return nil, fmt.Errorf("failed to create orderer connection: %w", err) } - defer sdk.Close() - adminIdentity, err := p.GetAdminIdentity(ctx, sdk) + defer ordererConn.Close() + ordererClient, err := orderer.NewAtomicBroadcastClient(ordererConn).Broadcast(context.Background()) if err != nil { - return nil, fmt.Errorf("failed to get admin identity: %w", err) + return nil, fmt.Errorf("failed to create orderer client: %w", err) } - sdkContext := sdk.Context( - fabsdk.WithIdentity(adminIdentity), - fabsdk.WithOrg(p.mspID), - ) - resClient, err := resmgmt.New(sdkContext) + err = ordererClient.Send(envelope) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return nil, fmt.Errorf("failed to send envelope: %w", err) } - configUpdateReader := bytes.NewReader(channelData) - chResponse, err := resClient.SaveChannel(resmgmt.SaveChannelRequest{ - ChannelID: channelID, - ChannelConfig: configUpdateReader, - }) + response, err := ordererClient.Recv() if err != nil { - return nil, fmt.Errorf("failed to save channel config: %w", err) + return nil, fmt.Errorf("failed to receive response: %w", err) } return &SaveChannelConfigResponse{ - TransactionID: string(chResponse.TransactionID), + TransactionID: response.String(), }, nil } +// SaveChannelConfigResponse contains the transaction ID of the saved channel configuration + +// CreateOrdererConnection establishes a gRPC connection to an orderer +func (p *LocalPeer) CreateOrdererConnection(ctx context.Context, ordererURL string, ordererTLSCACert string) (*grpc.ClientConn, error) { + p.logger.Info("Creating orderer connection", + "ordererURL", ordererURL) + + // Create a network node with the orderer details + networkNode := network.Node{ + Addr: ordererURL, + TLSCACertByte: []byte(ordererTLSCACert), + } + + // Establish connection to the orderer + ordererConn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial orderer connection: %w", err) + } + + return ordererConn, nil +} + +const ( + msgVersion = int32(0) + epoch = 0 +) + +func SignConfigTx(channelID string, envConfigUpdate *cb.Envelope, signer identity.SigningIdentity) (*cb.Envelope, error) { + payload, err := protoutil.UnmarshalPayload(envConfigUpdate.Payload) + if err != nil { + return nil, errors.New("bad payload") + } + + if payload.Header == nil || payload.Header.ChannelHeader == nil { + return nil, errors.New("bad header") + } + + ch, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) + if err != nil { + return nil, errors.New("could not unmarshall channel header") + } + + if ch.Type != int32(cb.HeaderType_CONFIG_UPDATE) { + return nil, errors.New("bad type") + } + + if ch.ChannelId == "" { + return nil, errors.New("empty channel id") + } + + configUpdateEnv, err := protoutil.UnmarshalConfigUpdateEnvelope(payload.Data) + if err != nil { + return nil, errors.New("bad config update env") + } + + sigHeader, err := protoutil.NewSignatureHeader(signer) + if err != nil { + return nil, err + } + + configSig := &cb.ConfigSignature{ + SignatureHeader: protoutil.MarshalOrPanic(sigHeader), + } + + configSig.Signature, err = signer.Sign(Concatenate(configSig.SignatureHeader, configUpdateEnv.ConfigUpdate)) + if err != nil { + return nil, err + } + + configUpdateEnv.Signatures = append(configUpdateEnv.Signatures, configSig) + + return protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, signer, configUpdateEnv, msgVersion, epoch) +} + +func Concatenate[T any](slices ...[]T) []T { + size := 0 + for _, slice := range slices { + size += len(slice) + } + + result := make([]T, size) + i := 0 + for _, slice := range slices { + copy(result[i:], slice) + i += len(slice) + } + + return result +} + +// CreatePeerConnection establishes a gRPC connection to a peer +func (p *LocalPeer) CreatePeerConnection(ctx context.Context, peerURL string, peerTLSCACert string) (*grpc.ClientConn, error) { + // Create a temporary file for the TLS CA certificate + + networkNode := network.Node{ + Addr: peerURL, + TLSCACertByte: []byte(peerTLSCACert), + } + peerConn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial peer connection: %w", err) + } + return peerConn, nil +} + func (p *LocalPeer) GetMSPID() string { return p.mspID } -func (p *LocalPeer) GetAdminIdentity(ctx context.Context, sdk *fabsdk.FabricSDK) (msp.SigningIdentity, error) { +func (p *LocalPeer) GetAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { adminSignKeyDB, err := p.keyService.GetKey(ctx, int(p.org.AdminSignKeyID.Int64)) if err != nil { return nil, fmt.Errorf("failed to get TLS CA key: %w", err) @@ -2100,32 +2174,22 @@ func (p *LocalPeer) GetAdminIdentity(ctx context.Context, sdk *fabsdk.FabricSDK) if err != nil { return nil, fmt.Errorf("failed to get decrypted private key: %w", err) } - sdkConfig, err := sdk.Config() - if err != nil { - return nil, fmt.Errorf("failed to get sdk config: %w", err) - } - cryptoConfig := cryptosuite.ConfigFromBackend(sdkConfig) - cryptoSuite, err := sw.GetSuiteByConfig(cryptoConfig) - if err != nil { - return nil, fmt.Errorf("failed to get crypto suite: %w", err) - } - userStore := mspimpl.NewMemoryUserStore() - endpointConfig, err := fab.ConfigFromBackend(sdkConfig) + + cert, err := gwidentity.CertificateFromPEM([]byte(certificate)) if err != nil { - return nil, fmt.Errorf("failed to get endpoint config: %w", err) + return nil, fmt.Errorf("failed to read certificate: %w", err) } - mspID := p.GetMSPID() - identityManager, err := mspimpl.NewIdentityManager(mspID, userStore, cryptoSuite, endpointConfig) + + priv, err := gwidentity.PrivateKeyFromPEM([]byte(privateKey)) if err != nil { - return nil, fmt.Errorf("failed to get identity manager: %w", err) + return nil, fmt.Errorf("failed to read private key: %w", err) } - signingIdentity, err := identityManager.CreateSigningIdentity( - msp.WithPrivateKey([]byte(privateKey)), - msp.WithCert([]byte(certificate)), - ) + + signingIdentity, err := identity.NewPrivateKeySigningIdentity(p.mspID, cert, priv) if err != nil { return nil, fmt.Errorf("failed to create signing identity: %w", err) } + return signingIdentity, nil } @@ -2141,58 +2205,55 @@ func (p *LocalPeer) GetChannelBlock(ctx context.Context, channelID string, order "channel", channelID, "ordererUrl", ordererUrl) - // Get peer URL and TLS cert - peerUrl := p.GetPeerURL() - tlsCACert, err := p.GetTLSRootCACert(ctx) + // Get admin identity + adminIdentity, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) + } + peerUrl := p.GetPeerAddress() + peerTLSCACert, err := p.GetTLSRootCACert(ctx) if err != nil { return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) } - - // Generate network config for SDK - networkConfig, err := p.generateNetworkConfigForPeer( - peerUrl, - p.mspID, - tlsCACert, - ordererUrl, - ordererTlsCACert, - ) + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, peerTLSCACert) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to create peer connection: %w", err) } - - // Initialize SDK with network config - configBackend := config.FromRaw([]byte(networkConfig.NetworkConfig), "yaml") - sdk, err := fabsdk.New(configBackend) + defer peerConn.Close() + // Fetch channel configuration + configBlock, err := channel.GetConfigBlock(ctx, peerConn, adminIdentity, channelID) if err != nil { - return nil, fmt.Errorf("failed to create sdk: %w", err) + return nil, fmt.Errorf("failed to query channel config: %w", err) } - defer sdk.Close() + return configBlock, nil - // Get admin identity - adminIdentity, err := p.GetAdminIdentity(ctx, sdk) +} + +// getOrdererTLSKeyPair creates a TLS key pair for secure communication with the orderer +func (p *LocalPeer) getOrdererTLSKeyPair(ctx context.Context, ordererTLSCert string) (tls.Certificate, error) { + // Get organization details + org, err := p.orgService.GetOrganizationByMspID(ctx, p.mspID) if err != nil { - return nil, fmt.Errorf("failed to get admin identity: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to get organization: %w", err) } - // Create SDK context with admin identity - sdkContext := sdk.Context( - fabsdk.WithIdentity(adminIdentity), - fabsdk.WithOrg(p.mspID), - ) + if !org.AdminSignKeyID.Valid { + return tls.Certificate{}, fmt.Errorf("organization has no admin sign key") + } - // Create resource management client - resClient, err := resmgmt.New(sdkContext) + // Get private key from key management service + privateKeyPEM, err := p.keyService.GetDecryptedPrivateKey(int(org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to get private key: %w", err) } - // Fetch channel configuration - configBlock, err := resClient.QueryConfigBlockFromOrderer(channelID) + // Parse the orderer TLS certificate + ordererTLSCertParsed, err := tls.X509KeyPair([]byte(ordererTLSCert), []byte(privateKeyPEM)) if err != nil { - return nil, fmt.Errorf("failed to query channel config: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to parse orderer TLS certificate: %w", err) } - return configBlock, nil + return ordererTLSCertParsed, nil } // Add this new method to the LocalPeer struct @@ -2204,7 +2265,7 @@ func (p *LocalPeer) GetChannelConfig(ctx context.Context, channelID string, orde return nil, fmt.Errorf("failed to query channel config: %w", err) } - cmnConfig, err := resource.ExtractConfigFromBlock(configBlock) + cmnConfig, err := ExtractConfigFromBlock(configBlock) if err != nil { return nil, fmt.Errorf("failed to extract config from block: %w", err) } @@ -2227,65 +2288,129 @@ func (p *LocalPeer) SaveChannelConfigWithSignatures( envelopeBytes []byte, signatures [][]byte, ) (*SaveChannelConfigWithSignaturesResponse, error) { - // Create a network config for the SDK - netConfig, err := p.generateNetworkConfigForPeer( - p.GetPeerURL(), - p.mspID, - "", // We don't need the peer TLS CA cert for this operation - ordererUrl, - ordererTlsCACert, - ) + var cbEnvelope *cb.Envelope + if err := proto.Unmarshal(envelopeBytes, cbEnvelope); err != nil { + return nil, fmt.Errorf("failed to unmarshal envelope: %w", err) + } + + signedEnvelope, err := protoutil.FormSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, cbEnvelope, signatures, 1, 0) + if err != nil { + return nil, fmt.Errorf("failed to form signed envelope: %w", err) + } + + ordererConn, err := p.CreateOrdererConnection(ctx, ordererUrl, ordererTlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create orderer connection: %w", err) + } + defer ordererConn.Close() + ordererClient, err := orderer.NewAtomicBroadcastClient(ordererConn).Broadcast(context.Background()) if err != nil { - return nil, fmt.Errorf("failed to generate network config: %w", err) + return nil, fmt.Errorf("failed to create orderer client: %w", err) } - // Initialize the SDK with the network config - configProvider := config.FromRaw([]byte(netConfig.NetworkConfig), "yaml") - sdk, err := fabsdk.New(configProvider) + err = ordererClient.Send(signedEnvelope) + if err != nil { + return nil, fmt.Errorf("failed to save channel config with signatures: %w", err) + } + response, err := ordererClient.Recv() if err != nil { - return nil, fmt.Errorf("failed to create SDK: %w", err) + return nil, fmt.Errorf("failed to receive response: %w", err) } - defer sdk.Close() + return &SaveChannelConfigWithSignaturesResponse{ + TransactionID: response.String(), + }, nil +} + +type PeerChannel struct { + Name string `json:"name"` + BlockNum int64 `json:"blockNum"` + CreatedAt time.Time `json:"createdAt"` +} - // Get admin identity for signing - adminIdentity, err := p.GetAdminIdentity(ctx, sdk) +// GetChannels returns a list of channels the peer has joined +func (p *LocalPeer) GetChannels(ctx context.Context) ([]PeerChannel, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + } + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + adminIdentity, err := p.GetAdminIdentity(ctx) if err != nil { return nil, fmt.Errorf("failed to get admin identity: %w", err) } - // Create a resource management client - sdkContext := sdk.Context( - fabsdk.WithIdentity(adminIdentity), - fabsdk.WithOrg(p.mspID), - ) - resClient, err := resmgmt.New(sdkContext) + channelList, err := channel.ListChannelOnPeer(ctx, peerConn, adminIdentity) if err != nil { - return nil, fmt.Errorf("failed to create resmgmt client: %w", err) + return nil, fmt.Errorf("failed to list channels on peer: %w", err) } - var cbSignatures []*common.ConfigSignature - for _, sig := range signatures { - configSig := &common.ConfigSignature{} - if err := proto.Unmarshal(sig, configSig); err != nil { - return nil, fmt.Errorf("failed to unmarshal signature: %w", err) + channels := make([]PeerChannel, len(channelList)) + for i, channel := range channelList { + blockInfo, err := p.getChannelBlockInfo(ctx, channel.ChannelId) + if err != nil { + return nil, fmt.Errorf("failed to get block height for channel: %w", err) } + channels[i] = PeerChannel{ + Name: channel.ChannelId, + BlockNum: int64(blockInfo.Height), + CreatedAt: time.Now(), + } + } + return channels, nil +} - cbSignatures = append(cbSignatures, configSig) +// getChannelBlockInfo gets the current block height for a channel +func (p *LocalPeer) getChannelBlockInfo(ctx context.Context, channelID string) (*cb.BlockchainInfo, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) } - // Submit the config update to the orderer - configUpdateReader := bytes.NewReader(envelopeBytes) - chResponse, err := resClient.SaveChannel( - resmgmt.SaveChannelRequest{ - ChannelID: channelID, - ChannelConfig: configUpdateReader, - }, - resmgmt.WithConfigSignatures(cbSignatures...), - ) + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) if err != nil { - return nil, fmt.Errorf("failed to save channel config with signatures: %w", err) + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + adminIdentity, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) } - return &SaveChannelConfigWithSignaturesResponse{ - TransactionID: string(chResponse.TransactionID), - }, nil + // Query info for the channel + channelInfo, err := channel.GetBlockChainInfo(ctx, peerConn, adminIdentity, channelID) + if err != nil { + return nil, fmt.Errorf("failed to query channel info: %w", err) + } + + // Get the block number from the channel info + + return channelInfo, nil +} + +// ExtractConfigFromBlock extracts channel configuration from block +func ExtractConfigFromBlock(block *cb.Block) (*cb.Config, error) { + if block == nil || block.Data == nil || len(block.Data.Data) == 0 { + return nil, errors.New("invalid block") + } + blockPayload := block.Data.Data[0] + + envelope := &cb.Envelope{} + if err := proto.Unmarshal(blockPayload, envelope); err != nil { + return nil, err + } + payload := &cb.Payload{} + if err := proto.Unmarshal(envelope.Payload, payload); err != nil { + return nil, err + } + + cfgEnv := &cb.ConfigEnvelope{} + if err := proto.Unmarshal(payload.Data, cfgEnv); err != nil { + return nil, err + } + return cfgEnv.Config, nil } diff --git a/pkg/nodes/service/service.go b/pkg/nodes/service/service.go index d9835e9..c8ddd78 100644 --- a/pkg/nodes/service/service.go +++ b/pkg/nodes/service/service.go @@ -12,6 +12,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/chainlaunch/chainlaunch/pkg/db" "github.com/chainlaunch/chainlaunch/pkg/errors" @@ -2024,3 +2025,72 @@ func (s *NodeService) GetNodeWithConfig(ctx context.Context, id int64) (*Node, e func (s *NodeService) GetNodeForDeployment(ctx context.Context, id int64) (*Node, error) { return s.GetNodeWithConfig(ctx, id) } + +// Channel represents a Fabric channel +type Channel struct { + Name string `json:"name"` + BlockNum int64 `json:"blockNum"` + CreatedAt time.Time `json:"createdAt"` +} + +// GetNodeChannels retrieves the list of channels for a Fabric node +func (s *NodeService) GetNodeChannels(ctx context.Context, id int64) ([]Channel, error) { + // Get the node first + node, err := s.db.GetNode(ctx, id) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.NewNotFoundError("node not found", nil) + } + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Verify node type + nodeType := types.NodeType(node.NodeType.String) + if nodeType != types.NodeTypeFabricPeer && nodeType != types.NodeTypeFabricOrderer { + return nil, errors.NewValidationError("node is not a Fabric node", nil) + } + + switch nodeType { + case types.NodeTypeFabricPeer: + // Get peer instance + peer, err := s.GetFabricPeer(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get peer: %w", err) + } + peerChannels, err := peer.GetChannels(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get peer channels: %w", err) + } + channels := make([]Channel, len(peerChannels)) + for i, channel := range peerChannels { + channels[i] = Channel{ + Name: channel.Name, + BlockNum: channel.BlockNum, + CreatedAt: channel.CreatedAt, + } + } + return channels, nil + + case types.NodeTypeFabricOrderer: + // Get orderer instance + orderer, err := s.GetFabricOrderer(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get orderer: %w", err) + } + ordererChannels, err := orderer.GetChannels(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get orderer channels: %w", err) + } + channels := make([]Channel, len(ordererChannels)) + for i, channel := range ordererChannels { + channels[i] = Channel{ + Name: channel.Name, + BlockNum: channel.BlockNum, + CreatedAt: channel.CreatedAt, + } + } + return channels, nil + } + + return nil, fmt.Errorf("unsupported node type: %s", nodeType) +} diff --git a/pkg/nodes/types/channel.go b/pkg/nodes/types/channel.go new file mode 100644 index 0000000..5cd2cbb --- /dev/null +++ b/pkg/nodes/types/channel.go @@ -0,0 +1,10 @@ +package types + +import "time" + +// Channel represents a Fabric channel +type Channel struct { + Name string `json:"name"` + BlockNum int64 `json:"blockNum"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/pkg/nodes/types/deployment.go b/pkg/nodes/types/deployment.go index 33a3d07..2547ed7 100644 --- a/pkg/nodes/types/deployment.go +++ b/pkg/nodes/types/deployment.go @@ -157,6 +157,10 @@ func (c *FabricOrdererDeploymentConfig) GetURL() string { return fmt.Sprintf("grpcs://%s", c.ExternalEndpoint) } +func (c *FabricOrdererDeploymentConfig) GetAddress() string { + return c.ExternalEndpoint +} + func (c *FabricOrdererDeploymentConfig) GetMode() string { return c.Mode } func (c *FabricOrdererDeploymentConfig) Validate() error { if c.Mode != "service" && c.Mode != "docker" { diff --git a/web/src/components/nodes/FabricOrdererConfig.tsx b/web/src/components/nodes/FabricOrdererConfig.tsx index 495feef..d2fe835 100644 --- a/web/src/components/nodes/FabricOrdererConfig.tsx +++ b/web/src/components/nodes/FabricOrdererConfig.tsx @@ -2,6 +2,7 @@ import { ServiceFabricOrdererProperties } from '@/api/client' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' +import { Link } from 'react-router-dom' interface FabricOrdererConfigProps { config: ServiceFabricOrdererProperties @@ -23,8 +24,8 @@ export function FabricOrdererConfig({ config }: FabricOrdererConfigProps) {

Key IDs

-

Sign Key: {config.signKeyId}

-

TLS Key: {config.tlsKeyId}

+

Sign Key: {config.signKeyId}

+

TLS Key: {config.tlsKeyId}

diff --git a/web/src/components/nodes/FabricPeerConfig.tsx b/web/src/components/nodes/FabricPeerConfig.tsx index 8925006..b4a65ac 100644 --- a/web/src/components/nodes/FabricPeerConfig.tsx +++ b/web/src/components/nodes/FabricPeerConfig.tsx @@ -2,6 +2,7 @@ import { ServiceFabricPeerProperties } from '@/api/client' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' +import { Link } from 'react-router-dom' interface FabricPeerConfigProps { config: ServiceFabricPeerProperties @@ -23,8 +24,18 @@ export function FabricPeerConfig({ config }: FabricPeerConfigProps) {

Key IDs

-

Sign Key: {config.signKeyId}

-

TLS Key: {config.tlsKeyId}

+

+ Sign Key:{' '} + + {config.signKeyId} + +

+

+ TLS Key:{' '} + + {config.tlsKeyId} + +

From e0f150de77640317b68b77a0c44be5ff565aa3bc Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 19:52:53 +0200 Subject: [PATCH 05/31] Add a temporal replacement for more features for the fork of kfsoftware for fabric-admin-sdk Signed-off-by: dviejokfs --- go.mod | 10 ++++++---- go.sum | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 219f437..15b6583 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,11 @@ require ( gopkg.in/mail.v2 v2.3.1 ) -require github.com/hyperledger/fabric-sdk-go v1.0.0 +require ( + github.com/hyperledger/fabric-gateway v1.5.0 + github.com/hyperledger/fabric-sdk-go v1.0.0 + google.golang.org/grpc v1.71.0 +) require ( dario.cat/mergo v1.0.1 // indirect @@ -62,7 +66,6 @@ require ( github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect - github.com/hyperledger/fabric-gateway v1.5.0 // indirect github.com/hyperledger/fabric-lib-go v1.0.0 // indirect github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -114,7 +117,6 @@ require ( go.uber.org/multierr v1.10.0 // indirect golang.org/x/sys v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/grpc v1.71.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -145,4 +147,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/hyperledger/fabric-admin-sdk => /Users/davidviejo/github-libs/fabric-admin-sdk \ No newline at end of file +replace github.com/hyperledger/fabric-admin-sdk => github.com/kfsoftware/fabric-admin-sdk v0.0.0-20250405175109-fd063100bb3f diff --git a/go.sum b/go.sum index 79c11a9..129b07a 100644 --- a/go.sum +++ b/go.sum @@ -405,6 +405,7 @@ github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDW github.com/hyperledger/fabric-lib-go v1.1.2 h1:3eHwudGZC5Ex7go5UAzVKhpF34gypPZGfSZksBKLWvE= github.com/hyperledger/fabric-lib-go v1.1.2/go.mod h1:SHNCq8AB0VpHAmvJEtdbzabv6NNV1F48JdmDihasBjc= github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.3.0 h1:MXxy44WTMENOh5TI8+PCK2x6pMj47Go2vFRKDHB2PZs= github.com/hyperledger/fabric-protos-go v0.3.0/go.mod h1:WWnyWP40P2roPmmvxsUXSvVI/CF6vwY1K1UFidnKBys= @@ -414,6 +415,7 @@ github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+ github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 h1:sQ5qv8vQQfwewa1JlCiSCC8dLElmaU2/frLolpgibEY= github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7/go.mod h1:bJnwzfv03oZQeCc863pdGTDgf5nmCy6Za3RAE7d2XsQ= +github.com/hyperledger/fabric-sdk-go v1.0.0/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -451,6 +453,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kfsoftware/fabric-admin-sdk v0.0.0-20250405175109-fd063100bb3f h1:w8Fhi718VAjC5Snwjx+1swaSyXCYEMzs0+kQICsjHc0= +github.com/kfsoftware/fabric-admin-sdk v0.0.0-20250405175109-fd063100bb3f/go.mod h1:lg28l2L1QhpsdKTfGLMmz0Ug+ZTbJOX6nhM7YhzcVgE= github.com/kfsoftware/fabric-sdk-go v0.0.0-20240114221414-98466038585d h1:HcMV8Lve3QkZUIWYHP+rVIR4xtTdDPooj7Id0IdBj0o= github.com/kfsoftware/fabric-sdk-go v0.0.0-20240114221414-98466038585d/go.mod h1:JRplpKBeAvXjsBhOCCM/KvMRUbdDyhsAh80qbXzKc10= github.com/kfsoftware/fabric-sdk-go v0.0.0-20250318193343-db7cb6f42306 h1:1HeRlKS4qdrC26HAe8ZqRiuBUPiGFDY7taHuehyraRE= @@ -494,6 +498,7 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= @@ -604,6 +609,7 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -628,6 +634,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -647,6 +654,7 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -657,6 +665,7 @@ github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -700,6 +709,7 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= @@ -971,6 +981,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From a2835c951119547d02f3ca7415c86b49a594d8f0 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 20:02:20 +0200 Subject: [PATCH 06/31] Add Fabric node channels feature - Introduced a new API endpoint to retrieve channels for a specific Fabric node. - Added types for channel responses and errors in the types.gen.ts file. - Implemented a React component, FabricNodeChannels, to display the channels associated with a Fabric node. - Updated the NodeDetailPage to include a tab for viewing channels, enhancing the user interface for node management. This feature improves the visibility of channel information for Fabric nodes, allowing users to better manage their network configurations. Signed-off-by: dviejokfs --- .../api/client/@tanstack/react-query.gen.ts | 23 +++++++- web/src/api/client/sdk.gen.ts | 13 ++++- web/src/api/client/types.gen.ts | 49 ++++++++++++++++ .../components/nodes/FabricNodeChannels.tsx | 58 +++++++++++++++++++ web/src/pages/nodes/[id].tsx | 26 +++++++-- 5 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 web/src/components/nodes/FabricNodeChannels.tsx diff --git a/web/src/api/client/@tanstack/react-query.gen.ts b/web/src/api/client/@tanstack/react-query.gen.ts index b571eab..db36c1d 100644 --- a/web/src/api/client/@tanstack/react-query.gen.ts +++ b/web/src/api/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import type { Options } from '@hey-api/client-fetch'; import { queryOptions, type UseMutationOptions, type DefaultError, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; -import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; -import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; +import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; +import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; type QueryKey = [ Pick & { @@ -1702,6 +1702,25 @@ export const getNodesByIdOptions = (options: Options) => { }); }; +export const getNodesByIdChannelsQueryKey = (options: Options) => [ + createQueryKey('getNodesByIdChannels', options) +]; + +export const getNodesByIdChannelsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getNodesByIdChannels({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getNodesByIdChannelsQueryKey(options) + }); +}; + export const getNodesByIdEventsQueryKey = (options: Options) => [ createQueryKey('getNodesByIdEvents', options) ]; diff --git a/web/src/api/client/sdk.gen.ts b/web/src/api/client/sdk.gen.ts index 292423e..7a37a2e 100644 --- a/web/src/api/client/sdk.gen.ts +++ b/web/src/api/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; +import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; export const client = createClient(createConfig()); @@ -794,6 +794,17 @@ export const getNodesById = (options: Opti }); }; +/** + * Get channels for a Fabric node + * Retrieves all channels for a specific Fabric node + */ +export const getNodesByIdChannels = (options: Options) => { + return (options?.client ?? client).get({ + url: '/nodes/{id}/channels', + ...options + }); +}; + /** * Get node events * Get a paginated list of events for a specific node diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts index bd6d738..f45330d 100644 --- a/web/src/api/client/types.gen.ts +++ b/web/src/api/client/types.gen.ts @@ -171,6 +171,12 @@ export type HttpChannelConfigResponse = { name?: string; }; +export type HttpChannelResponse = { + blockNum?: number; + createdAt?: string; + name?: string; +}; + export type HttpConsenterConfig = { id: string; }; @@ -456,6 +462,11 @@ export type HttpNetworkResponse = { updatedAt?: string; }; +export type HttpNodeChannelsResponse = { + channels?: Array; + nodeId?: number; +}; + export type HttpNodeEventResponse = { created_at?: string; data?: unknown; @@ -3398,6 +3409,44 @@ export type GetNodesByIdResponses = { export type GetNodesByIdResponse = GetNodesByIdResponses[keyof GetNodesByIdResponses]; +export type GetNodesByIdChannelsData = { + body?: never; + path: { + /** + * Node ID + */ + id: number; + }; + query?: never; + url: '/nodes/{id}/channels'; +}; + +export type GetNodesByIdChannelsErrors = { + /** + * Validation error + */ + 400: ResponseErrorResponse; + /** + * Node not found + */ + 404: ResponseErrorResponse; + /** + * Internal server error + */ + 500: ResponseErrorResponse; +}; + +export type GetNodesByIdChannelsError = GetNodesByIdChannelsErrors[keyof GetNodesByIdChannelsErrors]; + +export type GetNodesByIdChannelsResponses = { + /** + * OK + */ + 200: HttpNodeChannelsResponse; +}; + +export type GetNodesByIdChannelsResponse = GetNodesByIdChannelsResponses[keyof GetNodesByIdChannelsResponses]; + export type GetNodesByIdEventsData = { body?: never; path: { diff --git a/web/src/components/nodes/FabricNodeChannels.tsx b/web/src/components/nodes/FabricNodeChannels.tsx new file mode 100644 index 0000000..54bb2e8 --- /dev/null +++ b/web/src/components/nodes/FabricNodeChannels.tsx @@ -0,0 +1,58 @@ +import { useQuery } from '@tanstack/react-query' +import { getNodesByIdChannelsOptions } from '@/api/client/@tanstack/react-query.gen' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { HttpChannelResponse } from '@/api/client/types.gen' + +interface FabricNodeChannelsProps { + nodeId: number +} + +export function FabricNodeChannels({ nodeId }: FabricNodeChannelsProps) { + const { data: channels, isLoading, error } = useQuery({ + ...getNodesByIdChannelsOptions({ + path: { id: nodeId }, + }), + }) + + if (isLoading) { + return
Loading channels...
+ } + + if (error) { + return
Error loading channels: {(error as any).message}
+ } + + if (!channels?.channels?.length) { + return ( + + + Channels + No channels found for this node + + + ) + } + + return ( + + + Channels + List of channels this node is part of + + +
+ {channels.channels.map((channel: HttpChannelResponse) => ( +
+
+

{channel.name}

+

Block Number: {channel.blockNum || 'N/A'}

+

Created: {channel.createdAt ? new Date(channel.createdAt).toLocaleString() : 'N/A'}

+
+
+ ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/pages/nodes/[id].tsx b/web/src/pages/nodes/[id].tsx index 6aadbdc..b5581bb 100644 --- a/web/src/pages/nodes/[id].tsx +++ b/web/src/pages/nodes/[id].tsx @@ -10,6 +10,7 @@ import { import { BesuNodeConfig } from '@/components/nodes/BesuNodeConfig' import { FabricOrdererConfig } from '@/components/nodes/FabricOrdererConfig' import { FabricPeerConfig } from '@/components/nodes/FabricPeerConfig' +import { FabricNodeChannels } from '@/components/nodes/FabricNodeChannels' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' @@ -22,7 +23,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { format } from 'date-fns/format' import { AlertCircle, CheckCircle2, Clock, Play, PlayCircle, RefreshCcw, RefreshCw, Square, StopCircle, XCircle } from 'lucide-react' import { useEffect, useRef, useState } from 'react' -import { useNavigate, useParams } from 'react-router-dom' +import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { toast } from 'sonner' interface DeploymentConfig { @@ -123,10 +124,20 @@ function getEventStatusColor(status: string) { export default function NodeDetailPage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() const [logs, setLogs] = useState('') const logsRef = useRef(null) const abortControllerRef = useRef(null) + // Get the active tab from URL or default to 'logs' + const activeTab = searchParams.get('tab') || 'logs' + + // Update URL when tab changes + const handleTabChange = (value: string) => { + searchParams.set('tab', value) + setSearchParams(searchParams) + } + const { data: node, isLoading, @@ -282,9 +293,11 @@ export default function NodeDetailPage() { } if (error) { - return
Error loading node: {(error as ResponseErrorResponse).error.message}
+ return
Error loading node: {(error as any).error.message}
+ } + if (!node) { + return
Node not found
} - return (
@@ -345,11 +358,12 @@ export default function NodeDetailPage() {
- + Logs Crypto Material Events + {isFabricNode(node) && Channels} @@ -443,6 +457,10 @@ export default function NodeDetailPage() { + + + {isFabricNode(node) && } +
) From 4829e3e7350e7c91ad493d2cfbe9cf64f09564d2 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 20:39:54 +0200 Subject: [PATCH 07/31] Implement renewal of certificates in Fabric Signed-off-by: dviejokfs --- docs/docs.go | 178 +++++++++++------- docs/swagger.json | 176 +++++++++++------ docs/swagger.yaml | 147 +++++++++------ pkg/db/queries.sql | 3 +- pkg/db/queries.sql.go | 5 +- .../providers/database/provider.go | 1 + pkg/keymanagement/service/service.go | 92 +++++++++ pkg/networks/service/fabric/org/org.go | 3 + pkg/nodes/http/handler.go | 32 ++++ pkg/nodes/orderer/orderer.go | 142 ++++++++++++++ pkg/nodes/peer/peer.go | 140 +++++++++++++- pkg/nodes/service/service.go | 116 ++++++++++++ pkg/nodes/types/types.go | 1 + .../api/client/@tanstack/react-query.gen.ts | 37 +++- web/src/api/client/sdk.gen.ts | 13 +- web/src/api/client/types.gen.ts | 52 ++++- .../network-import/ImportNetworkForm.tsx | 12 +- web/src/pages/nodes/[id].tsx | 59 +++++- 18 files changed, 1006 insertions(+), 203 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 05800be..cfa41e5 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Package docs Code generated by swaggo/swag at 2025-04-04 10:59:35.206745 +0200 CEST m=+1.975204501. DO NOT EDIT +// Package docs Code generated by swaggo/swag at 2025-04-05 20:29:48.069943 +0200 CEST m=+1.706063459. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -3145,6 +3145,56 @@ const docTemplate = `{ } } }, + "/nodes/{id}/certificates/renew": { + "post": { + "description": "Renews the TLS and signing certificates for a Fabric node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Renew node certificates", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/nodes/{id}/channels": { "get": { "description": "Retrieves all channels for a specific Fabric node", @@ -4101,66 +4151,6 @@ const docTemplate = `{ } } }, - "crypto_x509.ExtKeyUsage": { - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13 - ], - "x-enum-varnames": [ - "ExtKeyUsageAny", - "ExtKeyUsageServerAuth", - "ExtKeyUsageClientAuth", - "ExtKeyUsageCodeSigning", - "ExtKeyUsageEmailProtection", - "ExtKeyUsageIPSECEndSystem", - "ExtKeyUsageIPSECTunnel", - "ExtKeyUsageIPSECUser", - "ExtKeyUsageTimeStamping", - "ExtKeyUsageOCSPSigning", - "ExtKeyUsageMicrosoftServerGatedCrypto", - "ExtKeyUsageNetscapeServerGatedCrypto", - "ExtKeyUsageMicrosoftCommercialCodeSigning", - "ExtKeyUsageMicrosoftKernelCodeSigning" - ] - }, - "crypto_x509.KeyUsage": { - "type": "integer", - "enum": [ - 1, - 2, - 4, - 8, - 16, - 32, - 64, - 128, - 256 - ], - "x-enum-varnames": [ - "KeyUsageDigitalSignature", - "KeyUsageContentCommitment", - "KeyUsageKeyEncipherment", - "KeyUsageDataEncipherment", - "KeyUsageKeyAgreement", - "KeyUsageCertSign", - "KeyUsageCRLSign", - "KeyUsageEncipherOnly", - "KeyUsageDecipherOnly" - ] - }, "github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse": { "type": "object", "properties": { @@ -5593,7 +5583,7 @@ const docTemplate = `{ "extKeyUsage": { "type": "array", "items": { - "$ref": "#/definitions/crypto_x509.ExtKeyUsage" + "$ref": "#/definitions/x509.ExtKeyUsage" } }, "ipAddresses": { @@ -5609,7 +5599,7 @@ const docTemplate = `{ "type": "boolean" }, "keyUsage": { - "$ref": "#/definitions/crypto_x509.KeyUsage" + "$ref": "#/definitions/x509.KeyUsage" }, "locality": { "type": "array", @@ -6505,6 +6495,7 @@ const docTemplate = `{ "STOPPED", "STOPPING", "STARTING", + "UPDATING", "ERROR" ], "x-enum-varnames": [ @@ -6513,6 +6504,7 @@ const docTemplate = `{ "NodeStatusStopped", "NodeStatusStopping", "NodeStatusStarting", + "NodeStatusUpdating", "NodeStatusError" ] }, @@ -6583,6 +6575,66 @@ const docTemplate = `{ }, "url.Userinfo": { "type": "object" + }, + "x509.ExtKeyUsage": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "x-enum-varnames": [ + "ExtKeyUsageAny", + "ExtKeyUsageServerAuth", + "ExtKeyUsageClientAuth", + "ExtKeyUsageCodeSigning", + "ExtKeyUsageEmailProtection", + "ExtKeyUsageIPSECEndSystem", + "ExtKeyUsageIPSECTunnel", + "ExtKeyUsageIPSECUser", + "ExtKeyUsageTimeStamping", + "ExtKeyUsageOCSPSigning", + "ExtKeyUsageMicrosoftServerGatedCrypto", + "ExtKeyUsageNetscapeServerGatedCrypto", + "ExtKeyUsageMicrosoftCommercialCodeSigning", + "ExtKeyUsageMicrosoftKernelCodeSigning" + ] + }, + "x509.KeyUsage": { + "type": "integer", + "enum": [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "x-enum-varnames": [ + "KeyUsageDigitalSignature", + "KeyUsageContentCommitment", + "KeyUsageKeyEncipherment", + "KeyUsageDataEncipherment", + "KeyUsageKeyAgreement", + "KeyUsageCertSign", + "KeyUsageCRLSign", + "KeyUsageEncipherOnly", + "KeyUsageDecipherOnly" + ] } }, "securityDefinitions": { diff --git a/docs/swagger.json b/docs/swagger.json index ce44d39..21323ac 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3143,6 +3143,56 @@ } } }, + "/nodes/{id}/certificates/renew": { + "post": { + "description": "Renews the TLS and signing certificates for a Fabric node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Renew node certificates", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/nodes/{id}/channels": { "get": { "description": "Retrieves all channels for a specific Fabric node", @@ -4099,66 +4149,6 @@ } } }, - "crypto_x509.ExtKeyUsage": { - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13 - ], - "x-enum-varnames": [ - "ExtKeyUsageAny", - "ExtKeyUsageServerAuth", - "ExtKeyUsageClientAuth", - "ExtKeyUsageCodeSigning", - "ExtKeyUsageEmailProtection", - "ExtKeyUsageIPSECEndSystem", - "ExtKeyUsageIPSECTunnel", - "ExtKeyUsageIPSECUser", - "ExtKeyUsageTimeStamping", - "ExtKeyUsageOCSPSigning", - "ExtKeyUsageMicrosoftServerGatedCrypto", - "ExtKeyUsageNetscapeServerGatedCrypto", - "ExtKeyUsageMicrosoftCommercialCodeSigning", - "ExtKeyUsageMicrosoftKernelCodeSigning" - ] - }, - "crypto_x509.KeyUsage": { - "type": "integer", - "enum": [ - 1, - 2, - 4, - 8, - 16, - 32, - 64, - 128, - 256 - ], - "x-enum-varnames": [ - "KeyUsageDigitalSignature", - "KeyUsageContentCommitment", - "KeyUsageKeyEncipherment", - "KeyUsageDataEncipherment", - "KeyUsageKeyAgreement", - "KeyUsageCertSign", - "KeyUsageCRLSign", - "KeyUsageEncipherOnly", - "KeyUsageDecipherOnly" - ] - }, "github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse": { "type": "object", "properties": { @@ -5591,7 +5581,7 @@ "extKeyUsage": { "type": "array", "items": { - "$ref": "#/definitions/crypto_x509.ExtKeyUsage" + "$ref": "#/definitions/x509.ExtKeyUsage" } }, "ipAddresses": { @@ -5607,7 +5597,7 @@ "type": "boolean" }, "keyUsage": { - "$ref": "#/definitions/crypto_x509.KeyUsage" + "$ref": "#/definitions/x509.KeyUsage" }, "locality": { "type": "array", @@ -6503,6 +6493,7 @@ "STOPPED", "STOPPING", "STARTING", + "UPDATING", "ERROR" ], "x-enum-varnames": [ @@ -6511,6 +6502,7 @@ "NodeStatusStopped", "NodeStatusStopping", "NodeStatusStarting", + "NodeStatusUpdating", "NodeStatusError" ] }, @@ -6581,6 +6573,66 @@ }, "url.Userinfo": { "type": "object" + }, + "x509.ExtKeyUsage": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "x-enum-varnames": [ + "ExtKeyUsageAny", + "ExtKeyUsageServerAuth", + "ExtKeyUsageClientAuth", + "ExtKeyUsageCodeSigning", + "ExtKeyUsageEmailProtection", + "ExtKeyUsageIPSECEndSystem", + "ExtKeyUsageIPSECTunnel", + "ExtKeyUsageIPSECUser", + "ExtKeyUsageTimeStamping", + "ExtKeyUsageOCSPSigning", + "ExtKeyUsageMicrosoftServerGatedCrypto", + "ExtKeyUsageNetscapeServerGatedCrypto", + "ExtKeyUsageMicrosoftCommercialCodeSigning", + "ExtKeyUsageMicrosoftKernelCodeSigning" + ] + }, + "x509.KeyUsage": { + "type": "integer", + "enum": [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "x-enum-varnames": [ + "KeyUsageDigitalSignature", + "KeyUsageContentCommitment", + "KeyUsageKeyEncipherment", + "KeyUsageDataEncipherment", + "KeyUsageKeyAgreement", + "KeyUsageCertSign", + "KeyUsageCRLSign", + "KeyUsageEncipherOnly", + "KeyUsageDecipherOnly" + ] } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e2596fe..85a73ad 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -44,60 +44,6 @@ definitions: @Example "admin" type: string type: object - crypto_x509.ExtKeyUsage: - enum: - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - 10 - - 11 - - 12 - - 13 - type: integer - x-enum-varnames: - - ExtKeyUsageAny - - ExtKeyUsageServerAuth - - ExtKeyUsageClientAuth - - ExtKeyUsageCodeSigning - - ExtKeyUsageEmailProtection - - ExtKeyUsageIPSECEndSystem - - ExtKeyUsageIPSECTunnel - - ExtKeyUsageIPSECUser - - ExtKeyUsageTimeStamping - - ExtKeyUsageOCSPSigning - - ExtKeyUsageMicrosoftServerGatedCrypto - - ExtKeyUsageNetscapeServerGatedCrypto - - ExtKeyUsageMicrosoftCommercialCodeSigning - - ExtKeyUsageMicrosoftKernelCodeSigning - crypto_x509.KeyUsage: - enum: - - 1 - - 2 - - 4 - - 8 - - 16 - - 32 - - 64 - - 128 - - 256 - type: integer - x-enum-varnames: - - KeyUsageDigitalSignature - - KeyUsageContentCommitment - - KeyUsageKeyEncipherment - - KeyUsageDataEncipherment - - KeyUsageKeyAgreement - - KeyUsageCertSign - - KeyUsageCRLSign - - KeyUsageEncipherOnly - - KeyUsageDecipherOnly github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse: properties: code: @@ -1114,7 +1060,7 @@ definitions: type: array extKeyUsage: items: - $ref: '#/definitions/crypto_x509.ExtKeyUsage' + $ref: '#/definitions/x509.ExtKeyUsage' type: array ipAddresses: items: @@ -1125,7 +1071,7 @@ definitions: isCA: type: boolean keyUsage: - $ref: '#/definitions/crypto_x509.KeyUsage' + $ref: '#/definitions/x509.KeyUsage' locality: items: type: string @@ -1758,6 +1704,7 @@ definitions: - STOPPED - STOPPING - STARTING + - UPDATING - ERROR type: string x-enum-varnames: @@ -1766,6 +1713,7 @@ definitions: - NodeStatusStopped - NodeStatusStopping - NodeStatusStarting + - NodeStatusUpdating - NodeStatusError types.NodeType: enum: @@ -1815,6 +1763,60 @@ definitions: type: object url.Userinfo: type: object + x509.ExtKeyUsage: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + type: integer + x-enum-varnames: + - ExtKeyUsageAny + - ExtKeyUsageServerAuth + - ExtKeyUsageClientAuth + - ExtKeyUsageCodeSigning + - ExtKeyUsageEmailProtection + - ExtKeyUsageIPSECEndSystem + - ExtKeyUsageIPSECTunnel + - ExtKeyUsageIPSECUser + - ExtKeyUsageTimeStamping + - ExtKeyUsageOCSPSigning + - ExtKeyUsageMicrosoftServerGatedCrypto + - ExtKeyUsageNetscapeServerGatedCrypto + - ExtKeyUsageMicrosoftCommercialCodeSigning + - ExtKeyUsageMicrosoftKernelCodeSigning + x509.KeyUsage: + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + - 128 + - 256 + type: integer + x-enum-varnames: + - KeyUsageDigitalSignature + - KeyUsageContentCommitment + - KeyUsageKeyEncipherment + - KeyUsageDataEncipherment + - KeyUsageKeyAgreement + - KeyUsageCertSign + - KeyUsageCRLSign + - KeyUsageEncipherOnly + - KeyUsageDecipherOnly host: localhost:8100 info: contact: @@ -3757,6 +3759,39 @@ paths: summary: Get a node tags: - nodes + /nodes/{id}/certificates/renew: + post: + consumes: + - application/json + description: Renews the TLS and signing certificates for a Fabric node + parameters: + - description: Node ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.NodeResponse' + "400": + description: Validation error + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Node not found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Renew node certificates + tags: + - nodes /nodes/{id}/channels: get: consumes: diff --git a/pkg/db/queries.sql b/pkg/db/queries.sql index 953ea94..abc1c01 100644 --- a/pkg/db/queries.sql +++ b/pkg/db/queries.sql @@ -207,7 +207,8 @@ SET name = ?, provider_id = ?, user_id = ?, ethereum_address = ?, - updated_at = CURRENT_TIMESTAMP + updated_at = CURRENT_TIMESTAMP, + signing_key_id = ? WHERE id = ? RETURNING *; diff --git a/pkg/db/queries.sql.go b/pkg/db/queries.sql.go index cece1cd..ac73a70 100644 --- a/pkg/db/queries.sql.go +++ b/pkg/db/queries.sql.go @@ -4022,7 +4022,8 @@ SET name = ?, provider_id = ?, user_id = ?, ethereum_address = ?, - updated_at = CURRENT_TIMESTAMP + updated_at = CURRENT_TIMESTAMP, + signing_key_id = ? WHERE id = ? RETURNING id, name, description, algorithm, key_size, curve, format, public_key, private_key, certificate, status, created_at, updated_at, expires_at, last_rotated_at, signing_key_id, sha256_fingerprint, sha1_fingerprint, provider_id, user_id, is_ca, ethereum_address ` @@ -4044,6 +4045,7 @@ type UpdateKeyParams struct { ProviderID int64 `json:"provider_id"` UserID int64 `json:"user_id"` EthereumAddress sql.NullString `json:"ethereum_address"` + SigningKeyID sql.NullInt64 `json:"signing_key_id"` ID int64 `json:"id"` } @@ -4065,6 +4067,7 @@ func (q *Queries) UpdateKey(ctx context.Context, arg UpdateKeyParams) (Key, erro arg.ProviderID, arg.UserID, arg.EthereumAddress, + arg.SigningKeyID, arg.ID, ) var i Key diff --git a/pkg/keymanagement/providers/database/provider.go b/pkg/keymanagement/providers/database/provider.go index c4a11f2..b42871c 100644 --- a/pkg/keymanagement/providers/database/provider.go +++ b/pkg/keymanagement/providers/database/provider.go @@ -789,6 +789,7 @@ func (p *DatabaseProvider) SignCertificate(ctx context.Context, req types.SignCe Sha1Fingerprint: key.Sha1Fingerprint, ProviderID: key.ProviderID, UserID: key.UserID, + SigningKeyID: sql.NullInt64{Int64: int64(req.CAKeyID), Valid: true}, } updatedKey, err := p.queries.UpdateKey(ctx, params) diff --git a/pkg/keymanagement/service/service.go b/pkg/keymanagement/service/service.go index 63a7902..a811b5b 100644 --- a/pkg/keymanagement/service/service.go +++ b/pkg/keymanagement/service/service.go @@ -608,3 +608,95 @@ type KeyInfo struct { KeyType string PublicKey string } + +// RenewCertificate renews a certificate using the same keypair and CA that was used to generate it +func (s *KeyManagementService) RenewCertificate(ctx context.Context, keyID int, certReq models.CertificateRequest) (*models.KeyResponse, error) { + // Get the key details + key, err := s.queries.GetKey(ctx, int64(keyID)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("key not found") + } + return nil, fmt.Errorf("failed to get key: %w", err) + } + + // Check if the key has a certificate + if !key.Certificate.Valid { + return nil, fmt.Errorf("key does not have a certificate to renew") + } + + // Get the CA key ID that was used to sign this certificate + if !key.SigningKeyID.Valid { + return nil, fmt.Errorf("key does not have an associated CA key") + } + caKeyID := int(key.SigningKeyID.Int64) + + // Validate that the CA key exists and is a CA + caKey, err := s.queries.GetKey(ctx, int64(caKeyID)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("CA key not found") + } + return nil, fmt.Errorf("failed to get CA key: %w", err) + } + + // Check if the CA key is marked as CA + if caKey.IsCa != 1 { + return nil, fmt.Errorf("key %d is not a CA", caKeyID) + } + + // Get provider + provider, err := s.providerFactory.GetProvider(providers.ProviderTypeDatabase) + if err != nil { + return nil, fmt.Errorf("failed to get provider: %w", err) + } + + // If no certificate request is provided, use the existing certificate's details + if certReq.CommonName == "" { + existingCert, err := parseCertificate(key.Certificate.String) + if err != nil { + return nil, fmt.Errorf("failed to parse existing certificate: %w", err) + } + + certReq = models.CertificateRequest{ + CommonName: existingCert.Subject.CommonName, + Organization: existingCert.Subject.Organization, + OrganizationalUnit: existingCert.Subject.OrganizationalUnit, + Country: existingCert.Subject.Country, + Province: existingCert.Subject.Province, + Locality: existingCert.Subject.Locality, + StreetAddress: existingCert.Subject.StreetAddress, + PostalCode: existingCert.Subject.PostalCode, + DNSNames: existingCert.DNSNames, + EmailAddresses: existingCert.EmailAddresses, + IPAddresses: existingCert.IPAddresses, + URIs: existingCert.URIs, + ValidFor: models.Duration(365 * 24 * time.Hour), + IsCA: existingCert.IsCA, + KeyUsage: x509.KeyUsage(existingCert.KeyUsage), + ExtKeyUsage: existingCert.ExtKeyUsage, + } + } + + // Sign the certificate with the same CA + return provider.SignCertificate(ctx, types.SignCertificateRequest{ + KeyID: keyID, + CAKeyID: caKeyID, + CertificateRequest: *ToProviderCertRequest(&certReq), + }) +} + +// Helper function to parse PEM certificate +func parseCertificate(certPEM string) (*x509.Certificate, error) { + block, _ := pem.Decode([]byte(certPEM)) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block containing certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + return cert, nil +} diff --git a/pkg/networks/service/fabric/org/org.go b/pkg/networks/service/fabric/org/org.go index 3a31c5a..ba4010f 100644 --- a/pkg/networks/service/fabric/org/org.go +++ b/pkg/networks/service/fabric/org/org.go @@ -114,6 +114,9 @@ func (s *FabricOrg) GetConfigBlockWithNetworkConfig(ctx context.Context, channel return ordererBlock, nil } + + + // getAdminIdentity retrieves the admin identity for the organization func (s *FabricOrg) getAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { // Get organization details diff --git a/pkg/nodes/http/handler.go b/pkg/nodes/http/handler.go index 75fb0ac..28bfb17 100644 --- a/pkg/nodes/http/handler.go +++ b/pkg/nodes/http/handler.go @@ -60,6 +60,7 @@ func (h *NodeHandler) RegisterRoutes(r chi.Router) { r.Get("/{id}/logs", h.TailLogs) r.Get("/{id}/events", response.Middleware(h.GetNodeEvents)) r.Get("/{id}/channels", response.Middleware(h.GetNodeChannels)) + r.Post("/{id}/certificates/renew", response.Middleware(h.RenewCertificates)) }) } @@ -693,3 +694,34 @@ func toNodeEventResponse(event service.NodeEvent) NodeEventResponse { CreatedAt: event.CreatedAt, } } + +// RenewCertificates godoc +// @Summary Renew node certificates +// @Description Renews the TLS and signing certificates for a Fabric node +// @Tags nodes +// @Accept json +// @Produce json +// @Param id path int true "Node ID" +// @Success 200 {object} NodeResponse +// @Failure 400 {object} response.ErrorResponse "Validation error" +// @Failure 404 {object} response.ErrorResponse "Node not found" +// @Failure 500 {object} response.ErrorResponse "Internal server error" +// @Router /nodes/{id}/certificates/renew [post] +func (h *NodeHandler) RenewCertificates(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + return errors.NewValidationError("invalid node ID", map[string]interface{}{ + "error": err.Error(), + }) + } + + node, err := h.service.RenewCertificates(r.Context(), id) + if err != nil { + if errors.IsType(err, errors.NotFoundError) { + return errors.NewNotFoundError("node not found", nil) + } + return errors.NewInternalError("failed to renew certificates", err, nil) + } + + return response.WriteJSON(w, http.StatusOK, toNodeResponse(node)) +} diff --git a/pkg/nodes/orderer/orderer.go b/pkg/nodes/orderer/orderer.go index a3e94c2..5dee795 100644 --- a/pkg/nodes/orderer/orderer.go +++ b/pkg/nodes/orderer/orderer.go @@ -1157,3 +1157,145 @@ func (o *LocalOrderer) GetChannels(ctx context.Context) ([]OrdererChannel, error return channels, nil } + +// RenewCertificates renews the orderer's TLS and signing certificates +func (o *LocalOrderer) RenewCertificates(ordererDeploymentConfig *types.FabricOrdererDeploymentConfig) error { + ctx := context.Background() + o.logger.Info("Starting certificate renewal for orderer", "ordererID", o.opts.ID) + + // Stop the orderer before renewing certificates + if err := o.Stop(); err != nil { + return fmt.Errorf("failed to stop orderer before certificate renewal: %w", err) + } + o.logger.Info("Successfully stopped orderer before certificate renewal") + + // Get organization details + org, err := o.orgService.GetOrganization(ctx, o.organizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } + + if ordererDeploymentConfig.SignKeyID == 0 || ordererDeploymentConfig.TLSKeyID == 0 { + return fmt.Errorf("orderer node does not have required key IDs") + } + + // Get the CA certificates + signCAKey, err := o.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get sign CA key: %w", err) + } + + tlsCAKey, err := o.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get TLS CA key: %w", err) + } + + // Renew signing certificate + validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity + signKeyDB, err := o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ + CommonName: o.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"orderer"}, + DNSNames: []string{o.opts.ID}, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew signing certificate: %w", err) + } + + // Renew TLS certificate + domainNames := o.opts.DomainNames + var ipAddresses []net.IP + var domains []string + + // Ensure localhost and 127.0.0.1 are included + hasLocalhost := false + hasLoopback := false + for _, domain := range domainNames { + if domain == "localhost" { + hasLocalhost = true + domains = append(domains, domain) + continue + } + if domain == "127.0.0.1" { + hasLoopback = true + ipAddresses = append(ipAddresses, net.ParseIP(domain)) + continue + } + if ip := net.ParseIP(domain); ip != nil { + ipAddresses = append(ipAddresses, ip) + } else { + domains = append(domains, domain) + } + } + if !hasLocalhost { + domains = append(domains, "localhost") + } + if !hasLoopback { + ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) + } + + tlsKeyDB, err := o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ + CommonName: o.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"orderer"}, + DNSNames: domains, + IPAddresses: ipAddresses, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew TLS certificate: %w", err) + } + + // Get the private keys + signKey, err := o.keyService.GetDecryptedPrivateKey(int(ordererDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + + tlsKey, err := o.keyService.GetDecryptedPrivateKey(int(ordererDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } + + // Update the certificates in the MSP directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + slugifiedID := strings.ReplaceAll(strings.ToLower(o.opts.ID), " ", "-") + dirPath := filepath.Join(homeDir, ".chainlaunch", "orderers", slugifiedID) + mspConfigPath := filepath.Join(dirPath, "config") + + err = o.writeCertificatesAndKeys( + mspConfigPath, + tlsKeyDB, + signKeyDB, + tlsKey, + signKey, + signCAKey, + tlsCAKey, + ) + if err != nil { + return fmt.Errorf("failed to write renewed certificates: %w", err) + } + + o.logger.Info("Successfully renewed orderer certificates", "ordererID", o.opts.ID) + o.logger.Info("Starting orderer after certificate renewal") + + // Start the orderer with renewed certificates + _, err = o.Start() + if err != nil { + return fmt.Errorf("failed to start orderer after certificate renewal: %w", err) + } + + o.logger.Info("Successfully started orderer after certificate renewal") + return nil +} diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index c80c446..cdceb19 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -590,9 +590,143 @@ func (p *LocalPeer) execSystemctl(command string, args ...string) error { return nil } -// RenewCertificates renews the peer's certificates -func (p *LocalPeer) RenewCertificates() error { - // Implementation details for certificate renewal +// RenewCertificates renews the peer's TLS and signing certificates +func (p *LocalPeer) RenewCertificates(peerDeploymentConfig *types.FabricPeerDeploymentConfig) error { + + ctx := context.Background() + p.logger.Info("Starting certificate renewal for peer", "peerID", p.opts.ID) + + // Get organization details + org, err := p.orgService.GetOrganization(ctx, p.organizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } + + if peerDeploymentConfig.SignKeyID == 0 || peerDeploymentConfig.TLSKeyID == 0 { + return fmt.Errorf("peer node does not have required key IDs") + } + + // Get the CA certificates + signCAKey, err := p.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get sign CA key: %w", err) + } + + tlsCAKey, err := p.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get TLS CA key: %w", err) + } + + // Renew signing certificate + validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity + signKeyDB, err := p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: []string{p.opts.ID}, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew signing certificate: %w", err) + } + + // Renew TLS certificate + domainNames := p.opts.DomainNames + var ipAddresses []net.IP + var domains []string + + // Ensure localhost and 127.0.0.1 are included + hasLocalhost := false + hasLoopback := false + for _, domain := range domainNames { + if domain == "localhost" { + hasLocalhost = true + domains = append(domains, domain) + continue + } + if domain == "127.0.0.1" { + hasLoopback = true + ipAddresses = append(ipAddresses, net.ParseIP(domain)) + continue + } + if ip := net.ParseIP(domain); ip != nil { + ipAddresses = append(ipAddresses, ip) + } else { + domains = append(domains, domain) + } + } + if !hasLocalhost { + domains = append(domains, "localhost") + } + if !hasLoopback { + ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) + } + + tlsKeyDB, err := p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: domains, + IPAddresses: ipAddresses, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew TLS certificate: %w", err) + } + + // Get the private keys + signKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + + tlsKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } + + // Update the certificates in the MSP directory + peerPath := p.getPeerPath() + mspConfigPath := filepath.Join(peerPath, "config") + + err = p.writeCertificatesAndKeys( + mspConfigPath, + tlsKeyDB, + signKeyDB, + tlsKey, + signKey, + signCAKey, + tlsCAKey, + ) + if err != nil { + return fmt.Errorf("failed to write renewed certificates: %w", err) + } + + // Restart the peer + _, err = p.Start() + if err != nil { + return fmt.Errorf("failed to restart peer after certificate renewal: %w", err) + } + + p.logger.Info("Successfully renewed peer certificates", "peerID", p.opts.ID) + p.logger.Info("Restarting peer after certificate renewal") + // Stop the peer before renewing certificates + if err := p.Stop(); err != nil { + return fmt.Errorf("failed to stop peer before certificate renewal: %w", err) + } + p.logger.Info("Successfully stopped peer before certificate renewal") + p.logger.Info("Starting peer after certificate renewal") + _, err = p.Start() + if err != nil { + return fmt.Errorf("failed to start peer after certificate renewal: %w", err) + } + p.logger.Info("Successfully started peer after certificate renewal") return nil } diff --git a/pkg/nodes/service/service.go b/pkg/nodes/service/service.go index c8ddd78..e6bfad4 100644 --- a/pkg/nodes/service/service.go +++ b/pkg/nodes/service/service.go @@ -2094,3 +2094,119 @@ func (s *NodeService) GetNodeChannels(ctx context.Context, id int64) ([]Channel, return nil, fmt.Errorf("unsupported node type: %s", nodeType) } + +// RenewCertificates renews the certificates for a node +func (s *NodeService) RenewCertificates(ctx context.Context, id int64) (*NodeResponse, error) { + // Get the node from database + node, err := s.db.GetNode(ctx, id) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.NewNotFoundError("node not found", nil) + } + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Update status to indicate certificate renewal is in progress + if err := s.updateNodeStatus(ctx, id, types.NodeStatusUpdating); err != nil { + return nil, fmt.Errorf("failed to update node status: %w", err) + } + + // Get deployment config + deploymentConfig, err := utils.DeserializeDeploymentConfig(node.DeploymentConfig.String) + if err != nil { + return nil, fmt.Errorf("failed to deserialize deployment config: %w", err) + } + + var renewErr error + switch types.NodeType(node.NodeType.String) { + case types.NodeTypeFabricPeer: + renewErr = s.renewPeerCertificates(ctx, node, deploymentConfig) + case types.NodeTypeFabricOrderer: + renewErr = s.renewOrdererCertificates(ctx, node, deploymentConfig) + default: + renewErr = fmt.Errorf("certificate renewal not supported for node type: %s", node.NodeType.String) + } + + if renewErr != nil { + // Update status to error if renewal failed + if err := s.updateNodeStatus(ctx, id, types.NodeStatusError); err != nil { + s.logger.Error("Failed to update node status after renewal error", "error", err) + } + return nil, fmt.Errorf("failed to renew certificates: %w", renewErr) + } + + // Update status to running after successful renewal + if err := s.updateNodeStatus(ctx, id, types.NodeStatusRunning); err != nil { + return nil, fmt.Errorf("failed to update node status: %w", err) + } + + // Get updated node + updatedNode, err := s.GetNode(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get updated node: %w", err) + } + + return updatedNode, nil +} + +// renewPeerCertificates handles certificate renewal for a Fabric peer +func (s *NodeService) renewPeerCertificates(ctx context.Context, dbNode db.Node, deploymentConfig types.NodeDeploymentConfig) error { + nodeConfig, err := utils.LoadNodeConfig([]byte(dbNode.NodeConfig.String)) + if err != nil { + return fmt.Errorf("failed to load node config: %w", err) + } + + peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) + if !ok { + return fmt.Errorf("invalid peer config type") + } + + peerDeployConfig, ok := deploymentConfig.(*types.FabricPeerDeploymentConfig) + if !ok { + return fmt.Errorf("invalid peer deployment config type") + } + + org, err := s.orgService.GetOrganization(ctx, peerConfig.OrganizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } + + localPeer := s.getPeerFromConfig(dbNode, org, peerConfig) + err = localPeer.RenewCertificates(peerDeployConfig) + if err != nil { + return fmt.Errorf("failed to renew peer certificates: %w", err) + } + + return nil +} + +// renewOrdererCertificates handles certificate renewal for a Fabric orderer +func (s *NodeService) renewOrdererCertificates(ctx context.Context, dbNode db.Node, deploymentConfig types.NodeDeploymentConfig) error { + nodeConfig, err := utils.LoadNodeConfig([]byte(dbNode.NodeConfig.String)) + if err != nil { + return fmt.Errorf("failed to load node config: %w", err) + } + + ordererConfig, ok := nodeConfig.(*types.FabricOrdererConfig) + if !ok { + return fmt.Errorf("invalid orderer config type") + } + + ordererDeployConfig, ok := deploymentConfig.(*types.FabricOrdererDeploymentConfig) + if !ok { + return fmt.Errorf("invalid orderer deployment config type") + } + + org, err := s.orgService.GetOrganization(ctx, ordererConfig.OrganizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } + + localOrderer := s.getOrdererFromConfig(dbNode, org, ordererConfig) + err = localOrderer.RenewCertificates(ordererDeployConfig) + if err != nil { + return fmt.Errorf("failed to renew orderer certificates: %w", err) + } + + return nil +} diff --git a/pkg/nodes/types/types.go b/pkg/nodes/types/types.go index 2e3b35b..93c72f4 100644 --- a/pkg/nodes/types/types.go +++ b/pkg/nodes/types/types.go @@ -38,6 +38,7 @@ const ( NodeStatusStopped NodeStatus = "STOPPED" NodeStatusStopping NodeStatus = "STOPPING" NodeStatusStarting NodeStatus = "STARTING" + NodeStatusUpdating NodeStatus = "UPDATING" NodeStatusError NodeStatus = "ERROR" ) diff --git a/web/src/api/client/@tanstack/react-query.gen.ts b/web/src/api/client/@tanstack/react-query.gen.ts index db36c1d..0ec74a1 100644 --- a/web/src/api/client/@tanstack/react-query.gen.ts +++ b/web/src/api/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import type { Options } from '@hey-api/client-fetch'; import { queryOptions, type UseMutationOptions, type DefaultError, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; -import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; -import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; +import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; +import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; type QueryKey = [ Pick & { @@ -1702,6 +1702,39 @@ export const getNodesByIdOptions = (options: Options) => { }); }; +export const postNodesByIdCertificatesRenewQueryKey = (options: Options) => [ + createQueryKey('postNodesByIdCertificatesRenew', options) +]; + +export const postNodesByIdCertificatesRenewOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await postNodesByIdCertificatesRenew({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: postNodesByIdCertificatesRenewQueryKey(options) + }); +}; + +export const postNodesByIdCertificatesRenewMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await postNodesByIdCertificatesRenew({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getNodesByIdChannelsQueryKey = (options: Options) => [ createQueryKey('getNodesByIdChannels', options) ]; diff --git a/web/src/api/client/sdk.gen.ts b/web/src/api/client/sdk.gen.ts index 7a37a2e..730cd26 100644 --- a/web/src/api/client/sdk.gen.ts +++ b/web/src/api/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; +import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; export const client = createClient(createConfig()); @@ -794,6 +794,17 @@ export const getNodesById = (options: Opti }); }; +/** + * Renew node certificates + * Renews the TLS and signing certificates for a Fabric node + */ +export const postNodesByIdCertificatesRenew = (options: Options) => { + return (options?.client ?? client).post({ + url: '/nodes/{id}/certificates/renew', + ...options + }); +}; + /** * Get channels for a Fabric node * Retrieves all channels for a specific Fabric node diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts index f45330d..fa9cbb5 100644 --- a/web/src/api/client/types.gen.ts +++ b/web/src/api/client/types.gen.ts @@ -48,10 +48,6 @@ export type AuthUserResponse = { username?: string; }; -export type CryptoX509ExtKeyUsage = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13; - -export type CryptoX509KeyUsage = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256; - export type GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse = { code?: number; error?: string; @@ -634,10 +630,10 @@ export type ModelsCertificateRequest = { country?: Array; dnsNames?: Array; emailAddresses?: Array; - extKeyUsage?: Array; + extKeyUsage?: Array; ipAddresses?: Array>; isCA?: boolean; - keyUsage?: CryptoX509KeyUsage; + keyUsage?: X509KeyUsage; locality?: Array; organization?: Array; organizationalUnit?: Array; @@ -998,7 +994,7 @@ export type TypesFabricPeerConfig = { version?: string; }; -export type TypesNodeStatus = 'PENDING' | 'RUNNING' | 'STOPPED' | 'STOPPING' | 'STARTING' | 'ERROR'; +export type TypesNodeStatus = 'PENDING' | 'RUNNING' | 'STOPPED' | 'STOPPING' | 'STARTING' | 'UPDATING' | 'ERROR'; export type TypesNodeType = 'FABRIC_PEER' | 'FABRIC_ORDERER' | 'BESU_FULLNODE'; @@ -1050,6 +1046,10 @@ export type UrlUserinfo = { [key: string]: unknown; }; +export type X509ExtKeyUsage = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13; + +export type X509KeyUsage = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256; + export type PostAuthLoginData = { /** * Login credentials @@ -3409,6 +3409,44 @@ export type GetNodesByIdResponses = { export type GetNodesByIdResponse = GetNodesByIdResponses[keyof GetNodesByIdResponses]; +export type PostNodesByIdCertificatesRenewData = { + body?: never; + path: { + /** + * Node ID + */ + id: number; + }; + query?: never; + url: '/nodes/{id}/certificates/renew'; +}; + +export type PostNodesByIdCertificatesRenewErrors = { + /** + * Validation error + */ + 400: ResponseErrorResponse; + /** + * Node not found + */ + 404: ResponseErrorResponse; + /** + * Internal server error + */ + 500: ResponseErrorResponse; +}; + +export type PostNodesByIdCertificatesRenewError = PostNodesByIdCertificatesRenewErrors[keyof PostNodesByIdCertificatesRenewErrors]; + +export type PostNodesByIdCertificatesRenewResponses = { + /** + * OK + */ + 200: HttpNodeResponse; +}; + +export type PostNodesByIdCertificatesRenewResponse = PostNodesByIdCertificatesRenewResponses[keyof PostNodesByIdCertificatesRenewResponses]; + export type GetNodesByIdChannelsData = { body?: never; path: { diff --git a/web/src/components/network-import/ImportNetworkForm.tsx b/web/src/components/network-import/ImportNetworkForm.tsx index c06b890..1dbd85a 100644 --- a/web/src/components/network-import/ImportNetworkForm.tsx +++ b/web/src/components/network-import/ImportNetworkForm.tsx @@ -60,7 +60,7 @@ type FormValues = z.infer export function ImportNetworkForm() { const [error, setError] = useState(null) const navigate = useNavigate() - const [fabricImportMethod, setFabricImportMethod] = useState<'genesis' | 'organization'>('genesis') + const [fabricImportMethod, setFabricImportMethod] = useState<'genesis' | 'organization'>('organization') const { data: organizations } = useQuery({ ...getOrganizationsOptions(), @@ -241,22 +241,22 @@ export function ImportNetworkForm() {
Import Method handleImportMethodChange(value as 'genesis' | 'organization')} className="flex flex-col space-y-1" > - + - Import using genesis block + Import using organization, orderer URL and TLS certificate - + - Import using organization, orderer URL and TLS certificate + Import using genesis block
diff --git a/web/src/pages/nodes/[id].tsx b/web/src/pages/nodes/[id].tsx index b5581bb..61a3f29 100644 --- a/web/src/pages/nodes/[id].tsx +++ b/web/src/pages/nodes/[id].tsx @@ -3,6 +3,7 @@ import { deleteNodesByIdMutation, getNodesByIdEventsOptions, getNodesByIdOptions, + postNodesByIdCertificatesRenewMutation, postNodesByIdRestartMutation, postNodesByIdStartMutation, postNodesByIdStopMutation, @@ -21,10 +22,11 @@ import { TimeAgo } from '@/components/ui/time-ago' import { cn } from '@/lib/utils' import { useMutation, useQuery } from '@tanstack/react-query' import { format } from 'date-fns/format' -import { AlertCircle, CheckCircle2, Clock, Play, PlayCircle, RefreshCcw, RefreshCw, Square, StopCircle, XCircle } from 'lucide-react' +import { AlertCircle, CheckCircle2, Clock, Play, PlayCircle, RefreshCcw, RefreshCw, Square, StopCircle, XCircle, KeyRound } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { toast } from 'sonner' +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' interface DeploymentConfig { type?: string @@ -128,6 +130,7 @@ export default function NodeDetailPage() { const [logs, setLogs] = useState('') const logsRef = useRef(null) const abortControllerRef = useRef(null) + const [showRenewCertDialog, setShowRenewCertDialog] = useState(false) // Get the active tab from URL or default to 'logs' const activeTab = searchParams.get('tab') || 'logs' @@ -193,6 +196,17 @@ export default function NodeDetailPage() { }, }) + const renewCertificates = useMutation({ + ...postNodesByIdCertificatesRenewMutation(), + onSuccess: () => { + toast.success('Certificates renewed successfully') + refetch() + }, + onError: (error: any) => { + toast.error(`Failed to renew certificates: ${error.message}`) + }, + }) + const { data: events, refetch: refetchEvents } = useQuery({ ...getNodesByIdEventsOptions({ path: { id: parseInt(id!) }, @@ -219,12 +233,27 @@ export default function NodeDetailPage() { case 'delete': await deleteNode.mutateAsync({ path: { id: node.id! } }) break + case 'renew-certificates': + setShowRenewCertDialog(true) + break } } catch (error) { // Error handling is done in the mutation callbacks } } + const handleRenewCertificates = async () => { + if (!node) return + try { + await renewCertificates.mutateAsync({ path: { id: node.id! } }) + refetchEvents() + refetch() + setShowRenewCertDialog(false) + } catch (error) { + // Error handling is done in the mutation callbacks + } + } + useEffect(() => { const fetchLogs = async () => { try { @@ -318,6 +347,17 @@ export default function NodeDetailPage() { {label} ))} + {isFabricNode(node) && ( + + )} @@ -462,6 +502,23 @@ export default function NodeDetailPage() { {isFabricNode(node) && } + + + + + Renew Certificates + + Are you sure you want to renew the certificates for this node? This will generate new TLS and signing certificates. + + + + Cancel + + Renew Certificates + + + + ) } From 26afc4f66bcfafc92c802c0e2abb4fecaff46f64 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 20:43:52 +0200 Subject: [PATCH 08/31] Fix tests Signed-off-by: dviejokfs --- internal/configtxgen/encoder/encoder.go | 612 ------------------ internal/configtxgen/genesisconfig/config.go | 517 --------------- internal/configtxgen/metadata/metadata.go | 25 - internal/configtxgen/viperutil/config_util.go | 505 --------------- 4 files changed, 1659 deletions(-) delete mode 100644 internal/configtxgen/encoder/encoder.go delete mode 100644 internal/configtxgen/genesisconfig/config.go delete mode 100644 internal/configtxgen/metadata/metadata.go delete mode 100644 internal/configtxgen/viperutil/config_util.go diff --git a/internal/configtxgen/encoder/encoder.go b/internal/configtxgen/encoder/encoder.go deleted file mode 100644 index 0f6153c..0000000 --- a/internal/configtxgen/encoder/encoder.go +++ /dev/null @@ -1,612 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package encoder - -import ( - "errors" - "fmt" - "os" - - icc "github.com/hyperledger/fabric-admin-sdk/internal/channelconfig" - "github.com/hyperledger/fabric-admin-sdk/internal/configtxgen/genesisconfig" - "github.com/hyperledger/fabric-admin-sdk/internal/configtxlator/update" - "github.com/hyperledger/fabric-admin-sdk/internal/genesis" - "github.com/hyperledger/fabric-admin-sdk/internal/msp" - ipc "github.com/hyperledger/fabric-admin-sdk/internal/policies" - ipd "github.com/hyperledger/fabric-admin-sdk/internal/policydsl" - "github.com/hyperledger/fabric-admin-sdk/internal/protoutil" - cb "github.com/hyperledger/fabric-protos-go-apiv2/common" - pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "google.golang.org/protobuf/proto" -) - -const ( - ordererAdminsPolicyName = "/Channel/Orderer/Admins" -) - -const ( - // ConsensusTypeSolo identifies the solo consensus implementation. - ConsensusTypeSolo = "solo" - // ConsensusTypeKafka identifies the Kafka-based consensus implementation. - ConsensusTypeKafka = "kafka" - // ConsensusTypeKafka identifies the Kafka-based consensus implementation. - ConsensusTypeEtcdRaft = "etcdraft" - ConsensusTypeBFT = "BFT" - - // BlockValidationPolicyKey TODO - BlockValidationPolicyKey = "BlockValidation" - - // OrdererAdminsPolicy is the absolute path to the orderer admins policy - OrdererAdminsPolicy = "/Channel/Orderer/Admins" - - // SignaturePolicyType is the 'Type' string for signature policies - SignaturePolicyType = "Signature" - - // ImplicitMetaPolicyType is the 'Type' string for implicit meta policies - ImplicitMetaPolicyType = "ImplicitMeta" -) - -func addValue(cg *cb.ConfigGroup, value icc.ConfigValue, modPolicy string) { - cg.Values[value.Key()] = &cb.ConfigValue{ - Value: protoutil.MarshalOrPanic(value.Value()), - ModPolicy: modPolicy, - } -} - -func addPolicy(cg *cb.ConfigGroup, policy ipc.ConfigPolicy, modPolicy string) { - cg.Policies[policy.Key()] = &cb.ConfigPolicy{ - Policy: policy.Value(), - ModPolicy: modPolicy, - } -} - -func AddOrdererPolicies(cg *cb.ConfigGroup, policyMap map[string]*genesisconfig.Policy, modPolicy string) error { - switch { - case policyMap == nil: - return errors.New("no policies defined") - case policyMap[BlockValidationPolicyKey] == nil: - return errors.New("no BlockValidation policy defined") - } - - return AddPolicies(cg, policyMap, modPolicy) -} - -func AddPolicies(cg *cb.ConfigGroup, policyMap map[string]*genesisconfig.Policy, modPolicy string) error { - switch { - case policyMap == nil: - return errors.New("no policies defined") - case policyMap[icc.AdminsPolicyKey] == nil: - return errors.New("no Admins policy defined") - case policyMap[icc.ReadersPolicyKey] == nil: - return errors.New("no Readers policy defined") - case policyMap[icc.WritersPolicyKey] == nil: - return errors.New("no Writers policy defined") - } - - for policyName, policy := range policyMap { - configPolicy, err := newConfigPolicy(policy, modPolicy) - if err != nil { - return err - } - - cg.Policies[policyName] = configPolicy - } - return nil -} - -func newConfigPolicy(policy *genesisconfig.Policy, modPolicy string) (*cb.ConfigPolicy, error) { - switch policy.Type { - case ImplicitMetaPolicyType: - imp, err := ipc.ImplicitMetaFromString(policy.Rule) - if err != nil { - return nil, fmt.Errorf("invalid implicit meta policy rule '%s' %w", policy.Rule, err) - } - result := &cb.ConfigPolicy{ - ModPolicy: modPolicy, - Policy: &cb.Policy{ - Type: int32(cb.Policy_IMPLICIT_META), - Value: protoutil.MarshalOrPanic(imp), - }, - } - return result, nil - case SignaturePolicyType: - sp, err := ipd.FromString(policy.Rule) - if err != nil { - return nil, fmt.Errorf("invalid signature policy rule '%s' %w", policy.Rule, err) - } - result := &cb.ConfigPolicy{ - ModPolicy: modPolicy, - Policy: &cb.Policy{ - Type: int32(cb.Policy_SIGNATURE), - Value: protoutil.MarshalOrPanic(sp), - }, - } - return result, nil - default: - return nil, fmt.Errorf("unknown policy type: %s", policy.Type) - } -} - -func NewConfigGroup() *cb.ConfigGroup { - return &cb.ConfigGroup{ - Groups: make(map[string]*cb.ConfigGroup), - Values: make(map[string]*cb.ConfigValue), - Policies: make(map[string]*cb.ConfigPolicy), - } -} - -// NewChannelGroup defines the root of the channel configuration. It defines basic operating principles like the hashing -// algorithm used for the blocks, as well as the location of the ordering service. It will recursively call into the -// NewOrdererGroup, NewConsortiumsGroup, and NewApplicationGroup depending on whether these sub-elements are set in the -// configuration. All mod_policy values are set to "Admins" for this group, with the exception of the OrdererAddresses -// value which is set to "/Channel/Orderer/Admins". -// -//nolint:cyclop -func NewChannelGroup(conf *genesisconfig.Profile) (*cb.ConfigGroup, error) { - channelGroup := NewConfigGroup() - if err := AddPolicies(channelGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to channel group %w", err) - } - - addValue(channelGroup, icc.HashingAlgorithmValue(), icc.AdminsPolicyKey) - addValue(channelGroup, icc.BlockDataHashingStructureValue(), icc.AdminsPolicyKey) - if conf.Orderer != nil && len(conf.Orderer.Addresses) > 0 { - addValue(channelGroup, icc.OrdererAddressesValue(conf.Orderer.Addresses), ordererAdminsPolicyName) - } - - if conf.Consortium != "" { - addValue(channelGroup, icc.ConsortiumValue(conf.Consortium), icc.AdminsPolicyKey) - } - - if len(conf.Capabilities) > 0 { - addValue(channelGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) - } - - var err error - if conf.Orderer != nil { - channelGroup.Groups[icc.OrdererGroupKey], err = NewOrdererGroup(conf.Orderer) - if err != nil { - return nil, fmt.Errorf("could not create orderer group %w", err) - } - } - - if conf.Application != nil { - channelGroup.Groups[icc.ApplicationGroupKey], err = NewApplicationGroup(conf.Application) - if err != nil { - return nil, fmt.Errorf("could not create application group %w", err) - } - } - - if conf.Consortiums != nil { - channelGroup.Groups[icc.ConsortiumsGroupKey], err = NewConsortiumsGroup(conf.Consortiums) - if err != nil { - return nil, fmt.Errorf("could not create consortiums group %w", err) - } - } - - channelGroup.ModPolicy = icc.AdminsPolicyKey - return channelGroup, nil -} - -// NewOrdererGroup returns the orderer component of the channel configuration. It defines parameters of the ordering service -// about how large blocks should be, how frequently they should be emitted, etc. as well as the organizations of the ordering network. -// It sets the mod_policy of all elements to "Admins". This group is always present in any channel configuration. -// -//nolint:cyclop -func NewOrdererGroup(conf *genesisconfig.Orderer) (*cb.ConfigGroup, error) { - ordererGroup := NewConfigGroup() - if err := AddOrdererPolicies(ordererGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to orderer group %w", err) - } - addValue(ordererGroup, icc.BatchSizeValue( - conf.BatchSize.MaxMessageCount, - conf.BatchSize.AbsoluteMaxBytes, - conf.BatchSize.PreferredMaxBytes, - ), icc.AdminsPolicyKey) - addValue(ordererGroup, icc.BatchTimeoutValue(conf.BatchTimeout.String()), icc.AdminsPolicyKey) - addValue(ordererGroup, icc.ChannelRestrictionsValue(conf.MaxChannels), icc.AdminsPolicyKey) - - if len(conf.Capabilities) > 0 { - addValue(ordererGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) - } - - var consensusMetadata []byte - var err error - - switch conf.OrdererType { - case ConsensusTypeSolo: - case ConsensusTypeEtcdRaft: - if consensusMetadata, err = icc.MarshalEtcdRaftMetadata(conf.EtcdRaft); err != nil { - return nil, fmt.Errorf("cannot marshal metadata for orderer type %s: %w", ConsensusTypeEtcdRaft, err) - } - case ConsensusTypeBFT: - consenterProtos, err := consenterProtosFromConfig(conf.ConsenterMapping) - if err != nil { - return nil, fmt.Errorf("cannot load consenter config for orderer type %s: %w", ConsensusTypeBFT, err) - } - addValue(ordererGroup, icc.OrderersValue(consenterProtos), icc.AdminsPolicyKey) - if consensusMetadata, err = icc.MarshalBFTOptions(conf.SmartBFT); err != nil { - return nil, fmt.Errorf("consenter options read failed with error %w for orderer type %s", err, ConsensusTypeBFT) - } - // Overwrite policy manually by computing it from the consenters - ipc.EncodeBFTBlockVerificationPolicy(consenterProtos, ordererGroup) - default: - return nil, fmt.Errorf("unknown orderer type: %s", conf.OrdererType) - } - - addValue(ordererGroup, icc.ConsensusTypeValue(conf.OrdererType, consensusMetadata), icc.AdminsPolicyKey) - - for _, org := range conf.Organizations { - var err error - ordererGroup.Groups[org.Name], err = NewOrdererOrgGroup(org) - if err != nil { - return nil, fmt.Errorf("failed to create orderer org %w", err) - } - } - - ordererGroup.ModPolicy = icc.AdminsPolicyKey - return ordererGroup, nil -} - -// NewConsortiumOrgGroup returns an org component of the channel configuration. It defines the crypto material for the -// organization (its MSP). It sets the mod_policy of all elements to "Admins". -func NewConsortiumOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { - consortiumsOrgGroup := NewConfigGroup() - consortiumsOrgGroup.ModPolicy = icc.AdminsPolicyKey - - if conf.SkipAsForeign { - return consortiumsOrgGroup, nil - } - - mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) - if err != nil { - return nil, fmt.Errorf("1 - Error loading MSP configuration for org: %s %w", conf.Name, err) - } - - if err := AddPolicies(consortiumsOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to consortiums org group '%s' %w", conf.Name, err) - } - - addValue(consortiumsOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) - - return consortiumsOrgGroup, nil -} - -// NewOrdererOrgGroup returns an orderer org component of the channel configuration. It defines the crypto material for the -// organization (its MSP). It sets the mod_policy of all elements to "Admins". -func NewOrdererOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { - ordererOrgGroup := NewConfigGroup() - ordererOrgGroup.ModPolicy = icc.AdminsPolicyKey - - if conf.SkipAsForeign { - return ordererOrgGroup, nil - } - - mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) - if err != nil { - return nil, fmt.Errorf("1 - Error loading MSP configuration for org: %s %w", conf.Name, err) - } - - if err := AddPolicies(ordererOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to orderer org group '%s' %w", conf.Name, err) - } - - addValue(ordererOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) - - if len(conf.OrdererEndpoints) > 0 { - addValue(ordererOrgGroup, icc.EndpointsValue(conf.OrdererEndpoints), icc.AdminsPolicyKey) - } - - return ordererOrgGroup, nil -} - -// NewApplicationGroup returns the application component of the channel configuration. It defines the organizations which are involved -// in application logic like chaincodes, and how these members may interact with the orderer. It sets the mod_policy of all elements to "Admins". -func NewApplicationGroup(conf *genesisconfig.Application) (*cb.ConfigGroup, error) { - applicationGroup := NewConfigGroup() - if err := AddPolicies(applicationGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to application group %w ", err) - } - - if len(conf.ACLs) > 0 { - addValue(applicationGroup, icc.ACLValues(conf.ACLs), icc.AdminsPolicyKey) - } - - if len(conf.Capabilities) > 0 { - addValue(applicationGroup, icc.CapabilitiesValue(conf.Capabilities), icc.AdminsPolicyKey) - } - - for _, org := range conf.Organizations { - var err error - applicationGroup.Groups[org.Name], err = NewApplicationOrgGroup(org) - if err != nil { - return nil, fmt.Errorf("failed to create application org %w", err) - } - } - - applicationGroup.ModPolicy = icc.AdminsPolicyKey - return applicationGroup, nil -} - -// NewApplicationOrgGroup returns an application org component of the channel configuration. It defines the crypto material for the organization -// (its MSP) as well as its anchor peers for use by the gossip network. It sets the mod_policy of all elements to "Admins". -func NewApplicationOrgGroup(conf *genesisconfig.Organization) (*cb.ConfigGroup, error) { - applicationOrgGroup := NewConfigGroup() - applicationOrgGroup.ModPolicy = icc.AdminsPolicyKey - - if conf.SkipAsForeign { - return applicationOrgGroup, nil - } - - mspConfig, err := msp.GetVerifyingMspConfig(conf.MSPDir, conf.ID, conf.MSPType) - if err != nil { - return nil, fmt.Errorf("1 - Error loading MSP configuration for org %s %w", conf.Name, err) - } - - if err := AddPolicies(applicationOrgGroup, conf.Policies, icc.AdminsPolicyKey); err != nil { - return nil, fmt.Errorf("error adding policies to application org group %s %w", conf.Name, err) - } - addValue(applicationOrgGroup, icc.MSPValue(mspConfig), icc.AdminsPolicyKey) - - var anchorProtos []*pb.AnchorPeer - for _, anchorPeer := range conf.AnchorPeers { - anchorProtos = append(anchorProtos, &pb.AnchorPeer{ - Host: anchorPeer.Host, - Port: anchorPeer.Port, - }) - } - - // Avoid adding an unnecessary anchor peers element when one is not required. This helps - // prevent a delta from the orderer system channel when computing more complex channel - // creation transactions - if len(anchorProtos) > 0 { - addValue(applicationOrgGroup, icc.AnchorPeersValue(anchorProtos), icc.AdminsPolicyKey) - } - - return applicationOrgGroup, nil -} - -// NewConsortiumsGroup returns the consortiums component of the channel configuration. This element is only defined for the ordering system channel. -// It sets the mod_policy for all elements to "/Channel/Orderer/Admins". -func NewConsortiumsGroup(conf map[string]*genesisconfig.Consortium) (*cb.ConfigGroup, error) { - consortiumsGroup := NewConfigGroup() - // This policy is not referenced anywhere, it is only used as part of the implicit meta policy rule at the channel level, so this setting - // effectively degrades control of the ordering system channel to the ordering admins - addPolicy(consortiumsGroup, ipc.SignaturePolicy(icc.AdminsPolicyKey, ipd.AcceptAllPolicy), ordererAdminsPolicyName) - - for consortiumName, consortium := range conf { - var err error - consortiumsGroup.Groups[consortiumName], err = NewConsortiumGroup(consortium) - if err != nil { - return nil, fmt.Errorf("failed to create consortium %s %w", consortiumName, err) - } - } - - consortiumsGroup.ModPolicy = ordererAdminsPolicyName - return consortiumsGroup, nil -} - -// NewConsortiumGroup returns a consortiums component of the channel configuration. Each consortium defines the organizations which may be involved in channel -// creation, as well as the channel creation policy the orderer checks at channel creation time to authorize the action. It sets the mod_policy of all -// elements to "/Channel/Orderer/Admins". -func NewConsortiumGroup(conf *genesisconfig.Consortium) (*cb.ConfigGroup, error) { - consortiumGroup := NewConfigGroup() - - for _, org := range conf.Organizations { - var err error - consortiumGroup.Groups[org.Name], err = NewConsortiumOrgGroup(org) - if err != nil { - return nil, fmt.Errorf("failed to create consortium org %w", err) - } - } - - addValue(consortiumGroup, icc.ChannelCreationPolicyValue(ipc.ImplicitMetaAnyPolicy(icc.AdminsPolicyKey).Value()), ordererAdminsPolicyName) - - consortiumGroup.ModPolicy = ordererAdminsPolicyName - return consortiumGroup, nil -} - -// NewChannelCreateConfigUpdate generates a ConfigUpdate which can be sent to the orderer to create a new channel. Optionally, the channel group of the -// ordering system channel may be passed in, and the resulting ConfigUpdate will extract the appropriate versions from this file. -func NewChannelCreateConfigUpdate(channelID string, conf *genesisconfig.Profile, templateConfig *cb.ConfigGroup) (*cb.ConfigUpdate, error) { - if conf.Application == nil { - return nil, errors.New("cannot define a new channel with no Application section") - } - - if conf.Consortium == "" { - return nil, errors.New("cannot define a new channel with no Consortium value") - } - - newChannelGroup, err := NewChannelGroup(conf) - if err != nil { - return nil, fmt.Errorf("could not turn parse profile into channel group %w", err) - } - - updt, err := update.Compute(&cb.Config{ChannelGroup: templateConfig}, &cb.Config{ChannelGroup: newChannelGroup}) - if err != nil { - return nil, fmt.Errorf("could not compute update %w", err) - } - - // Add the consortium name to create the channel for into the write set as required. - updt.ChannelId = channelID - updt.ReadSet.Values[icc.ConsortiumKey] = &cb.ConfigValue{Version: 0} - updt.WriteSet.Values[icc.ConsortiumKey] = &cb.ConfigValue{ - Version: 0, - Value: protoutil.MarshalOrPanic(&cb.Consortium{ - Name: conf.Consortium, - }), - } - - return updt, nil -} - -// DefaultConfigTemplate generates a config template based on the assumption that -// the input profile is a channel creation template and no system channel context -// is available. -func DefaultConfigTemplate(conf *genesisconfig.Profile) (*cb.ConfigGroup, error) { - channelGroup, err := NewChannelGroup(conf) - if err != nil { - return nil, fmt.Errorf("error parsing configuration %w", err) - } - - if _, ok := channelGroup.Groups[icc.ApplicationGroupKey]; !ok { - return nil, errors.New("channel template configs must contain an application section") - } - - channelGroup.Groups[icc.ApplicationGroupKey].Values = nil - channelGroup.Groups[icc.ApplicationGroupKey].Policies = nil - - return channelGroup, nil -} - -func ConfigTemplateFromGroup(conf *genesisconfig.Profile, cg *cb.ConfigGroup) (*cb.ConfigGroup, error) { - template := proto.Clone(cg).(*cb.ConfigGroup) - if template.Groups == nil { - return nil, errors.New("supplied system channel group has no sub-groups") - } - - template.Groups[icc.ApplicationGroupKey] = &cb.ConfigGroup{ - Groups: map[string]*cb.ConfigGroup{}, - Policies: map[string]*cb.ConfigPolicy{ - icc.AdminsPolicyKey: {}, - }, - } - - consortiums, ok := template.Groups[icc.ConsortiumsGroupKey] - if !ok { - return nil, errors.New("supplied system channel group does not appear to be system channel (missing consortiums group)") - } - - if consortiums.Groups == nil { - return nil, errors.New("system channel consortiums group appears to have no consortiums defined") - } - - consortium, ok := consortiums.Groups[conf.Consortium] - if !ok { - return nil, fmt.Errorf("supplied system channel group is missing '%s' consortium", conf.Consortium) - } - - if conf.Application == nil { - return nil, errors.New("supplied channel creation profile does not contain an application section") - } - - for _, organization := range conf.Application.Organizations { - var ok bool - template.Groups[icc.ApplicationGroupKey].Groups[organization.Name], ok = consortium.Groups[organization.Name] - if !ok { - return nil, fmt.Errorf("consortium %s does not contain member org %s", conf.Consortium, organization.Name) - } - } - delete(template.Groups, icc.ConsortiumsGroupKey) - - addValue(template, icc.ConsortiumValue(conf.Consortium), icc.AdminsPolicyKey) - - return template, nil -} - -// HasSkippedForeignOrgs is used to detect whether a configuration includes -// org definitions which should not be parsed because this tool is being -// run in a context where the user does not have access to that org's info -func HasSkippedForeignOrgs(conf *genesisconfig.Profile) error { - var organizations []*genesisconfig.Organization - - if conf.Orderer != nil { - organizations = append(organizations, conf.Orderer.Organizations...) - } - - if conf.Application != nil { - organizations = append(organizations, conf.Application.Organizations...) - } - - for _, consortium := range conf.Consortiums { - organizations = append(organizations, consortium.Organizations...) - } - - for _, org := range organizations { - if org.SkipAsForeign { - return fmt.Errorf("organization '%s' is marked to be skipped as foreign", org.Name) - } - } - - return nil -} - -// Bootstrapper is a wrapper around NewChannelConfigGroup which can produce genesis blocks -type Bootstrapper struct { - channelGroup *cb.ConfigGroup -} - -// NewBootstrapper creates a bootstrapper but returns an error instead of panic-ing -func NewBootstrapper(config *genesisconfig.Profile) (*Bootstrapper, error) { - if err := HasSkippedForeignOrgs(config); err != nil { - return nil, fmt.Errorf("all org definitions must be local during bootstrapping %w", err) - } - - channelGroup, err := NewChannelGroup(config) - if err != nil { - return nil, fmt.Errorf("could not create channel group %w", err) - } - - return &Bootstrapper{ - channelGroup: channelGroup, - }, nil -} - -// New creates a new Bootstrapper for generating genesis blocks -func New(config *genesisconfig.Profile) *Bootstrapper { - bs, err := NewBootstrapper(config) - if err != nil { - panic(err) - } - return bs -} - -// GenesisBlockForChannel produces a genesis block for a given channel ID -func (bs *Bootstrapper) GenesisBlockForChannel(channelID string) *cb.Block { - return genesis.NewFactoryImpl(bs.channelGroup).Block(channelID) -} - -//nolint:gocognit -func consenterProtosFromConfig(consenterMapping []*genesisconfig.Consenter) ([]*cb.Consenter, error) { - var consenterProtos []*cb.Consenter - for _, consenter := range consenterMapping { - c := &cb.Consenter{ - Id: consenter.ID, - Host: consenter.Host, - Port: consenter.Port, - MspId: consenter.MSPID, - } - // Expect the user to set the config value for client/server certs or identity to the - // path where they are persisted locally, then load these files to memory. - if consenter.ClientTLSCert != "" { - clientCert, err := os.ReadFile(consenter.ClientTLSCert) - if err != nil { - return nil, fmt.Errorf("cannot load client cert for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) - } - c.ClientTlsCert = clientCert - } - - if consenter.ServerTLSCert != "" { - serverCert, err := os.ReadFile(consenter.ServerTLSCert) - if err != nil { - return nil, fmt.Errorf("cannot load server cert for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) - } - c.ServerTlsCert = serverCert - } - - if consenter.Identity != "" { - identity, err := os.ReadFile(consenter.Identity) - if err != nil { - return nil, fmt.Errorf("cannot load identity for consenter %s:%d: %w", c.GetHost(), c.GetPort(), err) - } - c.Identity = identity - } - - consenterProtos = append(consenterProtos, c) - } - return consenterProtos, nil -} diff --git a/internal/configtxgen/genesisconfig/config.go b/internal/configtxgen/genesisconfig/config.go deleted file mode 100644 index e436693..0000000 --- a/internal/configtxgen/genesisconfig/config.go +++ /dev/null @@ -1,517 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package genesisconfig - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "path/filepath" - "sync" - "time" - - "github.com/hyperledger/fabric-admin-sdk/internal/configtxgen/viperutil" - "github.com/hyperledger/fabric-admin-sdk/internal/msp" - "github.com/hyperledger/fabric-protos-go-apiv2/orderer/smartbft" - - "github.com/hyperledger/fabric-protos-go-apiv2/orderer/etcdraft" -) - -const ( - // The type key for etcd based RAFT consensus. - EtcdRaft = "etcdraft" - BFT = "BFT" -) - -const ( - // SampleInsecureSoloProfile references the sample profile which does not - // include any MSPs and uses solo for ordering. - SampleInsecureSoloProfile = "SampleInsecureSolo" - // SampleDevModeSoloProfile references the sample profile which requires - // only basic membership for admin privileges and uses solo for ordering. - SampleDevModeSoloProfile = "SampleDevModeSolo" - // SampleSingleMSPSoloProfile references the sample profile which includes - // only the sample MSP and uses solo for ordering. - SampleSingleMSPSoloProfile = "SampleSingleMSPSolo" - - // SampleInsecureKafkaProfile references the sample profile which does not - // include any MSPs and uses Kafka for ordering. - SampleInsecureKafkaProfile = "SampleInsecureKafka" - // SampleDevModeKafkaProfile references the sample profile which requires only - // basic membership for admin privileges and uses Kafka for ordering. - SampleDevModeKafkaProfile = "SampleDevModeKafka" - // SampleSingleMSPKafkaProfile references the sample profile which includes - // only the sample MSP and uses Kafka for ordering. - SampleSingleMSPKafkaProfile = "SampleSingleMSPKafka" - - // SampleDevModeEtcdRaftProfile references the sample profile used for testing - // the etcd/raft-based ordering service. - SampleDevModeEtcdRaftProfile = "SampleDevModeEtcdRaft" - - // SampleAppChannelInsecureSoloProfile references the sample profile which - // does not include any MSPs and uses solo for ordering. - SampleAppChannelInsecureSoloProfile = "SampleAppChannelInsecureSolo" - // SampleApppChannelEtcdRaftProfile references the sample profile used for - // testing the etcd/raft-based ordering service using the channel - // participation API. - SampleAppChannelEtcdRaftProfile = "SampleAppChannelEtcdRaft" - - // SampleSingleMSPChannelProfile references the sample profile which - // includes only the sample MSP and is used to create a channel - SampleSingleMSPChannelProfile = "SampleSingleMSPChannel" - - // SampleConsortiumName is the sample consortium from the - // sample configtx.yaml - SampleConsortiumName = "SampleConsortium" - // SampleOrgName is the name of the sample org in the sample profiles - SampleOrgName = "SampleOrg" - - // AdminRoleAdminPrincipal is set as AdminRole to cause the MSP role of - // type Admin to be used as the admin principal default - AdminRoleAdminPrincipal = "Role.ADMIN" -) - -// TopLevel consists of the structs used by the configtxgen tool. -type TopLevel struct { - Profiles map[string]*Profile `yaml:"Profiles"` - Organizations []*Organization `yaml:"Organizations"` - Channel *Profile `yaml:"Channel"` - Application *Application `yaml:"Application"` - Orderer *Orderer `yaml:"Orderer"` - Capabilities map[string]map[string]bool `yaml:"Capabilities"` -} - -// Profile encodes orderer/application configuration combinations for the -// configtxgen tool. -type Profile struct { - Consortium string `yaml:"Consortium"` - Application *Application `yaml:"Application"` - Orderer *Orderer `yaml:"Orderer"` - Consortiums map[string]*Consortium `yaml:"Consortiums"` - Capabilities map[string]bool `yaml:"Capabilities"` - Policies map[string]*Policy `yaml:"Policies"` -} - -// Policy encodes a channel config policy -type Policy struct { - Type string `yaml:"Type"` - Rule string `yaml:"Rule"` -} - -// Consortium represents a group of organizations which may create channels -// with each other -type Consortium struct { - Organizations []*Organization `yaml:"Organizations"` -} - -// Application encodes the application-level configuration needed in config -// transactions. -type Application struct { - Organizations []*Organization `yaml:"Organizations"` - Capabilities map[string]bool `yaml:"Capabilities"` - Policies map[string]*Policy `yaml:"Policies"` - ACLs map[string]string `yaml:"ACLs"` -} - -// Organization encodes the organization-level configuration needed in -// config transactions. -type Organization struct { - Name string `yaml:"Name"` - ID string `yaml:"ID"` - MSPDir string `yaml:"MSPDir"` - MSPType string `yaml:"MSPType"` - Policies map[string]*Policy `yaml:"Policies"` - - // Note: Viper deserialization does not seem to care for - // embedding of types, so we use one organization struct - // for both orderers and applications. - AnchorPeers []*AnchorPeer `yaml:"AnchorPeers"` - OrdererEndpoints []string `yaml:"OrdererEndpoints"` - - // AdminPrincipal is deprecated and may be removed in a future release - // it was used for modifying the default policy generation, but policies - // may now be specified explicitly so it is redundant and unnecessary - AdminPrincipal string `yaml:"AdminPrincipal"` - - // SkipAsForeign indicates that this org definition is actually unknown to this - // instance of the tool, so, parsing of this org's parameters should be ignored. - SkipAsForeign bool -} - -// AnchorPeer encodes the necessary fields to identify an anchor peer. -type AnchorPeer struct { - Host string `yaml:"Host"` - Port int32 `yaml:"Port"` -} - -// Orderer contains configuration associated to a channel. -type Orderer struct { - OrdererType string `yaml:"OrdererType"` - Addresses []string `yaml:"Addresses"` - BatchTimeout time.Duration `yaml:"BatchTimeout"` - BatchSize BatchSize `yaml:"BatchSize"` - Kafka Kafka `yaml:"Kafka"` - ConsenterMapping []*Consenter `yaml:"ConsenterMapping"` - EtcdRaft *etcdraft.ConfigMetadata `yaml:"EtcdRaft"` - SmartBFT *smartbft.Options `yaml:"SmartBFT"` - Organizations []*Organization `yaml:"Organizations"` - MaxChannels uint64 `yaml:"MaxChannels"` - Capabilities map[string]bool `yaml:"Capabilities"` - Policies map[string]*Policy `yaml:"Policies"` -} - -type Consenter struct { - ID uint32 `yaml:"ID"` - Host string `yaml:"Host"` - Port uint32 `yaml:"Port"` - MSPID string `yaml:"MSPID"` - Identity string `yaml:"Identity"` - ClientTLSCert string `yaml:"ClientTLSCert"` - ServerTLSCert string `yaml:"ServerTLSCert"` -} - -// BatchSize contains configuration affecting the size of batches. -type BatchSize struct { - MaxMessageCount uint32 `yaml:"MaxMessageCount"` - AbsoluteMaxBytes uint32 `yaml:"AbsoluteMaxBytes"` - PreferredMaxBytes uint32 `yaml:"PreferredMaxBytes"` -} - -// Kafka contains configuration for the Kafka-based orderer. -type Kafka struct { - Brokers []string `yaml:"Brokers"` -} - -var genesisDefaults = TopLevel{ - Orderer: &Orderer{ - OrdererType: "solo", - BatchTimeout: 2 * time.Second, - BatchSize: BatchSize{ - MaxMessageCount: 500, - AbsoluteMaxBytes: 10 * 1024 * 1024, - PreferredMaxBytes: 2 * 1024 * 1024, - }, - Kafka: Kafka{ - Brokers: []string{"127.0.0.1:9092"}, - }, - EtcdRaft: &etcdraft.ConfigMetadata{ - Options: &etcdraft.Options{ - TickInterval: "500ms", - ElectionTick: 10, - HeartbeatTick: 1, - MaxInflightBlocks: 5, - SnapshotIntervalSize: 16 * 1024 * 1024, // 16 MB - }, - }, - }, -} - -// LoadTopLevel simply loads the configtx.yaml file into the structs above and -// completes their initialization. Config paths may optionally be provided and -// will be used in place of the FABRIC_CFG_PATH env variable. -// -// Note, for environment overrides to work properly within a profile, Load -// should be used instead. -func LoadTopLevel(configPaths ...string) *TopLevel { - config := viperutil.New() - config.AddConfigPaths(configPaths...) - config.SetConfigName("configtx") - - err := config.ReadInConfig() - if err != nil { - panic(fmt.Errorf("error reading configuration: %w", err)) - } - log.Printf("Using config file: %s", config.ConfigFileUsed()) - - uconf, err := cache.load(config, config.ConfigFileUsed()) - if err != nil { - panic(fmt.Errorf("error reading configuration: %w", err)) - } - uconf.completeInitialization(filepath.Dir(config.ConfigFileUsed())) - fmt.Printf("Loaded configuration: %s", config.ConfigFileUsed()) - - return uconf -} - -// Load returns the orderer/application config combination that corresponds to -// a given profile. Config paths may optionally be provided and will be used -// in place of the FABRIC_CFG_PATH env variable. -func Load(profile string, configPaths ...string) (*Profile, error) { - config := viperutil.New() - config.AddConfigPaths(configPaths...) - config.SetConfigName("configtx") - - fmt.Println(configPaths) - err := config.ReadInConfig() - if err != nil { - return nil, errors.New("Error reading configuration: " + err.Error()) - } - log.Printf("Using config file: %s", config.ConfigFileUsed()) - - uconf, err := cache.load(config, config.ConfigFileUsed()) - if err != nil { - panic(fmt.Errorf("error loading config from config cache: %w", err)) - } - - result, ok := uconf.Profiles[profile] - if !ok { - panic(fmt.Errorf("could not find profile: %s", profile)) - } - - result.completeInitialization(filepath.Dir(config.ConfigFileUsed())) - - log.Printf("Loaded configuration: %s", config.ConfigFileUsed()) - - return result, nil -} - -func (t *TopLevel) completeInitialization(configDir string) { - for _, org := range t.Organizations { - org.completeInitialization(configDir) - } - - if t.Orderer != nil { - t.Orderer.completeInitialization(configDir) - } -} - -func (p *Profile) completeInitialization(configDir string) { - if p.Application != nil { - for _, org := range p.Application.Organizations { - org.completeInitialization(configDir) - } - } - - if p.Consortiums != nil { - for _, consortium := range p.Consortiums { - for _, org := range consortium.Organizations { - org.completeInitialization(configDir) - } - } - } - - if p.Orderer != nil { - for _, org := range p.Orderer.Organizations { - org.completeInitialization(configDir) - } - // Some profiles will not define orderer parameters - p.Orderer.completeInitialization(configDir) - } -} - -func (org *Organization) completeInitialization(configDir string) { - // set the MSP type; if none is specified we assume BCCSP - if org.MSPType == "" { - org.MSPType = msp.ProviderTypeToString(msp.FABRIC) - } - - if org.AdminPrincipal == "" { - org.AdminPrincipal = AdminRoleAdminPrincipal - } - translatePaths(configDir, org) -} - -//nolint:cyclop,gocognit -func (ord *Orderer) completeInitialization(configDir string) { -loop: - for { - switch { - case ord.OrdererType == "": - log.Printf("Orderer.OrdererType unset, setting to %v", genesisDefaults.Orderer.OrdererType) - ord.OrdererType = genesisDefaults.Orderer.OrdererType - case ord.BatchTimeout == 0: - log.Printf("Orderer.BatchTimeout unset, setting to %s", genesisDefaults.Orderer.BatchTimeout) - ord.BatchTimeout = genesisDefaults.Orderer.BatchTimeout - case ord.BatchSize.MaxMessageCount == 0: - log.Printf("Orderer.BatchSize.MaxMessageCount unset, setting to %v", genesisDefaults.Orderer.BatchSize.MaxMessageCount) - ord.BatchSize.MaxMessageCount = genesisDefaults.Orderer.BatchSize.MaxMessageCount - case ord.BatchSize.AbsoluteMaxBytes == 0: - log.Printf("Orderer.BatchSize.AbsoluteMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes) - ord.BatchSize.AbsoluteMaxBytes = genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes - case ord.BatchSize.PreferredMaxBytes == 0: - log.Printf("Orderer.BatchSize.PreferredMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.PreferredMaxBytes) - ord.BatchSize.PreferredMaxBytes = genesisDefaults.Orderer.BatchSize.PreferredMaxBytes - default: - break loop - } - } - - log.Printf("orderer type: %s", ord.OrdererType) - // Additional, consensus type-dependent initialization goes here - // Also using this to panic on unknown orderer type. - switch ord.OrdererType { - case "solo": - // nothing to be done here - case "kafka": - if ord.Kafka.Brokers == nil { - log.Printf("Orderer.Kafka unset, setting to %v", genesisDefaults.Orderer.Kafka.Brokers) - ord.Kafka.Brokers = genesisDefaults.Orderer.Kafka.Brokers - } - case EtcdRaft: - if ord.EtcdRaft == nil { - log.Panicf("%s configuration missing", EtcdRaft) - } - if ord.EtcdRaft.Options == nil { - log.Printf("Orderer.EtcdRaft.Options unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options) - ord.EtcdRaft.Options = genesisDefaults.Orderer.EtcdRaft.Options - } - second_loop: - for { - switch { - case ord.EtcdRaft.Options.TickInterval == "": - log.Printf("Orderer.EtcdRaft.Options.TickInterval unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.TickInterval) - ord.EtcdRaft.Options.TickInterval = genesisDefaults.Orderer.EtcdRaft.Options.TickInterval - - case ord.EtcdRaft.Options.ElectionTick == 0: - log.Printf("Orderer.EtcdRaft.Options.ElectionTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick) - ord.EtcdRaft.Options.ElectionTick = genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick - - case ord.EtcdRaft.Options.HeartbeatTick == 0: - log.Printf("Orderer.EtcdRaft.Options.HeartbeatTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick) - ord.EtcdRaft.Options.HeartbeatTick = genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick - - case ord.EtcdRaft.Options.MaxInflightBlocks == 0: - log.Printf("Orderer.EtcdRaft.Options.MaxInflightBlocks unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks) - ord.EtcdRaft.Options.MaxInflightBlocks = genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks - - case ord.EtcdRaft.Options.SnapshotIntervalSize == 0: - log.Printf("Orderer.EtcdRaft.Options.SnapshotIntervalSize unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize) - ord.EtcdRaft.Options.SnapshotIntervalSize = genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize - - case len(ord.EtcdRaft.Consenters) == 0: - log.Panicf("%s configuration did not specify any consenter", EtcdRaft) - - default: - break second_loop - } - } - - if _, err := time.ParseDuration(ord.EtcdRaft.Options.TickInterval); err != nil { - log.Panicf("Etcdraft TickInterval (%s) must be in time duration format", ord.EtcdRaft.Options.TickInterval) - } - - // validate the specified members for Options - if ord.EtcdRaft.Options.ElectionTick <= ord.EtcdRaft.Options.HeartbeatTick { - log.Panicf("election tick must be greater than heartbeat tick") - } - - for _, c := range ord.EtcdRaft.GetConsenters() { - if c.Host == "" { - log.Panicf("consenter info in %s configuration did not specify host", EtcdRaft) - } - if c.Port == 0 { - log.Panicf("consenter info in %s configuration did not specify port", EtcdRaft) - } - if c.ClientTlsCert == nil { - log.Panicf("consenter info in %s configuration did not specify client TLS cert", EtcdRaft) - } - if c.ServerTlsCert == nil { - log.Panicf("consenter info in %s configuration did not specify server TLS cert", EtcdRaft) - } - clientCertPath := string(c.GetClientTlsCert()) - TranslatePathInPlace(configDir, &clientCertPath) - c.ClientTlsCert = []byte(clientCertPath) - serverCertPath := string(c.GetServerTlsCert()) - TranslatePathInPlace(configDir, &serverCertPath) - c.ServerTlsCert = []byte(serverCertPath) - } - case BFT: - if ord.SmartBFT == nil { - log.Printf("Orderer.SmartBFT.Options unset, setting to %v", genesisDefaults.Orderer.SmartBFT) - ord.SmartBFT = genesisDefaults.Orderer.SmartBFT - } - - if len(ord.ConsenterMapping) == 0 { - log.Panicf("%s configuration did not specify any consenter", BFT) - } - - for _, c := range ord.ConsenterMapping { - if c.Host == "" { - log.Panicf("consenter info in %s configuration did not specify host", BFT) - } - if c.Port == 0 { - log.Panicf("consenter info in %s configuration did not specify port", BFT) - } - if c.ClientTLSCert == "" { - log.Panicf("consenter info in %s configuration did not specify client TLS cert", BFT) - } - if c.ServerTLSCert == "" { - log.Panicf("consenter info in %s configuration did not specify server TLS cert", BFT) - } - if len(c.MSPID) == 0 { - log.Panicf("consenter info in %s configuration did not specify MSP ID", BFT) - } - if len(c.Identity) == 0 { - log.Panicf("consenter info in %s configuration did not specify identity certificate", BFT) - } - - c.ClientTLSCert = TranslatePath(configDir, c.ClientTLSCert) - c.ServerTLSCert = TranslatePath(configDir, c.ServerTLSCert) - c.Identity = TranslatePath(configDir, c.Identity) - } - default: - log.Panicf("unknown orderer type: %s", ord.OrdererType) - } -} - -func TranslatePathInPlace(base string, p *string) { - *p = TranslatePath(base, *p) -} - -func TranslatePath(base, p string) string { - if filepath.IsAbs(p) { - return p - } - - return filepath.Join(base, p) -} - -func translatePaths(configDir string, org *Organization) { - TranslatePathInPlace(configDir, &org.MSPDir) -} - -// configCache stores marshalled bytes of config structures that produced from -// EnhancedExactUnmarshal. Cache key is the path of the configuration file that was used. -type configCache struct { - mutex sync.Mutex - cache map[string][]byte -} - -var cache = &configCache{ - cache: make(map[string][]byte), -} - -// load loads the TopLevel config structure from configCache. -// if not successful, it unmarshal a config file, and populate configCache -// with marshaled TopLevel struct. -func (c *configCache) load(config *viperutil.ConfigParser, configPath string) (*TopLevel, error) { - c.mutex.Lock() - defer c.mutex.Unlock() - - conf := &TopLevel{} - serializedConf, ok := c.cache[configPath] - log.Printf("Loading configuration from cache: %t", ok) - if !ok { - err := config.EnhancedExactUnmarshal(conf) - if err != nil { - return nil, fmt.Errorf("error unmarshalling config into struct: %w", err) - } - - serializedConf, err = json.Marshal(conf) - if err != nil { - return nil, err - } - c.cache[configPath] = serializedConf - } - - err := json.Unmarshal(serializedConf, conf) - if err != nil { - return nil, err - } - return conf, nil -} diff --git a/internal/configtxgen/metadata/metadata.go b/internal/configtxgen/metadata/metadata.go deleted file mode 100644 index c2f9431..0000000 --- a/internal/configtxgen/metadata/metadata.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2017 Hitachi America - -SPDX-License-Identifier: Apache-2.0 -*/ - -package metadata - -import ( - "fmt" - "runtime" -) - -const ProgramName = "configtxgen" - -var ( - CommitSHA = "development build" - Version = "latest" -) - -func GetVersionInfo() string { - return fmt.Sprintf("%s:\n Version: %s\n Commit SHA: %s\n Go version: %s\n OS/Arch: %s", - ProgramName, Version, CommitSHA, runtime.Version(), - fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)) -} diff --git a/internal/configtxgen/viperutil/config_util.go b/internal/configtxgen/viperutil/config_util.go deleted file mode 100644 index c5d37c1..0000000 --- a/internal/configtxgen/viperutil/config_util.go +++ /dev/null @@ -1,505 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package viperutil - -import ( - "encoding/pem" - "fmt" - "io" - "log" - "math" - "os" - "path/filepath" - "reflect" - "regexp" - "strconv" - "strings" - - "github.com/IBM/sarama" - version "github.com/hashicorp/go-version" - "github.com/mitchellh/mapstructure" - "gopkg.in/yaml.v2" -) - -// ConfigPaths returns the paths from environment and -// defaults which are CWD and /etc/hyperledger/fabric. -func ConfigPaths() []string { - var paths []string - if p := os.Getenv("FABRIC_CFG_PATH"); p != "" { - paths = append(paths, p) - } - return append(paths, ".", "/etc/hyperledger/fabric") -} - -// ConfigParser holds the configuration file locations. -// It keeps the config file directory locations and env variables. -// From the file the config is unmarshalled and stored. -// Currently "yaml" is supported. -type ConfigParser struct { - // configuration file to process - configPaths []string - configName string - configFile string - - // parsed config - config map[string]interface{} -} - -// New creates a ConfigParser instance -func New() *ConfigParser { - return &ConfigParser{ - config: map[string]interface{}{}, - } -} - -// AddConfigPaths keeps a list of path to search the relevant -// config file. Multiple paths can be provided. -func (c *ConfigParser) AddConfigPaths(cfgPaths ...string) { - c.configPaths = append(c.configPaths, cfgPaths...) -} - -// SetConfigName provides the configuration file name stem. The upper-cased -// version of this value also serves as the environment variable override -// prefix. -func (c *ConfigParser) SetConfigName(in string) { - c.configName = in -} - -// ConfigFileUsed returns the used configFile. -func (c *ConfigParser) ConfigFileUsed() string { - return c.configFile -} - -// Search for the existence of filename for all supported extensions -func (c *ConfigParser) searchInPath(in string) (filename string) { - supportedExts := []string{"yaml", "yml"} - for _, ext := range supportedExts { - fullPath := filepath.Join(in, c.configName+"."+ext) - _, err := os.Stat(fullPath) - if err == nil { - return fullPath - } - } - return "" -} - -// Search for the configName in all configPaths -func (c *ConfigParser) findConfigFile() string { - paths := c.configPaths - if len(paths) == 0 { - paths = ConfigPaths() - } - for _, cp := range paths { - file := c.searchInPath(cp) - if file != "" { - return file - } - } - return "" -} - -// Get the valid and present config file -func (c *ConfigParser) getConfigFile() string { - // if explicitly set, then use it - if c.configFile != "" { - return c.configFile - } - - c.configFile = c.findConfigFile() - return c.configFile -} - -// ReadInConfig reads and unmarshals the config file. -func (c *ConfigParser) ReadInConfig() error { - cf := c.getConfigFile() - log.Printf("Attempting to open the config file: %s", cf) - file, err := os.Open(cf) - if err != nil { - log.Printf("Unable to open the config file: %s", cf) - return err - } - defer file.Close() - - return c.ReadConfig(file) -} - -// ReadConfig parses the buffer and initializes the config. -func (c *ConfigParser) ReadConfig(in io.Reader) error { - return yaml.NewDecoder(in).Decode(c.config) -} - -// Get value for the key by searching environment variables. -func (c *ConfigParser) getFromEnv(key string) string { - envKey := key - if c.configName != "" { - envKey = c.configName + "_" + envKey - } - envKey = strings.ToUpper(envKey) - envKey = strings.ReplaceAll(envKey, ".", "_") - return os.Getenv(envKey) -} - -// Prototype declaration for getFromEnv function. -type envGetter func(key string) string - -//nolint:cyclop,gocognit -func getKeysRecursively(base string, getenv envGetter, nodeKeys map[string]interface{}, oType reflect.Type) map[string]interface{} { - subTypes := map[string]reflect.Type{} - - if oType != nil && oType.Kind() == reflect.Struct { - outer: - for i := 0; i < oType.NumField(); i++ { - fieldName := oType.Field(i).Name - fieldType := oType.Field(i).Type - - for key := range nodeKeys { - if strings.EqualFold(fieldName, key) { - subTypes[key] = fieldType - continue outer - } - } - - subTypes[fieldName] = fieldType - nodeKeys[fieldName] = nil - } - } - - result := make(map[string]interface{}) - for key, val := range nodeKeys { - fqKey := base + key - - // overwrite val, if an environment is available - if override := getenv(fqKey); override != "" { - val = override - } - - switch val := val.(type) { - case map[string]interface{}: - log.Printf("Found map[string]interface{} value for %s", fqKey) - result[key] = getKeysRecursively(fqKey+".", getenv, val, subTypes[key]) - - case map[interface{}]interface{}: - log.Printf("Found map[interface{}]interface{} value for %s", fqKey) - result[key] = getKeysRecursively(fqKey+".", getenv, toMapStringInterface(val), subTypes[key]) - - case nil: - if override := getenv(fqKey + ".File"); override != "" { - result[key] = map[string]interface{}{"File": override} - } - - default: - result[key] = val - } - } - return result -} - -func toMapStringInterface(m map[interface{}]interface{}) map[string]interface{} { - result := map[string]interface{}{} - for k, v := range m { - k, ok := k.(string) - if !ok { - panic(fmt.Sprintf("Non string %v, %v: key-entry: %v", k, v, k)) - } - result[k] = v - } - return result -} - -// customDecodeHook parses strings of the format "[thing1, thing2, thing3]" -// into string slices. Note that whitespace around slice elements is removed. -func customDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - - raw := data.(string) - l := len(raw) - if l > 1 && raw[0] == '[' && raw[l-1] == ']' { - slice := strings.Split(raw[1:l-1], ",") - for i, v := range slice { - slice[i] = strings.TrimSpace(v) - } - return slice, nil - } - - return data, nil -} - -func byteSizeDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { - if f != reflect.String || t != reflect.Uint32 { - return data, nil - } - raw := data.(string) - if raw == "" { - return data, nil - } - re := regexp.MustCompile(`^(?P[0-9]+)\s*(?i)(?P(k|m|g))b?$`) - if re.MatchString(raw) { - size, err := strconv.ParseUint(re.ReplaceAllString(raw, "${size}"), 0, 64) - if err != nil { - return data, nil - } - unit := re.ReplaceAllString(raw, "${unit}") - switch strings.ToLower(unit) { - case "g": - size = size << 10 - fallthrough - case "m": - size = size << 10 - fallthrough - case "k": - size = size << 10 - } - if size > math.MaxUint32 { - return size, fmt.Errorf("value '%s' overflows uint32", raw) - } - return size, nil - } - return data, nil -} - -func stringFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { - // "to" type should be string - if t != reflect.String { - return data, nil - } - // "from" type should be map - if f != reflect.Map { - return data, nil - } - v := reflect.ValueOf(data) - switch v.Kind() { - case reflect.String: - return data, nil - case reflect.Map: - d := data.(map[string]interface{}) - fileName, ok := d["File"] - if !ok { - fileName, ok = d["file"] - } - switch { - case ok && fileName != nil: - bytes, err := os.ReadFile(fileName.(string)) - if err != nil { - return data, err - } - return string(bytes), nil - case ok: - // fileName was nil - return nil, fmt.Errorf("value of File: was nil") - } - } - return data, nil -} - -//nolint:cyclop,gocognit -func pemBlocksFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { - // "to" type should be string - if t != reflect.Slice { - return data, nil - } - // "from" type should be map - if f != reflect.Map { - return data, nil - } - v := reflect.ValueOf(data) - switch v.Kind() { - case reflect.String: - return data, nil - case reflect.Map: - var fileName string - var ok bool - switch d := data.(type) { - case map[string]string: - fileName, ok = d["File"] - if !ok { - fileName, ok = d["file"] - } - case map[string]interface{}: - var fileI interface{} - fileI, ok = d["File"] - if !ok { - fileI = d["file"] - } - fileName, ok = fileI.(string) - } - - switch { - case ok && fileName != "": - var result []string - bytes, err := os.ReadFile(fileName) - if err != nil { - return data, err - } - for len(bytes) > 0 { - var block *pem.Block - block, bytes = pem.Decode(bytes) - if block == nil { - break - } - if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - continue - } - result = append(result, string(pem.EncodeToMemory(block))) - } - return result, nil - case ok: - // fileName was nil - return nil, fmt.Errorf("value of File: was nil") - } - } - return data, nil -} - -var kafkaVersionConstraints map[sarama.KafkaVersion]version.Constraints - -func init() { - kafkaVersionConstraints = make(map[sarama.KafkaVersion]version.Constraints) - kafkaVersionConstraints[sarama.V0_8_2_0], _ = version.NewConstraint(">=0.8.2,<0.8.2.1") - kafkaVersionConstraints[sarama.V0_8_2_1], _ = version.NewConstraint(">=0.8.2.1,<0.8.2.2") - kafkaVersionConstraints[sarama.V0_8_2_2], _ = version.NewConstraint(">=0.8.2.2,<0.9.0.0") - kafkaVersionConstraints[sarama.V0_9_0_0], _ = version.NewConstraint(">=0.9.0.0,<0.9.0.1") - kafkaVersionConstraints[sarama.V0_9_0_1], _ = version.NewConstraint(">=0.9.0.1,<0.10.0.0") - kafkaVersionConstraints[sarama.V0_10_0_0], _ = version.NewConstraint(">=0.10.0.0,<0.10.0.1") - kafkaVersionConstraints[sarama.V0_10_0_1], _ = version.NewConstraint(">=0.10.0.1,<0.10.1.0") - kafkaVersionConstraints[sarama.V0_10_1_0], _ = version.NewConstraint(">=0.10.1.0,<0.10.2.0") - kafkaVersionConstraints[sarama.V0_10_2_0], _ = version.NewConstraint(">=0.10.2.0,<0.11.0.0") - kafkaVersionConstraints[sarama.V0_11_0_0], _ = version.NewConstraint(">=0.11.0.0,<1.0.0") - kafkaVersionConstraints[sarama.V1_0_0_0], _ = version.NewConstraint(">=1.0.0") -} - -func kafkaVersionDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if f.Kind() != reflect.String || t != reflect.TypeOf(sarama.KafkaVersion{}) { - return data, nil - } - - v, err := version.NewVersion(data.(string)) - if err != nil { - return nil, fmt.Errorf("unable to parse Kafka version: %w", err) - } - - for kafkaVersion, constraints := range kafkaVersionConstraints { - if constraints.Check(v) { - return kafkaVersion, nil - } - } - - return nil, fmt.Errorf("unsupported Kafka version: '%s'", data) -} - -// FactoryOpts holds configuration information used to initialize factory implementations -type FactoryOpts struct { - ProviderName string `mapstructure:"default" json:"default" yaml:"Default"` - SwOpts *SwOpts `mapstructure:"SW,omitempty" json:"SW,omitempty" yaml:"SwOpts"` -} - -// SwOpts contains options for the SWFactory -type SwOpts struct { - // Default algorithms when not specified (Deprecated?) - SecLevel int `mapstructure:"security" json:"security" yaml:"Security"` - HashFamily string `mapstructure:"hash" json:"hash" yaml:"Hash"` - - // Keystore Options - Ephemeral bool `mapstructure:"tempkeys,omitempty" json:"tempkeys,omitempty"` - FileKeystore *FileKeystoreOpts `mapstructure:"filekeystore,omitempty" json:"filekeystore,omitempty" yaml:"FileKeyStore"` - DummyKeystore *DummyKeystoreOpts `mapstructure:"dummykeystore,omitempty" json:"dummykeystore,omitempty"` - InmemKeystore *InmemKeystoreOpts `mapstructure:"inmemkeystore,omitempty" json:"inmemkeystore,omitempty"` -} - -type FileKeystoreOpts struct { - KeyStorePath string `mapstructure:"keystore" yaml:"KeyStore"` -} - -type DummyKeystoreOpts struct{} - -// InmemKeystoreOpts - empty, as there is no config for the in-memory keystore -type InmemKeystoreOpts struct{} - -// GetDefaultOpts offers a default implementation for Opts -// returns a new instance every time -func GetDefaultOpts() *FactoryOpts { - return &FactoryOpts{ - ProviderName: "SW", - SwOpts: &SwOpts{ - HashFamily: "SHA2", - SecLevel: 256, - Ephemeral: true, - }, - } -} - -func bccspHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if t != reflect.TypeOf(&FactoryOpts{}) { - return data, nil - } - - config := GetDefaultOpts() - - err := mapstructure.WeakDecode(data, config) - if err != nil { - return nil, fmt.Errorf("could not decode bccsp type %w", err) - } - - return config, nil -} - -// EnhancedExactUnmarshal is intended to unmarshal a config file into a structure -// producing error when extraneous variables are introduced and supporting -// the time.Duration type -func (c *ConfigParser) EnhancedExactUnmarshal(output interface{}) error { - oType := reflect.TypeOf(output) - if oType.Kind() != reflect.Ptr { - return fmt.Errorf("supplied output argument must be a pointer to a struct but is not pointer") - } - eType := oType.Elem() - if eType.Kind() != reflect.Struct { - return fmt.Errorf("supplied output argument must be a pointer to a struct, but it is pointer to something else") - } - - baseKeys := c.config - leafKeys := getKeysRecursively("", c.getFromEnv, baseKeys, eType) - - log.Printf("%+v", leafKeys) - config := &mapstructure.DecoderConfig{ - ErrorUnused: true, - Metadata: nil, - Result: output, - WeaklyTypedInput: true, - DecodeHook: mapstructure.ComposeDecodeHookFunc( - bccspHook, - mapstructure.StringToTimeDurationHookFunc(), - customDecodeHook, - byteSizeDecodeHook, - stringFromFileDecodeHook, - pemBlocksFromFileDecodeHook, - kafkaVersionDecodeHook, - ), - } - - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return err - } - return decoder.Decode(leafKeys) -} - -// YamlStringToStructHook is a hook for viper(viper.Unmarshal(*,*, here)), it is able to parse a string of minified yaml into a slice of structs -func YamlStringToStructHook(m interface{}) func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { - return func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { - if rf != reflect.String || rt != reflect.Slice { - return data, nil - } - - raw := data.(string) - if raw == "" { - return m, nil - } - - return m, yaml.UnmarshalStrict([]byte(raw), &m) - } -} From 9cc9e16a0a774568655a5641dd2666f8ee05755a Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 21:24:59 +0200 Subject: [PATCH 09/31] Update SigningKeyId when signing a certificate If SigningKeyId is not in the key when rotating certs in orderer/peers, set it Signed-off-by: dviejokfs --- pkg/keymanagement/models/models.go | 1 + pkg/keymanagement/service/service.go | 62 ++++++++++++++++++++++++++++ pkg/nodes/orderer/orderer.go | 30 +++++++++++++- pkg/nodes/peer/peer.go | 28 ++++++++++++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/pkg/keymanagement/models/models.go b/pkg/keymanagement/models/models.go index e2bb458..b2ef265 100644 --- a/pkg/keymanagement/models/models.go +++ b/pkg/keymanagement/models/models.go @@ -122,6 +122,7 @@ type KeyResponse struct { Provider KeyProviderInfo `json:"provider"` PrivateKey string `json:"privateKey"` EthereumAddress string `json:"ethereumAddress"` + SigningKeyID *int `json:"signingKeyID,omitempty"` } type KeyProviderInfo struct { diff --git a/pkg/keymanagement/service/service.go b/pkg/keymanagement/service/service.go index a811b5b..9cbfd8f 100644 --- a/pkg/keymanagement/service/service.go +++ b/pkg/keymanagement/service/service.go @@ -195,6 +195,7 @@ func (s *KeyManagementService) GetKey(ctx context.Context, id int) (*models.KeyR } keySize := int(key.KeySize.Int64) curve := models.ECCurve(key.Curve.String) + signingKeyID := int(key.SigningKeyID.Int64) return &models.KeyResponse{ ID: int(key.ID), Name: key.Name, @@ -217,6 +218,7 @@ func (s *KeyManagementService) GetKey(ctx context.Context, id int) (*models.KeyR }, PrivateKey: key.PrivateKey, EthereumAddress: key.EthereumAddress.String, + SigningKeyID: &signingKeyID, }, err } @@ -609,6 +611,66 @@ type KeyInfo struct { PublicKey string } +// SetSigningKeyIDForKey updates a key with the ID of the key that signed its certificate +func (s *KeyManagementService) SetSigningKeyIDForKey(ctx context.Context, keyID, signingKeyID int) error { + // Validate that both keys exist + key, err := s.queries.GetKey(ctx, int64(keyID)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("key not found") + } + return fmt.Errorf("failed to get key: %w", err) + } + + signingKey, err := s.queries.GetKey(ctx, int64(signingKeyID)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("signing key not found") + } + return fmt.Errorf("failed to get signing key: %w", err) + } + + // Verify that the signing key is a CA + if signingKey.IsCa != 1 { + return fmt.Errorf("signing key %d is not a CA", signingKeyID) + } + + // Verify that the key has a certificate + if !key.Certificate.Valid { + return fmt.Errorf("key %d does not have a certificate", keyID) + } + + // Update the key with the signing key ID + params := db.UpdateKeyParams{ + ID: key.ID, + Name: key.Name, + Description: key.Description, + Algorithm: key.Algorithm, + KeySize: key.KeySize, + Curve: key.Curve, + Format: key.Format, + PublicKey: key.PublicKey, + PrivateKey: key.PrivateKey, + Certificate: key.Certificate, + Status: key.Status, + ExpiresAt: key.ExpiresAt, + Sha256Fingerprint: key.Sha256Fingerprint, + Sha1Fingerprint: key.Sha1Fingerprint, + ProviderID: key.ProviderID, + UserID: key.UserID, + EthereumAddress: key.EthereumAddress, + SigningKeyID: sql.NullInt64{Int64: int64(signingKeyID), Valid: true}, + } + + _, err = s.queries.UpdateKey(ctx, params) + if err != nil { + return fmt.Errorf("failed to update key with signing key ID: %w", err) + } + + return nil +} + + // RenewCertificate renews a certificate using the same keypair and CA that was used to generate it func (s *KeyManagementService) RenewCertificate(ctx context.Context, keyID int, certReq models.CertificateRequest) (*models.KeyResponse, error) { // Get the key details diff --git a/pkg/nodes/orderer/orderer.go b/pkg/nodes/orderer/orderer.go index 5dee795..102da26 100644 --- a/pkg/nodes/orderer/orderer.go +++ b/pkg/nodes/orderer/orderer.go @@ -1190,9 +1190,35 @@ func (o *LocalOrderer) RenewCertificates(ordererDeploymentConfig *types.FabricOr return fmt.Errorf("failed to get TLS CA key: %w", err) } + // In case the sign key is not signed by the CA, set the signing key ID to the CA key ID + signKeyDB, err := o.keyService.GetKey(ctx, int(ordererDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + if signKeyDB.SigningKeyID == nil || *signKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = o.keyService.SetSigningKeyIDForKey(ctx, int(ordererDeploymentConfig.SignKeyID), int(signCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for sign key: %w", err) + } + } + + tlsKeyDB, err := o.keyService.GetKey(ctx, int(ordererDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } + + if tlsKeyDB.SigningKeyID == nil || *tlsKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = o.keyService.SetSigningKeyIDForKey(ctx, int(ordererDeploymentConfig.TLSKeyID), int(tlsCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for TLS key: %w", err) + } + } + // Renew signing certificate validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity - signKeyDB, err := o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ + _, err = o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ CommonName: o.opts.ID, Organization: []string{org.MspID}, OrganizationalUnit: []string{"orderer"}, @@ -1238,7 +1264,7 @@ func (o *LocalOrderer) RenewCertificates(ordererDeploymentConfig *types.FabricOr ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) } - tlsKeyDB, err := o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ + _, err = o.keyService.RenewCertificate(ctx, int(ordererDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ CommonName: o.opts.ID, Organization: []string{org.MspID}, OrganizationalUnit: []string{"orderer"}, diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index cdceb19..80870f1 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -616,10 +616,34 @@ func (p *LocalPeer) RenewCertificates(peerDeploymentConfig *types.FabricPeerDepl if err != nil { return fmt.Errorf("failed to get TLS CA key: %w", err) } + // In case the sign key is not signed by the CA, set the signing key ID to the CA key ID + signKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + if signKeyDB.SigningKeyID == nil || *signKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.SignKeyID), int(signCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for sign key: %w", err) + } + } + tlsKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } + + if tlsKeyDB.SigningKeyID == nil || *tlsKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.TLSKeyID), int(tlsCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for TLS key: %w", err) + } + } // Renew signing certificate validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity - signKeyDB, err := p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ + _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ CommonName: p.opts.ID, Organization: []string{org.MspID}, OrganizationalUnit: []string{"peer"}, @@ -665,7 +689,7 @@ func (p *LocalPeer) RenewCertificates(peerDeploymentConfig *types.FabricPeerDepl ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) } - tlsKeyDB, err := p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ + _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ CommonName: p.opts.ID, Organization: []string{org.MspID}, OrganizationalUnit: []string{"peer"}, From f436e8f5b0da40ac58e54976f89669a4cdd8ec24 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sat, 5 Apr 2025 22:02:50 +0200 Subject: [PATCH 10/31] Add API endpoint for updating Fabric network configuration - Introduced a new POST endpoint `/networks/fabric/{id}/update-config` to prepare a configuration update proposal for a Fabric network. - Implemented request and response structures for handling configuration update operations. - Added validation for various operation types and their payloads. - Enhanced Swagger documentation to reflect the new endpoint and its parameters. This feature allows users to manage Fabric network configurations more effectively by supporting multiple operation types for updates. Signed-off-by: dviejokfs --- docs/docs.go | 138 +++++++++++- docs/swagger.json | 136 ++++++++++++ docs/swagger.yaml | 132 +++++++++++ pkg/networks/http/handler.go | 206 +++++++++++++++++- pkg/networks/service/fabric/deployer.go | 72 ++++++ pkg/networks/service/fabric/org/org.go | 19 +- pkg/networks/service/service.go | 93 ++++++++ pkg/nodes/peer/peer.go | 1 + .../api/client/@tanstack/react-query.gen.ts | 37 +++- web/src/api/client/sdk.gen.ts | 28 ++- web/src/api/client/types.gen.ts | 91 ++++++++ 11 files changed, 935 insertions(+), 18 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index cfa41e5..f6951fd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Package docs Code generated by swaggo/swag at 2025-04-05 20:29:48.069943 +0200 CEST m=+1.706063459. DO NOT EDIT +// Package docs Code generated by swaggo/swag at 2025-04-05 22:02:23.040823 +0200 CEST m=+1.676072542. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -2748,6 +2748,59 @@ const docTemplate = `{ } } }, + "/networks/fabric/{id}/update-config": { + "post": { + "description": "Prepare a config update proposal for a Fabric network using the provided operations.\nThe following operation types are supported:\n- add_org: Add a new organization to the channel\n- remove_org: Remove an organization from the channel\n- update_org_msp: Update an organization's MSP configuration\n- set_anchor_peers: Set anchor peers for an organization\n- add_consenter: Add a new consenter to the orderer\n- remove_consenter: Remove a consenter from the orderer\n- update_consenter: Update a consenter in the orderer\n- update_etcd_raft_options: Update etcd raft options for the orderer\n- update_batch_size: Update batch size for the orderer\n- update_batch_timeout: Update batch timeout for the orderer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Prepare a config update for a Fabric network", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Config update operations", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateFabricNetworkRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.ConfigUpdateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/nodes": { "get": { "description": "Get a paginated list of nodes with optional platform filter", @@ -4502,6 +4555,71 @@ const docTemplate = `{ } } }, + "http.ConfigUpdateOperationRequest": { + "description": "A single configuration update operation", + "type": "object", + "required": [ + "payload", + "type" + ], + "properties": { + "payload": { + "description": "Payload contains the operation-specific data\nThe structure depends on the operation type:\n- add_org: AddOrgPayload\n- remove_org: RemoveOrgPayload\n- update_org_msp: UpdateOrgMSPPayload\n- set_anchor_peers: SetAnchorPeersPayload\n- add_consenter: AddConsenterPayload\n- remove_consenter: RemoveConsenterPayload\n- update_consenter: UpdateConsenterPayload\n- update_etcd_raft_options: UpdateEtcdRaftOptionsPayload\n- update_batch_size: UpdateBatchSizePayload\n- update_batch_timeout: UpdateBatchTimeoutPayload\n@Description The payload for the configuration update operation\n@Description Can be one of:\n@Description - AddOrgPayload when type is \"add_org\"\n@Description - RemoveOrgPayload when type is \"remove_org\"\n@Description - UpdateOrgMSPPayload when type is \"update_org_msp\"\n@Description - SetAnchorPeersPayload when type is \"set_anchor_peers\"\n@Description - AddConsenterPayload when type is \"add_consenter\"\n@Description - RemoveConsenterPayload when type is \"remove_consenter\"\n@Description - UpdateConsenterPayload when type is \"update_consenter\"\n@Description - UpdateEtcdRaftOptionsPayload when type is \"update_etcd_raft_options\"\n@Description - UpdateBatchSizePayload when type is \"update_batch_size\"\n@Description - UpdateBatchTimeoutPayload when type is \"update_batch_timeout\"", + "type": "array", + "items": { + "type": "integer" + } + }, + "type": { + "description": "Type is the type of configuration update operation\nenum: add_org,remove_org,update_org_msp,set_anchor_peers,add_consenter,remove_consenter,update_consenter,update_etcd_raft_options,update_batch_size,update_batch_timeout", + "type": "string", + "enum": [ + "add_org", + "remove_org", + "update_org_msp", + "set_anchor_peers", + "add_consenter", + "remove_consenter", + "update_consenter", + "update_etcd_raft_options", + "update_batch_size", + "update_batch_timeout" + ] + } + } + }, + "http.ConfigUpdateResponse": { + "type": "object", + "properties": { + "channel_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "string" + }, + "network_id": { + "type": "integer" + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/definitions/http.ConfigUpdateOperationRequest" + } + }, + "preview_json": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "http.ConsenterConfig": { "type": "object", "required": [ @@ -5484,6 +5602,21 @@ const docTemplate = `{ } } }, + "http.UpdateFabricNetworkRequest": { + "type": "object", + "required": [ + "operations" + ], + "properties": { + "operations": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/http.ConfigUpdateOperationRequest" + } + } + } + }, "http.UpdateOrgMSPPayload": { "type": "object", "required": [ @@ -5883,6 +6016,9 @@ const docTemplate = `{ "sha256Fingerprint": { "type": "string" }, + "signingKeyID": { + "type": "integer" + }, "status": { "type": "string" } diff --git a/docs/swagger.json b/docs/swagger.json index 21323ac..c3395dc 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2746,6 +2746,59 @@ } } }, + "/networks/fabric/{id}/update-config": { + "post": { + "description": "Prepare a config update proposal for a Fabric network using the provided operations.\nThe following operation types are supported:\n- add_org: Add a new organization to the channel\n- remove_org: Remove an organization from the channel\n- update_org_msp: Update an organization's MSP configuration\n- set_anchor_peers: Set anchor peers for an organization\n- add_consenter: Add a new consenter to the orderer\n- remove_consenter: Remove a consenter from the orderer\n- update_consenter: Update a consenter in the orderer\n- update_etcd_raft_options: Update etcd raft options for the orderer\n- update_batch_size: Update batch size for the orderer\n- update_batch_timeout: Update batch timeout for the orderer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Prepare a config update for a Fabric network", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Config update operations", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateFabricNetworkRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.ConfigUpdateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/nodes": { "get": { "description": "Get a paginated list of nodes with optional platform filter", @@ -4500,6 +4553,71 @@ } } }, + "http.ConfigUpdateOperationRequest": { + "description": "A single configuration update operation", + "type": "object", + "required": [ + "payload", + "type" + ], + "properties": { + "payload": { + "description": "Payload contains the operation-specific data\nThe structure depends on the operation type:\n- add_org: AddOrgPayload\n- remove_org: RemoveOrgPayload\n- update_org_msp: UpdateOrgMSPPayload\n- set_anchor_peers: SetAnchorPeersPayload\n- add_consenter: AddConsenterPayload\n- remove_consenter: RemoveConsenterPayload\n- update_consenter: UpdateConsenterPayload\n- update_etcd_raft_options: UpdateEtcdRaftOptionsPayload\n- update_batch_size: UpdateBatchSizePayload\n- update_batch_timeout: UpdateBatchTimeoutPayload\n@Description The payload for the configuration update operation\n@Description Can be one of:\n@Description - AddOrgPayload when type is \"add_org\"\n@Description - RemoveOrgPayload when type is \"remove_org\"\n@Description - UpdateOrgMSPPayload when type is \"update_org_msp\"\n@Description - SetAnchorPeersPayload when type is \"set_anchor_peers\"\n@Description - AddConsenterPayload when type is \"add_consenter\"\n@Description - RemoveConsenterPayload when type is \"remove_consenter\"\n@Description - UpdateConsenterPayload when type is \"update_consenter\"\n@Description - UpdateEtcdRaftOptionsPayload when type is \"update_etcd_raft_options\"\n@Description - UpdateBatchSizePayload when type is \"update_batch_size\"\n@Description - UpdateBatchTimeoutPayload when type is \"update_batch_timeout\"", + "type": "array", + "items": { + "type": "integer" + } + }, + "type": { + "description": "Type is the type of configuration update operation\nenum: add_org,remove_org,update_org_msp,set_anchor_peers,add_consenter,remove_consenter,update_consenter,update_etcd_raft_options,update_batch_size,update_batch_timeout", + "type": "string", + "enum": [ + "add_org", + "remove_org", + "update_org_msp", + "set_anchor_peers", + "add_consenter", + "remove_consenter", + "update_consenter", + "update_etcd_raft_options", + "update_batch_size", + "update_batch_timeout" + ] + } + } + }, + "http.ConfigUpdateResponse": { + "type": "object", + "properties": { + "channel_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "string" + }, + "network_id": { + "type": "integer" + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/definitions/http.ConfigUpdateOperationRequest" + } + }, + "preview_json": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "http.ConsenterConfig": { "type": "object", "required": [ @@ -5482,6 +5600,21 @@ } } }, + "http.UpdateFabricNetworkRequest": { + "type": "object", + "required": [ + "operations" + ], + "properties": { + "operations": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/http.ConfigUpdateOperationRequest" + } + } + } + }, "http.UpdateOrgMSPPayload": { "type": "object", "required": [ @@ -5881,6 +6014,9 @@ "sha256Fingerprint": { "type": "string" }, + "signingKeyID": { + "type": "integer" + }, "status": { "type": "string" } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 85a73ad..c56e856 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -278,6 +278,79 @@ definitions: name: type: string type: object + http.ConfigUpdateOperationRequest: + description: A single configuration update operation + properties: + payload: + description: |- + Payload contains the operation-specific data + The structure depends on the operation type: + - add_org: AddOrgPayload + - remove_org: RemoveOrgPayload + - update_org_msp: UpdateOrgMSPPayload + - set_anchor_peers: SetAnchorPeersPayload + - add_consenter: AddConsenterPayload + - remove_consenter: RemoveConsenterPayload + - update_consenter: UpdateConsenterPayload + - update_etcd_raft_options: UpdateEtcdRaftOptionsPayload + - update_batch_size: UpdateBatchSizePayload + - update_batch_timeout: UpdateBatchTimeoutPayload + @Description The payload for the configuration update operation + @Description Can be one of: + @Description - AddOrgPayload when type is "add_org" + @Description - RemoveOrgPayload when type is "remove_org" + @Description - UpdateOrgMSPPayload when type is "update_org_msp" + @Description - SetAnchorPeersPayload when type is "set_anchor_peers" + @Description - AddConsenterPayload when type is "add_consenter" + @Description - RemoveConsenterPayload when type is "remove_consenter" + @Description - UpdateConsenterPayload when type is "update_consenter" + @Description - UpdateEtcdRaftOptionsPayload when type is "update_etcd_raft_options" + @Description - UpdateBatchSizePayload when type is "update_batch_size" + @Description - UpdateBatchTimeoutPayload when type is "update_batch_timeout" + items: + type: integer + type: array + type: + description: |- + Type is the type of configuration update operation + enum: add_org,remove_org,update_org_msp,set_anchor_peers,add_consenter,remove_consenter,update_consenter,update_etcd_raft_options,update_batch_size,update_batch_timeout + enum: + - add_org + - remove_org + - update_org_msp + - set_anchor_peers + - add_consenter + - remove_consenter + - update_consenter + - update_etcd_raft_options + - update_batch_size + - update_batch_timeout + type: string + required: + - payload + - type + type: object + http.ConfigUpdateResponse: + properties: + channel_name: + type: string + created_at: + type: string + created_by: + type: string + id: + type: string + network_id: + type: integer + operations: + items: + $ref: '#/definitions/http.ConfigUpdateOperationRequest' + type: array + preview_json: + type: string + status: + type: string + type: object http.ConsenterConfig: properties: id: @@ -996,6 +1069,16 @@ definitions: - snapshot_interval_size - tick_interval type: object + http.UpdateFabricNetworkRequest: + properties: + operations: + items: + $ref: '#/definitions/http.ConfigUpdateOperationRequest' + minItems: 1 + type: array + required: + - operations + type: object http.UpdateOrgMSPPayload: properties: msp_id: @@ -1277,6 +1360,8 @@ definitions: type: string sha256Fingerprint: type: string + signingKeyID: + type: integer status: type: string type: object @@ -3538,6 +3623,53 @@ paths: summary: Reload network config block tags: - fabric-networks + /networks/fabric/{id}/update-config: + post: + consumes: + - application/json + description: |- + Prepare a config update proposal for a Fabric network using the provided operations. + The following operation types are supported: + - add_org: Add a new organization to the channel + - remove_org: Remove an organization from the channel + - update_org_msp: Update an organization's MSP configuration + - set_anchor_peers: Set anchor peers for an organization + - add_consenter: Add a new consenter to the orderer + - remove_consenter: Remove a consenter from the orderer + - update_consenter: Update a consenter in the orderer + - update_etcd_raft_options: Update etcd raft options for the orderer + - update_batch_size: Update batch size for the orderer + - update_batch_timeout: Update batch timeout for the orderer + parameters: + - description: Network ID + in: path + name: id + required: true + type: integer + - description: Config update operations + in: body + name: request + required: true + schema: + $ref: '#/definitions/http.UpdateFabricNetworkRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.ConfigUpdateResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + summary: Prepare a config update for a Fabric network + tags: + - fabric-networks /networks/fabric/by-name/{name}: get: description: Get details of a specific Fabric network using its slug diff --git a/pkg/networks/http/handler.go b/pkg/networks/http/handler.go index 0915e73..89887ec 100644 --- a/pkg/networks/http/handler.go +++ b/pkg/networks/http/handler.go @@ -13,6 +13,7 @@ import ( "encoding/base64" "github.com/chainlaunch/chainlaunch/pkg/networks/service" + "github.com/chainlaunch/chainlaunch/pkg/networks/service/fabric" "github.com/chainlaunch/chainlaunch/pkg/networks/service/types" nodeservice "github.com/chainlaunch/chainlaunch/pkg/nodes/service" nodetypes "github.com/chainlaunch/chainlaunch/pkg/nodes/types" @@ -57,6 +58,7 @@ func (h *Handler) RegisterRoutes(r chi.Router) { r.Get("/by-name/{name}", h.FabricNetworkGetByName) r.Post("/import", h.ImportFabricNetwork) r.Post("/import-with-org", h.ImportFabricNetworkWithOrg) + r.Post("/{id}/update-config", h.FabricUpdateChannelConfig) }) // New Besu routes @@ -1387,8 +1389,8 @@ type UpdateBatchTimeoutPayload struct { Timeout string `json:"timeout" validate:"required"` // e.g., "2s" } -// PrepareConfigUpdateRequest represents a request to prepare a config update -type PrepareConfigUpdateRequest struct { +// UpdateFabricNetworkRequest represents a request to update a Fabric network +type UpdateFabricNetworkRequest struct { Operations []ConfigUpdateOperationRequest `json:"operations" validate:"required,min=1,dive"` } @@ -1413,3 +1415,203 @@ type PrepareConfigUpdateRequest struct { func (h *Handler) DummyHandler(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, "dummy_error", "Dummy error") } + +// @Summary Prepare a config update for a Fabric network +// @Description Prepare a config update proposal for a Fabric network using the provided operations. +// @Description The following operation types are supported: +// @Description - add_org: Add a new organization to the channel +// @Description - remove_org: Remove an organization from the channel +// @Description - update_org_msp: Update an organization's MSP configuration +// @Description - set_anchor_peers: Set anchor peers for an organization +// @Description - add_consenter: Add a new consenter to the orderer +// @Description - remove_consenter: Remove a consenter from the orderer +// @Description - update_consenter: Update a consenter in the orderer +// @Description - update_etcd_raft_options: Update etcd raft options for the orderer +// @Description - update_batch_size: Update batch size for the orderer +// @Description - update_batch_timeout: Update batch timeout for the orderer +// @Tags fabric-networks +// @Accept json +// @Produce json +// @Param id path int true "Network ID" +// @Param request body UpdateFabricNetworkRequest true "Config update operations" +// @Success 200 {object} ConfigUpdateResponse +// @Failure 400 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /networks/fabric/{id}/update-config [post] +func (h *Handler) FabricUpdateChannelConfig(w http.ResponseWriter, r *http.Request) { + // Parse network ID from URL + networkID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_network_id", "Invalid network ID") + return + } + + // Parse request body + var req UpdateFabricNetworkRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(w, http.StatusBadRequest, "invalid_request", "Invalid request body") + return + } + + // Validate request + if err := h.validate.Struct(req); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", err.Error()) + return + } + + // Validate each operation's payload + for i, op := range req.Operations { + switch op.Type { + case "add_org": + var payload AddOrgPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "remove_org": + var payload RemoveOrgPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "update_org_msp": + var payload UpdateOrgMSPPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "set_anchor_peers": + var payload SetAnchorPeersPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "add_consenter": + var payload AddConsenterPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "remove_consenter": + var payload RemoveConsenterPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "update_consenter": + var payload UpdateConsenterPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "update_etcd_raft_options": + var payload UpdateEtcdRaftOptionsPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "update_batch_size": + var payload UpdateBatchSizePayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + case "update_batch_timeout": + var payload UpdateBatchTimeoutPayload + if err := json.Unmarshal(op.Payload, &payload); err != nil { + writeError(w, http.StatusBadRequest, "invalid_payload", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + if err := h.validate.Struct(payload); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid payload for operation %d: %s", i, err.Error())) + return + } + // Validate that the timeout is a valid duration + if _, err := time.ParseDuration(payload.Timeout); err != nil { + writeError(w, http.StatusBadRequest, "validation_error", fmt.Sprintf("Invalid timeout for operation %d: %s", i, err.Error())) + return + } + default: + writeError(w, http.StatusBadRequest, "invalid_operation_type", fmt.Sprintf("Unsupported operation type: %s", op.Type)) + return + } + } + + // Convert operations to fabric.ConfigUpdateOperation + operations := make([]fabric.ConfigUpdateOperation, len(req.Operations)) + for i, op := range req.Operations { + operations[i] = fabric.ConfigUpdateOperation{ + Type: fabric.ConfigUpdateOperationType(op.Type), + Payload: op.Payload, + } + } + + // Call service to prepare config update + proposal, err := h.networkService.UpdateFabricNetwork(r.Context(), networkID, operations) + if err != nil { + writeError(w, http.StatusInternalServerError, "prepare_config_update_failed", err.Error()) + return + } + + // Create response + resp := ConfigUpdateResponse{ + ID: proposal.ID, + NetworkID: proposal.NetworkID, + ChannelName: proposal.ChannelName, + Status: proposal.Status, + CreatedAt: proposal.CreatedAt, + CreatedBy: proposal.CreatedBy, + Operations: req.Operations, + } + + // Return response + writeJSON(w, http.StatusOK, resp) +} + +// ConfigUpdateResponse represents the response from preparing a config update +type ConfigUpdateResponse struct { + ID string `json:"id"` + NetworkID int64 `json:"network_id"` + ChannelName string `json:"channel_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + CreatedBy string `json:"created_by"` + Operations []ConfigUpdateOperationRequest `json:"operations"` + PreviewJSON string `json:"preview_json,omitempty"` +} diff --git a/pkg/networks/service/fabric/deployer.go b/pkg/networks/service/fabric/deployer.go index d82097c..cee06f4 100644 --- a/pkg/networks/service/fabric/deployer.go +++ b/pkg/networks/service/fabric/deployer.go @@ -26,14 +26,19 @@ import ( orgservicefabric "github.com/chainlaunch/chainlaunch/pkg/fabric/service" keymanagement "github.com/chainlaunch/chainlaunch/pkg/keymanagement/service" "github.com/chainlaunch/chainlaunch/pkg/logger" + "github.com/chainlaunch/chainlaunch/pkg/networks/service/fabric/org" fabricorg "github.com/chainlaunch/chainlaunch/pkg/networks/service/fabric/org" "github.com/chainlaunch/chainlaunch/pkg/networks/service/types" nodeservice "github.com/chainlaunch/chainlaunch/pkg/nodes/service" nodetypes "github.com/chainlaunch/chainlaunch/pkg/nodes/types" "github.com/golang/protobuf/proto" "github.com/google/uuid" + "github.com/hyperledger/fabric-admin-sdk/pkg/network" "github.com/hyperledger/fabric-config/configtx" "github.com/hyperledger/fabric-config/configtx/orderer" + ordererapi "github.com/hyperledger/fabric-protos-go-apiv2/orderer" + "google.golang.org/grpc" + "github.com/hyperledger/fabric-config/protolator" cb "github.com/hyperledger/fabric-protos-go-apiv2/common" ) @@ -602,6 +607,73 @@ func CreateConfigModifier(operation ConfigUpdateOperation) (ConfigModifier, erro return modifier, nil } +// UpdateChannelConfig updates the channel configuration with the provided config update envelope and signatures +func (d *FabricDeployer) UpdateChannelConfig(ctx context.Context, networkID int64, configUpdateEnvelope []byte, signingOrgIDs []string, ordererAddress string, ordererTLSCert string) (string, error) { + // Get network details + network, err := d.db.GetNetwork(ctx, networkID) + if err != nil { + return "", fmt.Errorf("failed to get network: %w", err) + } + + // Unmarshal the config update envelope + envelope := &cb.Envelope{} + if err := proto.Unmarshal(configUpdateEnvelope, envelope); err != nil { + return "", fmt.Errorf("failed to unmarshal config update envelope: %w", err) + } + + // Collect signatures from the specified organizations + for _, orgID := range signingOrgIDs { + // Get organization details and MSP + orgService := org.NewOrganizationService(d.orgService, d.keyMgmt, d.logger, orgID) + + // Sign the config update + envelope, err = orgService.CreateConfigSignature(ctx, network.Name, envelope) + if err != nil { + return "", fmt.Errorf("failed to sign config update for org %s: %w", orgID, err) + } + } + + ordererConn, err := d.createOrdererConnection(ordererAddress, ordererTLSCert) + if err != nil { + return "", fmt.Errorf("failed to create orderer connection: %w", err) + } + defer ordererConn.Close() + ordererClient, err := ordererapi.NewAtomicBroadcastClient(ordererConn).Broadcast(context.Background()) + if err != nil { + return "", fmt.Errorf("failed to create orderer client: %w", err) + } + err = ordererClient.Send(envelope) + if err != nil { + return "", fmt.Errorf("failed to send envelope: %w", err) + } + response, err := ordererClient.Recv() + if err != nil { + return "", fmt.Errorf("failed to receive response: %w", err) + } + return response.String(), nil + +} + +// CreateOrdererConnection establishes a gRPC connection to an orderer +func (d *FabricDeployer) createOrdererConnection(ordererURL string, ordererTLSCACert string) (*grpc.ClientConn, error) { + d.logger.Info("Creating orderer connection", + "ordererURL", ordererURL) + + // Create a network node with the orderer details + networkNode := network.Node{ + Addr: ordererURL, + TLSCACertByte: []byte(ordererTLSCACert), + } + + // Establish connection to the orderer + ordererConn, err := network.DialConnection(networkNode) + if err != nil { + return nil, fmt.Errorf("failed to dial orderer connection: %w", err) + } + + return ordererConn, nil +} + // PrepareConfigUpdate prepares a config update for the given operations func (d *FabricDeployer) PrepareConfigUpdate(ctx context.Context, networkID int64, operations []ConfigUpdateOperation) (*ConfigUpdateProposal, error) { // Get network details diff --git a/pkg/networks/service/fabric/org/org.go b/pkg/networks/service/fabric/org/org.go index ba4010f..c78b491 100644 --- a/pkg/networks/service/fabric/org/org.go +++ b/pkg/networks/service/fabric/org/org.go @@ -114,9 +114,6 @@ func (s *FabricOrg) GetConfigBlockWithNetworkConfig(ctx context.Context, channel return ordererBlock, nil } - - - // getAdminIdentity retrieves the admin identity for the organization func (s *FabricOrg) getAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { // Get organization details @@ -284,7 +281,7 @@ func (s *FabricOrg) GetGenesisBlock(ctx context.Context, channelID string, order } // CreateConfigSignature creates a signature for a config update using the organization's admin credentials -func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, configUpdateBytes []byte) (*cb.ConfigSignature, error) { +func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, configUpdateBytes *cb.Envelope) (*cb.Envelope, error) { s.logger.Info("Creating config signature", "mspID", s.mspID, "channel", channelID) @@ -315,13 +312,8 @@ func (s *FabricOrg) CreateConfigSignature(ctx context.Context, channelID string, return nil, fmt.Errorf("failed to create signing identity: %w", err) } - var envelope cb.Envelope - err = proto.Unmarshal(configUpdateBytes, &envelope) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal envelope: %w", err) - } // Create config signature from the config update bytes - signature, err := SignConfigTx(channelID, &envelope, signingIdentity) + signature, err := SignConfigTx(channelID, configUpdateBytes, signingIdentity) if err != nil { return nil, fmt.Errorf("failed to create config signature: %w", err) } @@ -333,7 +325,7 @@ const ( epoch = 0 ) -func SignConfigTx(channelID string, envConfigUpdate *cb.Envelope, signer identity.SigningIdentity) (*cb.ConfigSignature, error) { +func SignConfigTx(channelID string, envConfigUpdate *cb.Envelope, signer identity.SigningIdentity) (*cb.Envelope, error) { payload, err := protoutil.UnmarshalPayload(envConfigUpdate.Payload) if err != nil { return nil, errors.New("bad payload") @@ -375,8 +367,11 @@ func SignConfigTx(channelID string, envConfigUpdate *cb.Envelope, signer identit return nil, err } - return configSig, nil + configUpdateEnv.Signatures = append(configUpdateEnv.Signatures, configSig) + + return protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, signer, configUpdateEnv, msgVersion, epoch) } + func Concatenate[T any](slices ...[]T) []T { size := 0 for _, slice := range slices { diff --git a/pkg/networks/service/service.go b/pkg/networks/service/service.go index 26294b7..6bea074 100644 --- a/pkg/networks/service/service.go +++ b/pkg/networks/service/service.go @@ -916,3 +916,96 @@ func (s *NetworkService) importBesuNetwork(ctx context.Context, params ImportNet Message: "Besu network imported successfully", }, nil } + +// UpdateFabricNetwork prepares a config update proposal for a Fabric network +func (s *NetworkService) UpdateFabricNetwork(ctx context.Context, networkID int64, operations []fabric.ConfigUpdateOperation) (*fabric.ConfigUpdateProposal, error) { + // Get deployer for the network + deployer, err := s.deployerFactory.GetDeployer(string(BlockchainTypeFabric)) + if err != nil { + return nil, fmt.Errorf("failed to get deployer: %w", err) + } + + // Assert that it's a Fabric deployer + fabricDeployer, ok := deployer.(*fabric.FabricDeployer) + if !ok { + return nil, fmt.Errorf("network %d is not a Fabric network", networkID) + } + + // Prepare the config update + proposal, err := fabricDeployer.PrepareConfigUpdate(ctx, networkID, operations) + if err != nil { + return nil, fmt.Errorf("failed to prepare config update: %w", err) + } + + // Get organizations managed by us that can sign the config update + orgs, err := s.db.ListFabricOrganizations(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get network organizations: %w", err) + } + var signingOrgIDs []string + for _, org := range orgs { + signingOrgIDs = append(signingOrgIDs, org.MspID) + } + + ordererAddress, ordererTLSCert, err := s.getOrdererAddressAndCertForNetwork(ctx, networkID, fabricDeployer) + if err != nil { + return nil, fmt.Errorf("failed to get orderer address and TLS certificate: %w", err) + } + + res, err := fabricDeployer.UpdateChannelConfig(ctx, networkID, proposal.ConfigUpdateEnvelope, signingOrgIDs, ordererAddress, ordererTLSCert) + if err != nil { + return nil, fmt.Errorf("failed to update channel config: %w", err) + } + s.logger.Info("Channel config updated", "txID", res) + return proposal, nil +} + +func (s *NetworkService) getOrdererAddressAndCertForNetwork(ctx context.Context, networkID int64, fabricDeployer *fabric.FabricDeployer) (string, string, error) { + + // Try to get orderer info from network nodes first + networkNodes, err := s.GetNetworkNodes(ctx, networkID) + if err != nil { + return "", "", fmt.Errorf("failed to get network nodes: %w", err) + } + + var ordererAddress, ordererTLSCert string + + // Look for orderer in our registry + for _, node := range networkNodes { + if node.Node.NodeType == nodetypes.NodeTypeFabricOrderer { + ordererConfig, ok := node.Node.DeploymentConfig.(*nodetypes.FabricOrdererDeploymentConfig) + if !ok { + continue + } + ordererAddress = ordererConfig.ExternalEndpoint + ordererTLSCert = ordererConfig.TLSCACert + break + } + } + + // If no orderer found in registry, try to get from current config block + if ordererAddress == "" { + // Get current config block + configBlock, err := fabricDeployer.GetCurrentChannelConfig(networkID) + if err != nil { + return "", "", fmt.Errorf("failed to get current config block: %w", err) + } + + // Extract orderer info from config block + ordererInfo, err := fabricDeployer.GetOrderersFromConfigBlock(ctx, configBlock) + if err != nil { + return "", "", fmt.Errorf("failed to get orderer info from config: %w", err) + } + if len(ordererInfo) == 0 { + return "", "", fmt.Errorf("no orderer found in config block") + } + ordererAddress = ordererInfo[0].URL + ordererTLSCert = ordererInfo[0].TLSCert + } + + if ordererAddress == "" { + return "", "", fmt.Errorf("no orderer found in network or config block") + } + + return ordererAddress, ordererTLSCert, nil +} diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index 80870f1..1d806b1 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -2189,6 +2189,7 @@ func (p *LocalPeer) SaveChannelConfig(ctx context.Context, channelID string, ord if err != nil { return nil, fmt.Errorf("failed to set anchor peers: %w", err) } + ordererConn, err := p.CreateOrdererConnection(ctx, ordererUrl, ordererTlsCACert) if err != nil { return nil, fmt.Errorf("failed to create orderer connection: %w", err) diff --git a/web/src/api/client/@tanstack/react-query.gen.ts b/web/src/api/client/@tanstack/react-query.gen.ts index 0ec74a1..e167c2e 100644 --- a/web/src/api/client/@tanstack/react-query.gen.ts +++ b/web/src/api/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import type { Options } from '@hey-api/client-fetch'; import { queryOptions, type UseMutationOptions, type DefaultError, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; -import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; -import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; +import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigError, PostNetworksFabricByIdUpdateConfigResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; +import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, postNetworksFabricByIdUpdateConfig, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; type QueryKey = [ Pick & { @@ -1466,6 +1466,39 @@ export const postNetworksFabricByIdReloadBlockMutation = (options?: Partial) => [ + createQueryKey('postNetworksFabricByIdUpdateConfig', options) +]; + +export const postNetworksFabricByIdUpdateConfigOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await postNetworksFabricByIdUpdateConfig({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: postNetworksFabricByIdUpdateConfigQueryKey(options) + }); +}; + +export const postNetworksFabricByIdUpdateConfigMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await postNetworksFabricByIdUpdateConfig({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getNodesQueryKey = (options?: Options) => [ createQueryKey('getNodes', options) ]; diff --git a/web/src/api/client/sdk.gen.ts b/web/src/api/client/sdk.gen.ts index 730cd26..c345208 100644 --- a/web/src/api/client/sdk.gen.ts +++ b/web/src/api/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; +import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigResponse, PostNetworksFabricByIdUpdateConfigError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; export const client = createClient(createConfig()); @@ -691,6 +691,32 @@ export const postNetworksFabricByIdReloadBlock = (options: Options) => { + return (options?.client ?? client).post({ + url: '/networks/fabric/{id}/update-config', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * List all nodes * Get a paginated list of nodes with optional platform filter diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts index fa9cbb5..03a1393 100644 --- a/web/src/api/client/types.gen.ts +++ b/web/src/api/client/types.gen.ts @@ -173,6 +173,44 @@ export type HttpChannelResponse = { name?: string; }; +/** + * A single configuration update operation + */ +export type HttpConfigUpdateOperationRequest = { + /** + * Payload contains the operation-specific data + * The structure depends on the operation type: + * - add_org: AddOrgPayload + * - remove_org: RemoveOrgPayload + * - update_org_msp: UpdateOrgMSPPayload + * - set_anchor_peers: SetAnchorPeersPayload + * - add_consenter: AddConsenterPayload + * - remove_consenter: RemoveConsenterPayload + * - update_consenter: UpdateConsenterPayload + * - update_etcd_raft_options: UpdateEtcdRaftOptionsPayload + * - update_batch_size: UpdateBatchSizePayload + * - update_batch_timeout: UpdateBatchTimeoutPayload + * @Description The payload for the configuration update operation + * @Description Can be one of: + * @Description - AddOrgPayload when type is "add_org" + * @Description - RemoveOrgPayload when type is "remove_org" + * @Description - UpdateOrgMSPPayload when type is "update_org_msp" + * @Description - SetAnchorPeersPayload when type is "set_anchor_peers" + * @Description - AddConsenterPayload when type is "add_consenter" + * @Description - RemoveConsenterPayload when type is "remove_consenter" + * @Description - UpdateConsenterPayload when type is "update_consenter" + * @Description - UpdateEtcdRaftOptionsPayload when type is "update_etcd_raft_options" + * @Description - UpdateBatchSizePayload when type is "update_batch_size" + * @Description - UpdateBatchTimeoutPayload when type is "update_batch_timeout" + */ + payload: Array; + /** + * Type is the type of configuration update operation + * enum: add_org,remove_org,update_org_msp,set_anchor_peers,add_consenter,remove_consenter,update_consenter,update_etcd_raft_options,update_batch_size,update_batch_timeout + */ + type: 'add_org' | 'remove_org' | 'update_org_msp' | 'set_anchor_peers' | 'add_consenter' | 'remove_consenter' | 'update_consenter' | 'update_etcd_raft_options' | 'update_batch_size' | 'update_batch_timeout'; +}; + export type HttpConsenterConfig = { id: string; }; @@ -504,6 +542,21 @@ export type HttpPaginatedNodesResponse = { total?: number; }; +export type HttpPrepareConfigUpdateRequest = { + operations: Array; +}; + +export type HttpPrepareConfigUpdateResponse = { + channel_name?: string; + created_at?: string; + created_by?: string; + id?: string; + network_id?: number; + operations?: Array; + preview_json?: string; + status?: string; +}; + export type HttpProviderResponse = { config?: unknown; createdAt?: string; @@ -722,6 +775,7 @@ export type ModelsKeyResponse = { publicKey?: string; sha1Fingerprint?: string; sha256Fingerprint?: string; + signingKeyID?: number; status?: string; }; @@ -3101,6 +3155,43 @@ export type PostNetworksFabricByIdReloadBlockResponses = { export type PostNetworksFabricByIdReloadBlockResponse = PostNetworksFabricByIdReloadBlockResponses[keyof PostNetworksFabricByIdReloadBlockResponses]; +export type PostNetworksFabricByIdUpdateConfigData = { + /** + * Config update operations + */ + body: HttpPrepareConfigUpdateRequest; + path: { + /** + * Network ID + */ + id: number; + }; + query?: never; + url: '/networks/fabric/{id}/update-config'; +}; + +export type PostNetworksFabricByIdUpdateConfigErrors = { + /** + * Bad Request + */ + 400: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Internal Server Error + */ + 500: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; +}; + +export type PostNetworksFabricByIdUpdateConfigError = PostNetworksFabricByIdUpdateConfigErrors[keyof PostNetworksFabricByIdUpdateConfigErrors]; + +export type PostNetworksFabricByIdUpdateConfigResponses = { + /** + * OK + */ + 200: HttpPrepareConfigUpdateResponse; +}; + +export type PostNetworksFabricByIdUpdateConfigResponse = PostNetworksFabricByIdUpdateConfigResponses[keyof PostNetworksFabricByIdUpdateConfigResponses]; + export type GetNodesData = { body?: never; path?: never; From 479e4f5c41e17d6393ea5583c5012248ac4eb0ce Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Sun, 6 Apr 2025 18:27:13 +0200 Subject: [PATCH 11/31] Refactor Fabric network operations and introduce new components - Migrated various operations related to Fabric network management to new React components, including AddOrgOperation, RemoveOrgOperation, UpdateOrgMSPOperation, and others. - Implemented validation schemas for each operation using Zod to ensure proper data handling. - Enhanced the ChannelUpdateForm to support multiple operations for updating channel configurations. - Removed deprecated components and files related to channel updates, streamlining the codebase. - Updated the UI to improve user experience when managing Fabric network configurations. These changes enhance the modularity and maintainability of the code, allowing for easier updates and feature additions in the future. Signed-off-by: dviejokfs --- pkg/networks/service/fabric/deployer.go | 163 ++++++++--- .../networks/FabricNetworkDetails.tsx | 10 +- .../SetAnchorPeersOperation.tsx | 156 ----------- .../UpdateBatchSizeOperation.tsx | 103 ------- .../UpdateBatchTimeoutOperation.tsx | 60 ----- .../UpdateEtcdRaftOptionsOperation.tsx | 142 ---------- .../networks/channel-update/index.ts | 11 - web/src/components/networks/network-tabs.tsx | 100 ++++--- .../components/nodes/ChannelUpdateForm.tsx | 254 ++++++++++++++++++ .../operations}/AddConsenterOperation.tsx | 0 .../operations}/AddOrgOperation.tsx | 39 +-- .../operations}/RemoveConsenterOperation.tsx | 0 .../operations}/RemoveOrgOperation.tsx | 0 .../operations/SetAnchorPeersOperation.tsx | 122 +++++++++ .../operations/UpdateBatchSizeOperation.tsx | 87 ++++++ .../UpdateBatchTimeoutOperation.tsx | 56 ++++ .../operations}/UpdateConsenterOperation.tsx | 0 .../UpdateEtcdRaftOptionsOperation.tsx | 124 +++++++++ .../operations}/UpdateOrgMSPOperation.tsx | 0 19 files changed, 840 insertions(+), 587 deletions(-) delete mode 100644 web/src/components/networks/channel-update/SetAnchorPeersOperation.tsx delete mode 100644 web/src/components/networks/channel-update/UpdateBatchSizeOperation.tsx delete mode 100644 web/src/components/networks/channel-update/UpdateBatchTimeoutOperation.tsx delete mode 100644 web/src/components/networks/channel-update/UpdateEtcdRaftOptionsOperation.tsx delete mode 100644 web/src/components/networks/channel-update/index.ts create mode 100644 web/src/components/nodes/ChannelUpdateForm.tsx rename web/src/components/{networks/channel-update => nodes/operations}/AddConsenterOperation.tsx (100%) rename web/src/components/{networks/channel-update => nodes/operations}/AddOrgOperation.tsx (85%) rename web/src/components/{networks/channel-update => nodes/operations}/RemoveConsenterOperation.tsx (100%) rename web/src/components/{networks/channel-update => nodes/operations}/RemoveOrgOperation.tsx (100%) create mode 100644 web/src/components/nodes/operations/SetAnchorPeersOperation.tsx create mode 100644 web/src/components/nodes/operations/UpdateBatchSizeOperation.tsx create mode 100644 web/src/components/nodes/operations/UpdateBatchTimeoutOperation.tsx rename web/src/components/{networks/channel-update => nodes/operations}/UpdateConsenterOperation.tsx (100%) create mode 100644 web/src/components/nodes/operations/UpdateEtcdRaftOptionsOperation.tsx rename web/src/components/{networks/channel-update => nodes/operations}/UpdateOrgMSPOperation.tsx (100%) diff --git a/pkg/networks/service/fabric/deployer.go b/pkg/networks/service/fabric/deployer.go index cee06f4..7a14dbe 100644 --- a/pkg/networks/service/fabric/deployer.go +++ b/pkg/networks/service/fabric/deployer.go @@ -35,6 +35,7 @@ import ( "github.com/google/uuid" "github.com/hyperledger/fabric-admin-sdk/pkg/network" "github.com/hyperledger/fabric-config/configtx" + "github.com/hyperledger/fabric-config/configtx/membership" "github.com/hyperledger/fabric-config/configtx/orderer" ordererapi "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "google.golang.org/grpc" @@ -139,10 +140,6 @@ func (op *AddOrgOperation) Modify(ctx context.Context, c *configtx.ConfigTx) err mspID := op.MSPID // Create a new organization in the application group - appOrg := c.Application().Organization(mspID) - if appOrg == nil { - return fmt.Errorf("failed to create application organization") - } var rootCerts []*x509.Certificate for _, rootCertStr := range op.RootCerts { @@ -161,13 +158,56 @@ func (op *AddOrgOperation) Modify(ctx context.Context, c *configtx.ConfigTx) err } tlsRootCerts = append(tlsRootCerts, tlsRootCert) } + signCACert := rootCerts[0] // Set MSP configuration - err := appOrg.SetMSP(configtx.MSP{ - Name: mspID, - RootCerts: rootCerts, - TLSRootCerts: tlsRootCerts, - Admins: []*x509.Certificate{}, + err := c.Application().SetOrganization(configtx.Organization{ + Name: mspID, + MSP: configtx.MSP{ + Name: mspID, + RootCerts: rootCerts, + TLSRootCerts: tlsRootCerts, + Admins: []*x509.Certificate{}, + NodeOUs: membership.NodeOUs{ + Enable: true, + ClientOUIdentifier: membership.OUIdentifier{ + Certificate: signCACert, + OrganizationalUnitIdentifier: "client", + }, + PeerOUIdentifier: membership.OUIdentifier{ + Certificate: signCACert, + OrganizationalUnitIdentifier: "peer", + }, + AdminOUIdentifier: membership.OUIdentifier{ + Certificate: signCACert, + OrganizationalUnitIdentifier: "admin", + }, + OrdererOUIdentifier: membership.OUIdentifier{ + Certificate: signCACert, + OrganizationalUnitIdentifier: "orderer", + }, + }, + }, + AnchorPeers: []configtx.Address{}, + OrdererEndpoints: []string{}, + Policies: map[string]configtx.Policy{ + "Admins": { + Type: "Signature", + Rule: fmt.Sprintf("OR('%s.admin')", mspID), + }, + "Readers": { + Type: "Signature", + Rule: fmt.Sprintf("OR('%s.member')", mspID), + }, + "Writers": { + Type: "Signature", + Rule: fmt.Sprintf("OR('%s.member')", mspID), + }, + "Endorsement": { + Type: "Signature", + Rule: fmt.Sprintf("OR('%s.member')", mspID), + }, + }, }) if err != nil { return fmt.Errorf("failed to set MSP configuration: %w", err) @@ -1733,67 +1773,104 @@ func (d *FabricDeployer) FetchCurrentChannelConfig(ctx context.Context, networkI } fabricOrgItem := fabricorg.NewOrganizationService(d.orgService, d.keyMgmt, d.logger, fabricOrg.MspID) - // First try to get orderer from active nodes - var ordererURL, ordererAddress, ordererTLSCert string + // Get all available orderers - first from active nodes + var orderersList []struct { + address string + tlsCert string + } + + // First try to get orderers from active nodes for _, node := range networkNodes { if node.NodeType.String == string(nodetypes.NodeTypeFabricOrderer) && node.Status == "joined" { ordererNode, err := d.nodes.GetNodeByID(ctx, node.NodeID) if err != nil { + d.logger.Warn("Failed to get orderer node", "nodeID", node.NodeID, "error", err) continue } ordererConfig := ordererNode.FabricOrderer - ordererURL = fmt.Sprintf("grpcs://%s", ordererConfig.ExternalEndpoint) - ordererAddress = ordererConfig.ExternalEndpoint // Get orderer TLS cert ordererTLSKey, err := d.keyMgmt.GetKey(ctx, int(ordererConfig.TLSKeyID)) if err != nil || ordererTLSKey.Certificate == nil { + d.logger.Warn("Failed to get orderer TLS cert", "nodeID", node.NodeID, "error", err) continue } - ordererTLSCert = *ordererTLSKey.Certificate - break + orderersList = append(orderersList, struct { + address string + tlsCert string + }{ + address: ordererConfig.ExternalEndpoint, + tlsCert: *ordererTLSKey.Certificate, + }) } } - // If no active orderer found, try to get from genesis block - if ordererURL == "" { + // If no active orderers found, try to get from genesis block + if len(orderersList) == 0 { orderers, err := d.GetOrderersFromGenesisBlock(ctx, networkID) if err != nil { return nil, fmt.Errorf("failed to get orderers from genesis block: %w", err) } - if len(orderers) > 0 { - ordererURL = orderers[0].URL - ordererTLSCert = orderers[0].TLSCert - } else { - return nil, fmt.Errorf("no orderers found in network or genesis block") + for _, orderer := range orderers { + // Remove the grpcs:// prefix if present + address := orderer.URL + if strings.HasPrefix(address, "grpcs://") { + address = strings.TrimPrefix(address, "grpcs://") + } + orderersList = append(orderersList, struct { + address string + tlsCert string + }{ + address: address, + tlsCert: orderer.TLSCert, + }) } } - // Fetch channel config from peer - channelConfig, err := fabricOrgItem.GetConfigBlockWithNetworkConfig(ctx, network.Name, ordererAddress, ordererTLSCert) - if err != nil { - return nil, fmt.Errorf("failed to get channel config from peer: %w", err) + if len(orderersList) == 0 { + return nil, fmt.Errorf("no orderers found in network or genesis block") } - // Marshal the config block - configBytes, err := proto.Marshal(channelConfig) - if err != nil { - return nil, fmt.Errorf("failed to marshal config block: %w", err) - } + // Try each orderer until one succeeds + var lastErr error + for _, orderer := range orderersList { + d.logger.Info("Attempting to fetch channel config from orderer", "address", orderer.address) - // Update the current config block in the database - configBase64 := base64.StdEncoding.EncodeToString(configBytes) - err = d.db.UpdateNetworkCurrentConfigBlock(ctx, db.UpdateNetworkCurrentConfigBlockParams{ - ID: networkID, - CurrentConfigBlockB64: sql.NullString{ - String: configBase64, - Valid: true, - }, - }) - if err != nil { - return nil, fmt.Errorf("failed to update network current config block: %w", err) + // Fetch channel config from orderer + channelConfig, err := fabricOrgItem.GetConfigBlockWithNetworkConfig(ctx, network.Name, orderer.address, orderer.tlsCert) + if err != nil { + d.logger.Warn("Failed to get channel config from orderer", "address", orderer.address, "error", err) + lastErr = err + continue + } + + // Marshal the config block + configBytes, err := proto.Marshal(channelConfig) + if err != nil { + lastErr = fmt.Errorf("failed to marshal config block: %w", err) + continue + } + + // Update the current config block in the database + configBase64 := base64.StdEncoding.EncodeToString(configBytes) + err = d.db.UpdateNetworkCurrentConfigBlock(ctx, db.UpdateNetworkCurrentConfigBlockParams{ + ID: networkID, + CurrentConfigBlockB64: sql.NullString{ + String: configBase64, + Valid: true, + }, + }) + if err != nil { + lastErr = fmt.Errorf("failed to update network current config block: %w", err) + continue + } + + // Successfully fetched and stored config + d.logger.Info("Successfully fetched channel config from orderer", "address", orderer.address) + return configBytes, nil } - return configBytes, nil + // If we get here, all orderers failed + return nil, fmt.Errorf("failed to fetch channel config from any orderer: %w", lastErr) } // GetOrdererInfoFromConfig extracts orderer information from a channel config diff --git a/web/src/components/networks/FabricNetworkDetails.tsx b/web/src/components/networks/FabricNetworkDetails.tsx index f6cff02..cf9b940 100644 --- a/web/src/components/networks/FabricNetworkDetails.tsx +++ b/web/src/components/networks/FabricNetworkDetails.tsx @@ -34,6 +34,7 @@ import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs' import rehypeRaw from 'rehype-raw' import { toast } from 'sonner' import { AddMultipleNodesDialog } from './add-multiple-nodes-dialog' +import { ChannelUpdateForm } from '../nodes/ChannelUpdateForm' interface FabricNetworkDetailsProps { network: HttpNetworkResponse @@ -607,7 +608,14 @@ export default function FabricNetworkDetails({ network }: FabricNetworkDetailsPr } channelUpdate={
-

Coming Soon

+ { + refetchCurrentChannelConfig() + refetchNetworkNodes() + }} + />
} proposals={ diff --git a/web/src/components/networks/channel-update/SetAnchorPeersOperation.tsx b/web/src/components/networks/channel-update/SetAnchorPeersOperation.tsx deleted file mode 100644 index 64e71f3..0000000 --- a/web/src/components/networks/channel-update/SetAnchorPeersOperation.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useState } from 'react' -import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Input } from '@/components/ui/input' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Trash2, Plus } from 'lucide-react' -import { z } from 'zod' -import { useFieldArray, useFormContext } from 'react-hook-form' - -// Schema for the SetAnchorPeersPayload -export const setAnchorPeersSchema = z.object({ - msp_id: z.string().min(1, "MSP ID is required"), - anchor_peers: z.array( - z.object({ - host: z.string().min(1, "Host is required"), - port: z.number().int().positive("Port must be a positive integer") - }) - ).min(1, "At least one anchor peer is required") -}) - -export type SetAnchorPeersFormValues = z.infer - -interface SetAnchorPeersOperationProps { - index: number - onRemove: () => void -} - -export function SetAnchorPeersOperation({ index, onRemove }: SetAnchorPeersOperationProps) { - const formContext = useFormContext() - - const { fields: anchorPeersFields, append: appendAnchorPeer, remove: removeAnchorPeer } = - useFieldArray({ - name: `operations.${index}.payload.anchor_peers`, - control: formContext.control - }) - - const handleAddAnchorPeer = () => { - appendAnchorPeer({ host: '', port: 7051 }) - } - - return ( - - -
- Set Anchor Peers - -
-
- -
- ( - - MSP ID - - - - - - )} - /> - -
-
- Anchor Peers - -
- - {anchorPeersFields.map((field, i) => ( -
-
- ( - - Host - - - - - - )} - /> - - ( - - Port - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> -
- - -
- ))} - - {anchorPeersFields.length === 0 && ( -
-

No anchor peers added yet

- -
- )} -
-
-
-
- ) -} \ No newline at end of file diff --git a/web/src/components/networks/channel-update/UpdateBatchSizeOperation.tsx b/web/src/components/networks/channel-update/UpdateBatchSizeOperation.tsx deleted file mode 100644 index bd721a0..0000000 --- a/web/src/components/networks/channel-update/UpdateBatchSizeOperation.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Input } from '@/components/ui/input' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Trash2 } from 'lucide-react' -import { z } from 'zod' -import { useFormContext } from 'react-hook-form' - -// Schema for the UpdateBatchSizePayload -export const updateBatchSizeSchema = z.object({ - absolute_max_bytes: z.number().int().positive("Absolute max bytes must be a positive integer"), - max_message_count: z.number().int().positive("Max message count must be a positive integer"), - preferred_max_bytes: z.number().int().positive("Preferred max bytes must be a positive integer") -}) - -export type UpdateBatchSizeFormValues = z.infer - -interface UpdateBatchSizeOperationProps { - index: number - onRemove: () => void -} - -export function UpdateBatchSizeOperation({ index, onRemove }: UpdateBatchSizeOperationProps) { - const formContext = useFormContext() - - return ( - - -
- Update Batch Size - -
-
- -
- ( - - Absolute Max Bytes - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> - - ( - - Max Message Count - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> - - ( - - Preferred Max Bytes - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> -
-
-
- ) -} \ No newline at end of file diff --git a/web/src/components/networks/channel-update/UpdateBatchTimeoutOperation.tsx b/web/src/components/networks/channel-update/UpdateBatchTimeoutOperation.tsx deleted file mode 100644 index 60d036b..0000000 --- a/web/src/components/networks/channel-update/UpdateBatchTimeoutOperation.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Input } from '@/components/ui/input' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Trash2 } from 'lucide-react' -import { z } from 'zod' -import { useFormContext } from 'react-hook-form' - -// Schema for the UpdateBatchTimeoutPayload -export const updateBatchTimeoutSchema = z.object({ - timeout: z.string().min(1, "Timeout is required") -}) - -export type UpdateBatchTimeoutFormValues = z.infer - -interface UpdateBatchTimeoutOperationProps { - index: number - onRemove: () => void -} - -export function UpdateBatchTimeoutOperation({ index, onRemove }: UpdateBatchTimeoutOperationProps) { - const formContext = useFormContext() - - return ( - - -
- Update Batch Timeout - -
-
- -
- ( - - Timeout - - - - - Format examples: 500ms, 1s, 2m - - - )} - /> -
-
-
- ) -} \ No newline at end of file diff --git a/web/src/components/networks/channel-update/UpdateEtcdRaftOptionsOperation.tsx b/web/src/components/networks/channel-update/UpdateEtcdRaftOptionsOperation.tsx deleted file mode 100644 index 4217458..0000000 --- a/web/src/components/networks/channel-update/UpdateEtcdRaftOptionsOperation.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { Input } from '@/components/ui/input' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Trash2 } from 'lucide-react' -import { z } from 'zod' -import { useFormContext } from 'react-hook-form' - -// Schema for the UpdateEtcdRaftOptionsPayload -export const updateEtcdRaftOptionsSchema = z.object({ - election_tick: z.number().int().positive("Election tick must be a positive integer"), - heartbeat_tick: z.number().int().positive("Heartbeat tick must be a positive integer"), - max_inflight_blocks: z.number().int().positive("Max inflight blocks must be a positive integer"), - snapshot_interval_size: z.number().int().positive("Snapshot interval size must be a positive integer"), - tick_interval: z.string().min(1, "Tick interval is required") -}) - -export type UpdateEtcdRaftOptionsFormValues = z.infer - -interface UpdateEtcdRaftOptionsOperationProps { - index: number - onRemove: () => void -} - -export function UpdateEtcdRaftOptionsOperation({ index, onRemove }: UpdateEtcdRaftOptionsOperationProps) { - const formContext = useFormContext() - - return ( - - -
- Update Etcd Raft Options - -
-
- -
-
- ( - - Election Tick - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> - - ( - - Heartbeat Tick - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> -
- -
- ( - - Max Inflight Blocks - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> - - ( - - Snapshot Interval Size - - field.onChange(parseInt(e.target.value) || 0)} - /> - - - - )} - /> -
- - ( - - Tick Interval - - - - - - )} - /> -
-
-
- ) -} \ No newline at end of file diff --git a/web/src/components/networks/channel-update/index.ts b/web/src/components/networks/channel-update/index.ts deleted file mode 100644 index ff022e7..0000000 --- a/web/src/components/networks/channel-update/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './ChannelUpdateForm' -export * from './AddOrgOperation' -export * from './RemoveOrgOperation' -export * from './UpdateOrgMSPOperation' -export * from './SetAnchorPeersOperation' -export * from './AddConsenterOperation' -export * from './RemoveConsenterOperation' -export * from './UpdateConsenterOperation' -export * from './UpdateEtcdRaftOptionsOperation' -export * from './UpdateBatchSizeOperation' -export * from './UpdateBatchTimeoutOperation' \ No newline at end of file diff --git a/web/src/components/networks/network-tabs.tsx b/web/src/components/networks/network-tabs.tsx index 0a6a445..dfe83aa 100644 --- a/web/src/components/networks/network-tabs.tsx +++ b/web/src/components/networks/network-tabs.tsx @@ -20,33 +20,28 @@ interface NetworkTabsProps { export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consenters, chaincode, share, channelUpdate, proposals }: NetworkTabsProps) { // Check if current tab is a pro feature and redirect to details if needed useEffect(() => { - const proTabs: TabValue[] = ['share', 'channel-update', 'proposals']; + const proTabs: TabValue[] = ['share', 'proposals'] if (proTabs.includes(tab)) { - setTab('details'); + setTab('details') } - }, [tab, setTab]); + }, [tab, setTab]) // Handle tab change with pro feature check const handleTabChange = (value: string) => { - const proTabs: TabValue[] = ['share', 'channel-update', 'proposals']; - + const proTabs: TabValue[] = ['share', 'proposals'] + if (proTabs.includes(value as TabValue)) { // Don't change tab, it will be handled by the TabsTrigger onClick - return; + return } - - setTab(value as TabValue); - }; + + setTab(value as TabValue) + } // Render pro feature gate for specific tabs const renderProContent = (title: string, description: string) => { - return ( - - ); - }; + return + } return ( @@ -55,72 +50,67 @@ export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consente {anchorPeers && Anchor Peers} {consenters && Consenters} {chaincode && Chaincode} - + {channelUpdate && ( - window.open("https://chainlaunch.dev/premium", "_blank")} - className="flex items-center gap-2" - > - Channel Update - - + <> + + Channel Update + + )} - + {proposals && ( - window.open("https://chainlaunch.dev/premium", "_blank")} - className="flex items-center gap-2" - > + window.open('https://chainlaunch.dev/premium', '_blank')} className="flex items-center gap-2"> Proposals )} - + {share && ( - window.open("https://chainlaunch.dev/premium", "_blank")} - className="flex items-center gap-2" - > + window.open('https://chainlaunch.dev/premium', '_blank')} className="flex items-center gap-2"> Share )} - + {networkDetails} - - {anchorPeers && {anchorPeers}} - {consenters && {consenters}} - {chaincode && {chaincode}} - + + {anchorPeers && ( + + {anchorPeers} + + )} + {consenters && ( + + {consenters} + + )} + {chaincode && ( + + {chaincode} + + )} + {channelUpdate && ( - {renderProContent( - "Channel Update Pro Feature", - "Upgrade to ChainLaunch Pro to access advanced channel update capabilities, enabling seamless network configuration changes." - )} + {channelUpdate} )} - + {proposals && ( - {renderProContent( - "Proposals Pro Feature", - "Upgrade to ChainLaunch Pro to manage network proposals, enabling collaborative governance and decision-making across organizations." - )} + {renderProContent('Proposals Pro Feature', 'Upgrade to ChainLaunch Pro to manage network proposals, enabling collaborative governance and decision-making across organizations.')} )} - + {share && ( {renderProContent( - "Network Sharing Pro Feature", - "Upgrade to ChainLaunch Pro to share networks with other organizations, enabling cross-organizational collaboration and network participation." + 'Network Sharing Pro Feature', + 'Upgrade to ChainLaunch Pro to share networks with other organizations, enabling cross-organizational collaboration and network participation.' )} )} diff --git a/web/src/components/nodes/ChannelUpdateForm.tsx b/web/src/components/nodes/ChannelUpdateForm.tsx new file mode 100644 index 0000000..b280647 --- /dev/null +++ b/web/src/components/nodes/ChannelUpdateForm.tsx @@ -0,0 +1,254 @@ +import { HttpChannelResponse, HttpNodeResponse } from '@/api/client' +import { Button } from '@/components/ui/button' +import { Form } from '@/components/ui/form' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { zodResolver } from '@hookform/resolvers/zod' +import { useMutation } from '@tanstack/react-query' +import { Loader2, PlusCircle } from 'lucide-react' +import { useState } from 'react' +import { useFieldArray, useForm } from 'react-hook-form' +import { toast } from 'sonner' +import { z } from 'zod' + +// Import operation components +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { AlertCircle } from 'lucide-react' +import { AddOrgOperation, addOrgSchema } from './operations/AddOrgOperation' +import { RemoveOrgOperation, removeOrgSchema } from './operations/RemoveOrgOperation' +import { UpdateOrgMSPOperation, updateOrgMSPSchema } from './operations/UpdateOrgMSPOperation' +import { SetAnchorPeersOperation, setAnchorPeersSchema } from './operations/SetAnchorPeersOperation' +import { AddConsenterOperation, addConsenterSchema } from './operations/AddConsenterOperation' +import { RemoveConsenterOperation, removeConsenterSchema } from './operations/RemoveConsenterOperation' +import { UpdateConsenterOperation, updateConsenterSchema } from './operations/UpdateConsenterOperation' +import { UpdateEtcdRaftOptionsOperation, updateEtcdRaftOptionsSchema } from './operations/UpdateEtcdRaftOptionsOperation' +import { UpdateBatchSizeOperation, updateBatchSizeSchema } from './operations/UpdateBatchSizeOperation' +import { UpdateBatchTimeoutOperation, updateBatchTimeoutSchema } from './operations/UpdateBatchTimeoutOperation' +import { useNavigate } from 'react-router-dom' +import { postNetworksFabricByIdUpdateConfigMutation } from '@/api/client/@tanstack/react-query.gen' + +// Operation type mapping +const operationTypes = { + add_org: 'Add Organization', + remove_org: 'Remove Organization', + update_org_msp: 'Update Organization MSP', + add_consenter: 'Add Consenter', + remove_consenter: 'Remove Consenter', + update_consenter: 'Update Consenter', + update_etcd_raft_options: 'Update Etcd Raft Options', + update_batch_size: 'Update Batch Size', + update_batch_timeout: 'Update Batch Timeout', +} as const + +type OperationType = keyof typeof operationTypes + +// Define the schema for each operation type +const operationSchemaMap = { + add_org: addOrgSchema, + remove_org: removeOrgSchema, + update_org_msp: updateOrgMSPSchema, + set_anchor_peers: setAnchorPeersSchema, + add_consenter: addConsenterSchema, + remove_consenter: removeConsenterSchema, + update_consenter: updateConsenterSchema, + update_etcd_raft_options: updateEtcdRaftOptionsSchema, + update_batch_size: updateBatchSizeSchema, + update_batch_timeout: updateBatchTimeoutSchema, +} + +// Define default values for each operation type +const getDefaultPayloadForType = (type: OperationType) => { + switch (type) { + case 'add_org': + return { msp_id: '', root_certs: [], tls_root_certs: [] } + case 'remove_org': + return { msp_id: '' } + case 'update_org_msp': + return { msp_id: '', root_certs: [], tls_root_certs: [] } + case 'set_anchor_peers': + return { msp_id: '', anchor_peers: [] } + case 'add_consenter': + return { host: '', port: 7050, client_tls_cert: '', server_tls_cert: '' } + case 'remove_consenter': + return { host: '', port: 7050 } + case 'update_consenter': + return { host: '', port: 7050, new_host: '', new_port: 7050, client_tls_cert: '', server_tls_cert: '' } + case 'update_etcd_raft_options': + return { election_tick: 10, heartbeat_tick: 1, max_inflight_blocks: 5, snapshot_interval_size: 16777216, tick_interval: '500ms' } + case 'update_batch_size': + return { absolute_max_bytes: 10485760, max_message_count: 500, preferred_max_bytes: 2097152 } + case 'update_batch_timeout': + return { timeout: '2s' } + default: + return {} + } +} + +// Define the form schema +const formSchema = z.object({ + operations: z + .array( + z.object({ + type: z.enum([ + 'add_org', + 'remove_org', + 'update_org_msp', + 'set_anchor_peers', + 'add_consenter', + 'remove_consenter', + 'update_consenter', + 'update_etcd_raft_options', + 'update_batch_size', + 'update_batch_timeout', + ]), + payload: z.any(), + }) + ) + .min(1, 'At least one operation is required'), +}) + +type FormValues = z.infer + +interface ChannelUpdateFormProps { + network: HttpChannelResponse + onSuccess?: () => void + channelConfig?: any +} + +export function ChannelUpdateForm({ network, onSuccess, channelConfig }: ChannelUpdateFormProps) { + const [selectedOperationType, setSelectedOperationType] = useState('') + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + operations: [], + }, + }) + + const { fields, append, remove } = useFieldArray({ + control: form.control, + name: 'operations', + }) + + const navigate = useNavigate() + + const prepareUpdate = useMutation({ + ...postNetworksFabricByIdUpdateConfigMutation(), + onSuccess: (data) => { + toast.success('Channel updated') + if (onSuccess) onSuccess() + }, + onError: (error) => { + toast.error(`Failed to create channel update proposal: ${error.message}`) + }, + }) + + const handleAddOperation = () => { + if (!selectedOperationType) return + + append({ + type: selectedOperationType, + payload: getDefaultPayloadForType(selectedOperationType), + }) + + setSelectedOperationType('') + } + + const onSubmit = (data: FormValues) => { + // Convert the payload to the format expected by the API + const operations = data.operations.map((op) => { + // In a real implementation, we would need to convert the payload to the format expected by the API + // For now, we'll just pass it as is + return { + type: op.type, + payload: op.payload, + } + }) + + prepareUpdate.mutate({ + path: { id: Number(network.id) }, + body: { operations }, + }) + } + + const renderOperationComponent = (type: OperationType, index: number) => { + switch (type) { + case 'add_org': + return remove(index)} /> + case 'remove_org': + return remove(index)} /> + case 'update_org_msp': + return remove(index)} /> + case 'set_anchor_peers': + return remove(index)} channelConfig={channelConfig} /> + case 'add_consenter': + return remove(index)} /> + case 'remove_consenter': + return remove(index)} /> + case 'update_consenter': + return remove(index)} /> + case 'update_etcd_raft_options': + return remove(index)} channelConfig={channelConfig} /> + case 'update_batch_size': + return remove(index)} channelConfig={channelConfig} /> + case 'update_batch_timeout': + return remove(index)} channelConfig={channelConfig} /> + default: + return null + } + } + + return ( +
+ +
+
+

Channel Update Proposal

+

Create a proposal to update the channel configuration. You can add multiple operations to be included in a single proposal.

+
+
+ {fields.length === 0 && ( + + + No operations added + Add at least one operation to create a channel update proposal. + + )} + + {fields.map((field, index) => renderOperationComponent(field.type as OperationType, index))} + +
+
+ + +
+ +
+
+
+ + +
+
+
+ + ) +} diff --git a/web/src/components/networks/channel-update/AddConsenterOperation.tsx b/web/src/components/nodes/operations/AddConsenterOperation.tsx similarity index 100% rename from web/src/components/networks/channel-update/AddConsenterOperation.tsx rename to web/src/components/nodes/operations/AddConsenterOperation.tsx diff --git a/web/src/components/networks/channel-update/AddOrgOperation.tsx b/web/src/components/nodes/operations/AddOrgOperation.tsx similarity index 85% rename from web/src/components/networks/channel-update/AddOrgOperation.tsx rename to web/src/components/nodes/operations/AddOrgOperation.tsx index e854abe..b78d49d 100644 --- a/web/src/components/networks/channel-update/AddOrgOperation.tsx +++ b/web/src/components/nodes/operations/AddOrgOperation.tsx @@ -1,11 +1,12 @@ -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { useState } from 'react' import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' import { Trash2 } from 'lucide-react' -import { useState } from 'react' -import { useFieldArray, useFormContext } from 'react-hook-form' import { z } from 'zod' +import { useFieldArray, useFormContext } from 'react-hook-form' // Schema for the AddOrgPayload export const addOrgSchema = z.object({ @@ -87,32 +88,35 @@ export function AddOrgOperation({ index, onRemove }: AddOrgOperationProps) { Root Certificates {rootCertsFields.map((field, i) => (
-
))}
- setNewRootCert(e.target.value)} placeholder="Paste PEM certificate" - className="flex-1" + className="flex-1 min-h-[100px]" /> @@ -123,32 +127,35 @@ export function AddOrgOperation({ index, onRemove }: AddOrgOperationProps) { TLS Root Certificates {tlsRootCertsFields.map((field, i) => (
-
))}
- setNewTlsRootCert(e.target.value)} placeholder="Paste PEM certificate" - className="flex-1" + className="flex-1 min-h-[100px]" /> diff --git a/web/src/components/networks/channel-update/RemoveConsenterOperation.tsx b/web/src/components/nodes/operations/RemoveConsenterOperation.tsx similarity index 100% rename from web/src/components/networks/channel-update/RemoveConsenterOperation.tsx rename to web/src/components/nodes/operations/RemoveConsenterOperation.tsx diff --git a/web/src/components/networks/channel-update/RemoveOrgOperation.tsx b/web/src/components/nodes/operations/RemoveOrgOperation.tsx similarity index 100% rename from web/src/components/networks/channel-update/RemoveOrgOperation.tsx rename to web/src/components/nodes/operations/RemoveOrgOperation.tsx diff --git a/web/src/components/nodes/operations/SetAnchorPeersOperation.tsx b/web/src/components/nodes/operations/SetAnchorPeersOperation.tsx new file mode 100644 index 0000000..18c67ce --- /dev/null +++ b/web/src/components/nodes/operations/SetAnchorPeersOperation.tsx @@ -0,0 +1,122 @@ +import { Button } from '@/components/ui/button' +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { useFormContext } from 'react-hook-form' +import { X } from 'lucide-react' +import { z } from 'zod' +import React from 'react' + +export const setAnchorPeersSchema = z.object({ + msp_id: z.string().min(1, 'MSP ID is required'), + anchor_peers: z.array( + z.object({ + host: z.string().min(1, 'Host is required'), + port: z.number().int().positive('Port must be a positive integer'), + }) + ).min(1, 'At least one anchor peer is required'), +}) + +interface AnchorPeer { + host: string + port: number +} + +interface SetAnchorPeersOperationProps { + index: number + onRemove: () => void + channelConfig?: { + anchor_peers?: AnchorPeer[] + } +} + +export function SetAnchorPeersOperation({ index, onRemove, channelConfig }: SetAnchorPeersOperationProps) { + const form = useFormContext() + + // Set the default values from channel config if available + React.useEffect(() => { + if (channelConfig?.anchor_peers) { + form.setValue(`operations.${index}.payload.anchor_peers`, channelConfig.anchor_peers) + } + }, [channelConfig?.anchor_peers, form, index]) + + return ( +
+
+

Set Anchor Peers

+ +
+ ( + + MSP ID + + + + + + )} + /> + ( + + Anchor Peers + +
+ {(field.value as AnchorPeer[])?.map((peer: AnchorPeer, peerIndex: number) => ( +
+ { + const newPeers = [...field.value as AnchorPeer[]] + newPeers[peerIndex] = { ...newPeers[peerIndex], host: e.target.value } + field.onChange(newPeers) + }} + /> + { + const newPeers = [...field.value as AnchorPeer[]] + newPeers[peerIndex] = { ...newPeers[peerIndex], port: parseInt(e.target.value) || 0 } + field.onChange(newPeers) + }} + /> + +
+ ))} + +
+
+ +
+ )} + /> +
+ ) +} \ No newline at end of file diff --git a/web/src/components/nodes/operations/UpdateBatchSizeOperation.tsx b/web/src/components/nodes/operations/UpdateBatchSizeOperation.tsx new file mode 100644 index 0000000..ff6ade8 --- /dev/null +++ b/web/src/components/nodes/operations/UpdateBatchSizeOperation.tsx @@ -0,0 +1,87 @@ +import { Button } from '@/components/ui/button' +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { useFormContext } from 'react-hook-form' +import { X } from 'lucide-react' +import { z } from 'zod' +import React, { useEffect } from 'react' + +// Schema for the UpdateBatchSizePayload +export const updateBatchSizeSchema = z.object({ + absolute_max_bytes: z.number().int().positive('Absolute max bytes must be a positive integer'), + max_message_count: z.number().int().positive('Max message count must be a positive integer'), + preferred_max_bytes: z.number().int().positive('Preferred max bytes must be a positive integer'), +}) + +export type UpdateBatchSizeFormValues = z.infer + +interface UpdateBatchSizeOperationProps { + index: number + onRemove: () => void + channelConfig?: any +} + +export function UpdateBatchSizeOperation({ index, onRemove, channelConfig }: UpdateBatchSizeOperationProps) { + const form = useFormContext() + + // Set the default values from channel config if available + useEffect(() => { + const batchSize = channelConfig?.config.data.data[0].payload.data.config.channel_group?.groups?.Orderer?.values?.BatchSize?.value + if (batchSize) { + const { absolute_max_bytes, max_message_count, preferred_max_bytes } = batchSize + if (absolute_max_bytes) form.setValue(`operations.${index}.payload.absolute_max_bytes`, absolute_max_bytes) + if (max_message_count) form.setValue(`operations.${index}.payload.max_message_count`, max_message_count) + if (preferred_max_bytes) form.setValue(`operations.${index}.payload.preferred_max_bytes`, preferred_max_bytes) + } + }, [channelConfig]) + + return ( +
+
+

Update Batch Size

+ +
+ ( + + Absolute Max Bytes + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Max Message Count + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Preferred Max Bytes + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> +
+ ) +} \ No newline at end of file diff --git a/web/src/components/nodes/operations/UpdateBatchTimeoutOperation.tsx b/web/src/components/nodes/operations/UpdateBatchTimeoutOperation.tsx new file mode 100644 index 0000000..6d7cd86 --- /dev/null +++ b/web/src/components/nodes/operations/UpdateBatchTimeoutOperation.tsx @@ -0,0 +1,56 @@ +import { Button } from '@/components/ui/button' +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { useFormContext } from 'react-hook-form' +import { X } from 'lucide-react' +import { z } from 'zod' +import React, { useEffect } from 'react' + +// Schema for the UpdateBatchTimeoutPayload +export const updateBatchTimeoutSchema = z.object({ + timeout: z.string().min(1, 'Timeout is required'), +}) + +export type UpdateBatchTimeoutFormValues = z.infer + +interface UpdateBatchTimeoutOperationProps { + index: number + onRemove: () => void + channelConfig?: any +} + +export function UpdateBatchTimeoutOperation({ index, onRemove, channelConfig }: UpdateBatchTimeoutOperationProps) { + const form = useFormContext() + // Set the default value from channel config if available + useEffect(() => { + const batchTimeout = channelConfig?.config.data.data[0].payload.data.config.channel_group?.groups?.Orderer?.values?.BatchTimeout?.value?.timeout + + if (batchTimeout) { + form.setValue(`operations.${index}.payload.timeout`, batchTimeout) + } + }, [channelConfig]) + + return ( +
+
+

Update Batch Timeout

+ +
+ ( + + Timeout + + + + + + )} + /> +
+ ) +} diff --git a/web/src/components/networks/channel-update/UpdateConsenterOperation.tsx b/web/src/components/nodes/operations/UpdateConsenterOperation.tsx similarity index 100% rename from web/src/components/networks/channel-update/UpdateConsenterOperation.tsx rename to web/src/components/nodes/operations/UpdateConsenterOperation.tsx diff --git a/web/src/components/nodes/operations/UpdateEtcdRaftOptionsOperation.tsx b/web/src/components/nodes/operations/UpdateEtcdRaftOptionsOperation.tsx new file mode 100644 index 0000000..9672447 --- /dev/null +++ b/web/src/components/nodes/operations/UpdateEtcdRaftOptionsOperation.tsx @@ -0,0 +1,124 @@ +import { Button } from '@/components/ui/button' +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { useFormContext } from 'react-hook-form' +import { X } from 'lucide-react' +import { z } from 'zod' +import React from 'react' + +// Schema for the UpdateEtcdRaftOptionsPayload +export const updateEtcdRaftOptionsSchema = z.object({ + election_tick: z.number().int().positive('Election tick must be a positive integer'), + heartbeat_tick: z.number().int().positive('Heartbeat tick must be a positive integer'), + max_inflight_blocks: z.number().int().positive('Max inflight blocks must be a positive integer'), + snapshot_interval_size: z.number().int().positive('Snapshot interval size must be a positive integer'), + tick_interval: z.string().min(1, 'Tick interval is required'), +}) + +export type UpdateEtcdRaftOptionsFormValues = z.infer + +interface UpdateEtcdRaftOptionsOperationProps { + index: number + onRemove: () => void + channelConfig?: { + etcd_raft?: { + election_tick?: number + heartbeat_tick?: number + max_inflight_blocks?: number + snapshot_interval_size?: number + tick_interval?: string + } + } +} + +export function UpdateEtcdRaftOptionsOperation({ index, onRemove, channelConfig }: UpdateEtcdRaftOptionsOperationProps) { + const form = useFormContext() + + // Set the default values from channel config if available + React.useEffect(() => { + if (channelConfig?.etcd_raft) { + const { election_tick, heartbeat_tick, max_inflight_blocks, snapshot_interval_size, tick_interval } = channelConfig.etcd_raft + if (election_tick) form.setValue(`operations.${index}.payload.election_tick`, election_tick) + if (heartbeat_tick) form.setValue(`operations.${index}.payload.heartbeat_tick`, heartbeat_tick) + if (max_inflight_blocks) form.setValue(`operations.${index}.payload.max_inflight_blocks`, max_inflight_blocks) + if (snapshot_interval_size) form.setValue(`operations.${index}.payload.snapshot_interval_size`, snapshot_interval_size) + if (tick_interval) form.setValue(`operations.${index}.payload.tick_interval`, tick_interval) + } + }, [channelConfig?.etcd_raft, form, index]) + + return ( +
+
+

Update Etcd Raft Options

+ +
+ ( + + Election Tick + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Heartbeat Tick + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Max Inflight Blocks + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Snapshot Interval Size + + field.onChange(parseInt(e.target.value) || 0)} /> + + + + )} + /> + ( + + Tick Interval + + + + + + )} + /> +
+ ) +} \ No newline at end of file diff --git a/web/src/components/networks/channel-update/UpdateOrgMSPOperation.tsx b/web/src/components/nodes/operations/UpdateOrgMSPOperation.tsx similarity index 100% rename from web/src/components/networks/channel-update/UpdateOrgMSPOperation.tsx rename to web/src/components/nodes/operations/UpdateOrgMSPOperation.tsx From 63ee9e481de2700adc38cb4d9fe8ed324157785d Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Tue, 8 Apr 2025 11:11:48 +0200 Subject: [PATCH 12/31] Add basic blocks API Signed-off-by: dviejokfs --- cmd/fabric/install/install.go | 43 +-- cmd/fabric/invoke/invoke.go | 53 +--- cmd/fabric/query/query.go | 24 +- docs/docs.go | 263 +++++++++++++++++- docs/swagger.json | 261 +++++++++++++++++ docs/swagger.yaml | 173 ++++++++++++ pkg/networks/http/handler.go | 149 ++++++++++ pkg/networks/http/types.go | 17 ++ pkg/networks/service/fabric/deployer.go | 260 +++++++++++++++++ pkg/networks/service/fabric/org/org.go | 4 +- pkg/networks/service/fabric/types.go | 65 +++++ pkg/networks/service/service.go | 105 +++++++ pkg/networks/service/types.go | 24 ++ pkg/nodes/peer/peer.go | 198 ++++++++++++- pkg/nodes/peer/service.go | 2 +- pkg/nodes/peer/types.go | 6 + .../api/client/@tanstack/react-query.gen.ts | 89 +++++- web/src/api/client/sdk.gen.ts | 35 ++- web/src/api/client/types.gen.ts | 201 +++++++++++-- .../networks/FabricNetworkDetails.tsx | 18 +- .../components/networks/block-explorer.tsx | 66 +++++ web/src/components/networks/network-tabs.tsx | 17 +- 22 files changed, 1932 insertions(+), 141 deletions(-) create mode 100644 web/src/components/networks/block-explorer.tsx diff --git a/cmd/fabric/install/install.go b/cmd/fabric/install/install.go index 401ae95..5cfe5b2 100644 --- a/cmd/fabric/install/install.go +++ b/cmd/fabric/install/install.go @@ -5,9 +5,7 @@ import ( "bytes" "compress/gzip" "context" - "crypto/x509" "encoding/json" - "encoding/pem" "fmt" "io/ioutil" "os" @@ -26,6 +24,7 @@ import ( "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-admin-sdk/pkg/identity" "github.com/hyperledger/fabric-admin-sdk/pkg/network" + gwidentity "github.com/hyperledger/fabric-gateway/pkg/identity" pb "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/spf13/cobra" ) @@ -65,15 +64,15 @@ func (c *installCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkConfig, o if !ok { return nil, nil, fmt.Errorf("user %s not found in network config", userID) } - userCert, err := identity.ReadCertificate(user.Cert.PEM) + userCert, err := gwidentity.CertificateFromPEM([]byte(user.Cert.PEM)) if err != nil { return nil, nil, errors.Wrapf(err, "failed to read user certificate for user %s and org %s", userID, org) } - userPrivateKey, err := identity.ReadPrivateKey(user.Key.PEM) + userPrivateKey, err := gwidentity.PrivateKeyFromPEM([]byte(user.Key.PEM)) if err != nil { return nil, nil, errors.Wrapf(err, "failed to read user private key for user %s and org %s", userID, org) } - userIdentity, err := identity.NewPrivateKeySigningIdentity(user.Cert.PEM, userCert, userPrivateKey) + userIdentity, err := identity.NewPrivateKeySigningIdentity(org, userCert, userPrivateKey) if err != nil { return nil, nil, errors.Wrapf(err, "failed to create user identity for user %s and org %s", userID, org) } @@ -81,30 +80,9 @@ func (c *installCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkConfig, o } func (c *installCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { - // Parse the TLS CA certificate - if tlsCACert == "" { - return nil, fmt.Errorf("TLS CA certificate is required") - } - // Read the certificate file - certBytes, err := os.ReadFile(tlsCACert) - if err != nil { - return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) - } - - // Decode the PEM block - block, _ := pem.Decode(certBytes) - if block == nil { - return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") - } - // Parse the certificate - _, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) - } - networkNode := network.Node{ - Addr: address, - TLSCACert: tlsCACert, + Addr: strings.Replace(address, "grpcs://", "", 1), + TLSCACertByte: []byte(tlsCACert), } conn, err := network.DialConnection(networkNode) if err != nil { @@ -179,10 +157,14 @@ func (c installCmd) start() error { defer conn.Close() peerClient := chaincode.NewPeer(conn, userIdentity) result, err := peerClient.Install(ctx, bytes.NewReader(pkg)) - if err != nil { + if err != nil && !strings.Contains(err.Error(), "chaincode already successfully installed") { return errors.Wrapf(err, "failed to install chaincode for user %s and org %s", c.users[idx], org) } - c.logger.Infof("Chaincode installed %s in %s", result.PackageId, peerConfig.URL) + if result != nil { + c.logger.Infof("Chaincode installed %s in %s", result.PackageId, peerConfig.URL) + } else { + c.logger.Infof("Chaincode already installed in %s", peerConfig.URL) + } } } @@ -326,7 +308,6 @@ func (c installCmd) start() error { c.logger.Errorf("Error when approving chaincode: %v", err) return err } - c.logger.Infof("Chaincode approved, org=%s", c.organizations[idx]) if err != nil && !strings.Contains(err.Error(), "redefine uncommitted") { c.logger.Errorf("Error when approving chaincode: %v", err) return err diff --git a/cmd/fabric/invoke/invoke.go b/cmd/fabric/invoke/invoke.go index 653355e..cb26a35 100644 --- a/cmd/fabric/invoke/invoke.go +++ b/cmd/fabric/invoke/invoke.go @@ -1,12 +1,10 @@ package invoke import ( - "crypto/x509" - "encoding/pem" "fmt" "io" "math/rand/v2" - "os" + "strings" "github.com/chainlaunch/chainlaunch/pkg/fabric/networkconfig" "github.com/chainlaunch/chainlaunch/pkg/logger" @@ -70,30 +68,10 @@ func (c *invokeChaincodeCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkC } func (c *invokeChaincodeCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { - // Parse the TLS CA certificate - if tlsCACert == "" { - return nil, fmt.Errorf("TLS CA certificate is required") - } - // Read the certificate file - certBytes, err := os.ReadFile(tlsCACert) - if err != nil { - return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) - } - - // Decode the PEM block - block, _ := pem.Decode(certBytes) - if block == nil { - return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") - } - // Parse the certificate - _, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) - } networkNode := network.Node{ - Addr: address, - TLSCACert: tlsCACert, + Addr: strings.Replace(address, "grpcs://", "", 1), + TLSCACertByte: []byte(tlsCACert), } conn, err := network.DialConnection(networkNode) if err != nil { @@ -167,32 +145,9 @@ func (c *invokeChaincodeCmd) run(out io.Writer) error { if err != nil { return err } - c.logger.Infof("txid=%s", submitResponse.TransactionID) + c.logger.Infof("txid=%s", submitResponse.TransactionID()) return nil - // var args [][]byte - // for _, arg := range c.args { - // args = append(args, []byte(arg)) - // } - // response, err := ch.Execute( - // channel.Request{ - // ChaincodeID: c.chaincode, - // Fcn: c.fcn, - // Args: args, - // TransientMap: nil, - // InvocationChain: nil, - // IsInit: false, - // }, - // ) - // if err != nil { - // return err - // } - // _, err = fmt.Fprint(out, string(response.Payload)) - // if err != nil { - // return err - // } - // c.logger.Infof("txid=%s", response.TransactionID) - // return nil } func NewInvokeChaincodeCMD(out io.Writer, errOut io.Writer, logger *logger.Logger) *cobra.Command { diff --git a/cmd/fabric/query/query.go b/cmd/fabric/query/query.go index f8d9829..20a405c 100644 --- a/cmd/fabric/query/query.go +++ b/cmd/fabric/query/query.go @@ -1,12 +1,10 @@ package query import ( - "crypto/x509" - "encoding/pem" "fmt" "io" "math/rand/v2" - "os" + "strings" "github.com/chainlaunch/chainlaunch/pkg/fabric/networkconfig" "github.com/chainlaunch/chainlaunch/pkg/logger" @@ -70,26 +68,10 @@ func (c *queryChaincodeCmd) getPeerAndIdentityForOrg(nc *networkconfig.NetworkCo } func (c *queryChaincodeCmd) getPeerConnection(address string, tlsCACert string) (*grpc.ClientConn, error) { - if tlsCACert == "" { - return nil, fmt.Errorf("TLS CA certificate is required") - } - certBytes, err := os.ReadFile(tlsCACert) - if err != nil { - return nil, fmt.Errorf("failed to read TLS CA certificate file: %w", err) - } - - block, _ := pem.Decode(certBytes) - if block == nil { - return nil, fmt.Errorf("failed to decode PEM block from TLS CA certificate") - } - _, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse TLS CA certificate: %w", err) - } networkNode := network.Node{ - Addr: address, - TLSCACert: tlsCACert, + Addr: strings.Replace(address, "grpcs://", "", 1), + TLSCACertByte: []byte(tlsCACert), } conn, err := network.DialConnection(networkNode) if err != nil { diff --git a/docs/docs.go b/docs/docs.go index f6951fd..82e9f88 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Package docs Code generated by swaggo/swag at 2025-04-05 22:02:23.040823 +0200 CEST m=+1.676072542. DO NOT EDIT +// Package docs Code generated by swaggo/swag at 2025-04-07 17:07:59.846415 +0200 CEST m=+1.603514084. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -2146,6 +2146,125 @@ const docTemplate = `{ } } }, + "/networks/fabric/{id}/blocks": { + "get": { + "description": "Get a paginated list of blocks from a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get list of blocks from Fabric network", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Number of blocks to return (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of blocks to skip (default: 0)", + "name": "offset", + "in": "query" + }, + { + "type": "boolean", + "description": "Get blocks in reverse order (default: false)", + "name": "reverse", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.BlockListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, + "/networks/fabric/{id}/blocks/{blockNum}/transactions": { + "get": { + "description": "Get all transactions from a specific block in a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get transactions from a specific block", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Block Number", + "name": "blockNum", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.BlockTransactionsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/networks/fabric/{id}/channel-config": { "get": { "description": "Retrieve the channel configuration for a Fabric network", @@ -2748,6 +2867,60 @@ const docTemplate = `{ } } }, + "/networks/fabric/{id}/transactions/{txId}": { + "get": { + "description": "Get detailed information about a specific transaction in a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get transaction details by transaction ID", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Transaction ID", + "name": "txId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.TransactionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/networks/fabric/{id}/update-config": { "post": { "description": "Prepare a config update proposal for a Fabric network using the provided operations.\nThe following operation types are supported:\n- add_org: Add a new organization to the channel\n- remove_org: Remove an organization from the channel\n- update_org_msp: Update an organization's MSP configuration\n- set_anchor_peers: Set anchor peers for an organization\n- add_consenter: Add a new consenter to the orderer\n- remove_consenter: Remove a consenter from the orderer\n- update_consenter: Update a consenter in the orderer\n- update_etcd_raft_options: Update etcd raft options for the orderer\n- update_batch_size: Update batch size for the orderer\n- update_batch_timeout: Update batch timeout for the orderer", @@ -4529,6 +4702,34 @@ const docTemplate = `{ } } }, + "http.BlockListResponse": { + "type": "object", + "properties": { + "blocks": { + "type": "array", + "items": { + "$ref": "#/definitions/service.Block" + } + }, + "total": { + "type": "integer" + } + } + }, + "http.BlockTransactionsResponse": { + "type": "object", + "properties": { + "block_number": { + "type": "integer" + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/definitions/service.Transaction" + } + } + } + }, "http.ChannelConfigResponse": { "type": "object", "properties": { @@ -5432,6 +5633,14 @@ const docTemplate = `{ } } }, + "http.TransactionResponse": { + "type": "object", + "properties": { + "transaction": { + "$ref": "#/definitions/service.Transaction" + } + } + }, "http.UpdateBackupScheduleRequest": { "type": "object", "required": [ @@ -6165,6 +6374,32 @@ const docTemplate = `{ } } }, + "service.Block": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + }, + "hash": { + "type": "string" + }, + "number": { + "type": "integer" + }, + "previous_hash": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "tx_count": { + "type": "integer" + } + } + }, "service.FabricOrdererProperties": { "type": "object", "properties": { @@ -6414,6 +6649,32 @@ const docTemplate = `{ } } }, + "service.Transaction": { + "type": "object", + "properties": { + "block_number": { + "type": "integer" + }, + "creator": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "type": "integer" + } + }, + "timestamp": { + "type": "string" + }, + "tx_id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "types.BesuNodeConfig": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index c3395dc..425308b 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2144,6 +2144,125 @@ } } }, + "/networks/fabric/{id}/blocks": { + "get": { + "description": "Get a paginated list of blocks from a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get list of blocks from Fabric network", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Number of blocks to return (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of blocks to skip (default: 0)", + "name": "offset", + "in": "query" + }, + { + "type": "boolean", + "description": "Get blocks in reverse order (default: false)", + "name": "reverse", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.BlockListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, + "/networks/fabric/{id}/blocks/{blockNum}/transactions": { + "get": { + "description": "Get all transactions from a specific block in a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get transactions from a specific block", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Block Number", + "name": "blockNum", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.BlockTransactionsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/networks/fabric/{id}/channel-config": { "get": { "description": "Retrieve the channel configuration for a Fabric network", @@ -2746,6 +2865,60 @@ } } }, + "/networks/fabric/{id}/transactions/{txId}": { + "get": { + "description": "Get detailed information about a specific transaction in a Fabric network", + "produces": [ + "application/json" + ], + "tags": [ + "fabric-networks" + ], + "summary": "Get transaction details by transaction ID", + "parameters": [ + { + "type": "integer", + "description": "Network ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Transaction ID", + "name": "txId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.TransactionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse" + } + } + } + } + }, "/networks/fabric/{id}/update-config": { "post": { "description": "Prepare a config update proposal for a Fabric network using the provided operations.\nThe following operation types are supported:\n- add_org: Add a new organization to the channel\n- remove_org: Remove an organization from the channel\n- update_org_msp: Update an organization's MSP configuration\n- set_anchor_peers: Set anchor peers for an organization\n- add_consenter: Add a new consenter to the orderer\n- remove_consenter: Remove a consenter from the orderer\n- update_consenter: Update a consenter in the orderer\n- update_etcd_raft_options: Update etcd raft options for the orderer\n- update_batch_size: Update batch size for the orderer\n- update_batch_timeout: Update batch timeout for the orderer", @@ -4527,6 +4700,34 @@ } } }, + "http.BlockListResponse": { + "type": "object", + "properties": { + "blocks": { + "type": "array", + "items": { + "$ref": "#/definitions/service.Block" + } + }, + "total": { + "type": "integer" + } + } + }, + "http.BlockTransactionsResponse": { + "type": "object", + "properties": { + "block_number": { + "type": "integer" + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/definitions/service.Transaction" + } + } + } + }, "http.ChannelConfigResponse": { "type": "object", "properties": { @@ -5430,6 +5631,14 @@ } } }, + "http.TransactionResponse": { + "type": "object", + "properties": { + "transaction": { + "$ref": "#/definitions/service.Transaction" + } + } + }, "http.UpdateBackupScheduleRequest": { "type": "object", "required": [ @@ -6163,6 +6372,32 @@ } } }, + "service.Block": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + }, + "hash": { + "type": "string" + }, + "number": { + "type": "integer" + }, + "previous_hash": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "tx_count": { + "type": "integer" + } + } + }, "service.FabricOrdererProperties": { "type": "object", "properties": { @@ -6412,6 +6647,32 @@ } } }, + "service.Transaction": { + "type": "object", + "properties": { + "block_number": { + "type": "integer" + }, + "creator": { + "type": "string" + }, + "payload": { + "type": "array", + "items": { + "type": "integer" + } + }, + "timestamp": { + "type": "string" + }, + "tx_id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "types.BesuNodeConfig": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c56e856..c2a7aa6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -261,6 +261,24 @@ definitions: updatedAt: type: string type: object + http.BlockListResponse: + properties: + blocks: + items: + $ref: '#/definitions/service.Block' + type: array + total: + type: integer + type: object + http.BlockTransactionsResponse: + properties: + block_number: + type: integer + transactions: + items: + $ref: '#/definitions/service.Transaction' + type: array + type: object http.ChannelConfigResponse: properties: config: @@ -948,6 +966,11 @@ definitions: testedAt: type: string type: object + http.TransactionResponse: + properties: + transaction: + $ref: '#/definitions/service.Transaction' + type: object http.UpdateBackupScheduleRequest: properties: cronExpression: @@ -1458,6 +1481,23 @@ definitions: rpcPort: type: integer type: object + service.Block: + properties: + data: + items: + type: integer + type: array + hash: + type: string + number: + type: integer + previous_hash: + type: string + timestamp: + type: string + tx_count: + type: integer + type: object service.FabricOrdererProperties: properties: adminAddress: @@ -1625,6 +1665,23 @@ definitions: $ref: '#/definitions/service.NodeDefaults' type: array type: object + service.Transaction: + properties: + block_number: + type: integer + creator: + type: string + payload: + items: + type: integer + type: array + timestamp: + type: string + tx_id: + type: string + type: + type: string + type: object types.BesuNodeConfig: properties: bootNodes: @@ -3223,6 +3280,85 @@ paths: summary: Set anchor peers for an organization tags: - fabric-networks + /networks/fabric/{id}/blocks: + get: + description: Get a paginated list of blocks from a Fabric network + parameters: + - description: Network ID + in: path + name: id + required: true + type: integer + - description: 'Number of blocks to return (default: 10)' + in: query + name: limit + type: integer + - description: 'Number of blocks to skip (default: 0)' + in: query + name: offset + type: integer + - description: 'Get blocks in reverse order (default: false)' + in: query + name: reverse + type: boolean + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.BlockListResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + summary: Get list of blocks from Fabric network + tags: + - fabric-networks + /networks/fabric/{id}/blocks/{blockNum}/transactions: + get: + description: Get all transactions from a specific block in a Fabric network + parameters: + - description: Network ID + in: path + name: id + required: true + type: integer + - description: Block Number + in: path + name: blockNum + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.BlockTransactionsResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + summary: Get transactions from a specific block + tags: + - fabric-networks /networks/fabric/{id}/channel-config: get: description: Retrieve the channel configuration for a Fabric network @@ -3623,6 +3759,43 @@ paths: summary: Reload network config block tags: - fabric-networks + /networks/fabric/{id}/transactions/{txId}: + get: + description: Get detailed information about a specific transaction in a Fabric + network + parameters: + - description: Network ID + in: path + name: id + required: true + type: integer + - description: Transaction ID + in: path + name: txId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.TransactionResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_chainlaunch_chainlaunch_pkg_networks_http.ErrorResponse' + summary: Get transaction details by transaction ID + tags: + - fabric-networks /networks/fabric/{id}/update-config: post: consumes: diff --git a/pkg/networks/http/handler.go b/pkg/networks/http/handler.go index 89887ec..0b7fad8 100644 --- a/pkg/networks/http/handler.go +++ b/pkg/networks/http/handler.go @@ -59,6 +59,9 @@ func (h *Handler) RegisterRoutes(r chi.Router) { r.Post("/import", h.ImportFabricNetwork) r.Post("/import-with-org", h.ImportFabricNetworkWithOrg) r.Post("/{id}/update-config", h.FabricUpdateChannelConfig) + r.Get("/{id}/blocks", h.FabricGetBlocks) + r.Get("/{id}/blocks/{blockNum}/transactions", h.FabricGetBlockTransactions) + r.Get("/{id}/transactions/{txId}", h.FabricGetTransaction) }) // New Besu routes @@ -1615,3 +1618,149 @@ type ConfigUpdateResponse struct { Operations []ConfigUpdateOperationRequest `json:"operations"` PreviewJSON string `json:"preview_json,omitempty"` } + +// @Summary Get list of blocks from Fabric network +// @Description Get a paginated list of blocks from a Fabric network +// @Tags fabric-networks +// @Produce json +// @Param id path int true "Network ID" +// @Param limit query int false "Number of blocks to return (default: 10)" +// @Param offset query int false "Number of blocks to skip (default: 0)" +// @Param reverse query bool false "Get blocks in reverse order (default: false)" +// @Success 200 {object} BlockListResponse +// @Failure 400 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /networks/fabric/{id}/blocks [get] +func (h *Handler) FabricGetBlocks(w http.ResponseWriter, r *http.Request) { + networkID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_network_id", "Invalid network ID") + return + } + + // Parse pagination parameters + limit := int32(10) // Default limit + offset := int32(0) // Default offset + reverse := false // Default order + + if limitStr := r.URL.Query().Get("limit"); limitStr != "" { + limitInt, err := strconv.ParseInt(limitStr, 10, 32) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_limit", "Invalid limit parameter") + return + } + limit = int32(limitInt) + } + + if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" { + offsetInt, err := strconv.ParseInt(offsetStr, 10, 32) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_offset", "Invalid offset parameter") + return + } + offset = int32(offsetInt) + } + + if reverseStr := r.URL.Query().Get("reverse"); reverseStr != "" { + reverseBool, err := strconv.ParseBool(reverseStr) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_reverse", "Invalid reverse parameter") + return + } + reverse = reverseBool + } + + blocks, total, err := h.networkService.GetBlocks(r.Context(), networkID, limit, offset, reverse) + if err != nil { + writeError(w, http.StatusInternalServerError, "get_blocks_failed", err.Error()) + return + } + + resp := BlockListResponse{ + Blocks: blocks, + Total: total, + } + writeJSON(w, http.StatusOK, resp) +} + +// @Summary Get transactions from a specific block +// @Description Get all transactions from a specific block in a Fabric network +// @Tags fabric-networks +// @Produce json +// @Param id path int true "Network ID" +// @Param blockNum path int true "Block Number" +// @Success 200 {object} BlockTransactionsResponse +// @Failure 400 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /networks/fabric/{id}/blocks/{blockNum}/transactions [get] +func (h *Handler) FabricGetBlockTransactions(w http.ResponseWriter, r *http.Request) { + networkID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_network_id", "Invalid network ID") + return + } + + blockNum, err := strconv.ParseUint(chi.URLParam(r, "blockNum"), 10, 64) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_block_number", "Invalid block number") + return + } + + transactions, err := h.networkService.GetBlockTransactions(r.Context(), networkID, blockNum) + if err != nil { + if err.Error() == "block not found" { + writeError(w, http.StatusNotFound, "block_not_found", "Block not found") + return + } + writeError(w, http.StatusInternalServerError, "get_transactions_failed", err.Error()) + return + } + + resp := BlockTransactionsResponse{ + BlockNumber: blockNum, + Transactions: transactions, + } + writeJSON(w, http.StatusOK, resp) +} + +// @Summary Get transaction details by transaction ID +// @Description Get detailed information about a specific transaction in a Fabric network +// @Tags fabric-networks +// @Produce json +// @Param id path int true "Network ID" +// @Param txId path string true "Transaction ID" +// @Success 200 {object} TransactionResponse +// @Failure 400 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /networks/fabric/{id}/transactions/{txId} [get] +func (h *Handler) FabricGetTransaction(w http.ResponseWriter, r *http.Request) { + networkID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_network_id", "Invalid network ID") + return + } + + txID := chi.URLParam(r, "txId") + if txID == "" { + writeError(w, http.StatusBadRequest, "invalid_transaction_id", "Invalid transaction ID") + return + } + + transaction, err := h.networkService.GetTransaction(r.Context(), networkID, txID) + if err != nil { + if err.Error() == "transaction not found" { + writeError(w, http.StatusNotFound, "transaction_not_found", "Transaction not found") + return + } + writeError(w, http.StatusInternalServerError, "get_transaction_failed", err.Error()) + return + } + + resp := TransactionResponse{ + Transaction: transaction, + } + writeJSON(w, http.StatusOK, resp) +} diff --git a/pkg/networks/http/types.go b/pkg/networks/http/types.go index ae4113a..b994d99 100644 --- a/pkg/networks/http/types.go +++ b/pkg/networks/http/types.go @@ -138,3 +138,20 @@ type ImportBesuNetworkRequest struct { Description string `json:"description"` ChainID int64 `json:"chainId" validate:"required"` } + +// BlockListResponse represents the response for listing blocks +type BlockListResponse struct { + Blocks []networksservice.Block `json:"blocks"` + Total int64 `json:"total"` +} + +// BlockTransactionsResponse represents the response for listing transactions in a block +type BlockTransactionsResponse struct { + BlockNumber uint64 `json:"block_number"` + Transactions []networksservice.Transaction `json:"transactions"` +} + +// TransactionResponse represents the response for getting a single transaction +type TransactionResponse struct { + Transaction networksservice.Transaction `json:"transaction"` +} diff --git a/pkg/networks/service/fabric/deployer.go b/pkg/networks/service/fabric/deployer.go index 7a14dbe..825a62b 100644 --- a/pkg/networks/service/fabric/deployer.go +++ b/pkg/networks/service/fabric/deployer.go @@ -14,6 +14,8 @@ import ( "strings" "time" + "github.com/golang/protobuf/ptypes" + "bytes" "text/template" @@ -2286,3 +2288,261 @@ func ExtractConfigFromBlock(block *cb.Block) (*cb.Config, error) { } return cfgEnv.Config, nil } + +// GetBlocks retrieves a paginated list of blocks from the network +func (d *FabricDeployer) GetBlocks(ctx context.Context, networkID int64, limit, offset int32, reverse bool) ([]Block, int64, error) { + // Get network details + network, err := d.db.GetNetwork(ctx, networkID) + if err != nil { + return nil, 0, fmt.Errorf("failed to get network: %w", err) + } + + // Get a peer from the network + networkNodes, err := d.db.GetNetworkNodes(ctx, networkID) + if err != nil { + return nil, 0, fmt.Errorf("failed to get network nodes: %w", err) + } + + var peerNode *db.GetNetworkNodesRow + for _, node := range networkNodes { + if node.Role == "peer" && node.Status == "joined" { + peerNode = &node + break + } + } + + if peerNode == nil { + return nil, 0, fmt.Errorf("no active peer found in network") + } + + // Get peer instance + peer, err := d.nodes.GetFabricPeer(ctx, peerNode.NodeID) + if err != nil { + return nil, 0, fmt.Errorf("failed to get peer: %w", err) + } + + // Get channel info to get total blocks + channelInfo, err := peer.GetChannelBlockInfo(ctx, network.Name) + if err != nil { + return nil, 0, fmt.Errorf("failed to get channel info: %w", err) + } + + total := int64(channelInfo.Height) + + // Calculate start and end blocks based on reverse flag + var startBlock, endBlock uint64 + if reverse { + // If reverse is true, we start from the newest blocks + endBlock = channelInfo.Height - 1 - uint64(offset) + + // Calculate endBlock based on startBlock and limit + startBlock = endBlock - uint64(limit) + + // Example: if height is 31, limit is 10, offset is 0 + // endBlock = 30, startBlock = 21 + } else { + // Normal order (oldest first) + startBlock = uint64(offset) + endBlock = uint64(offset + limit - 1) + if endBlock >= channelInfo.Height { + endBlock = channelInfo.Height - 1 + } + } + + // Get blocks in range + blocks, err := peer.GetBlocksInRange(ctx, network.Name, startBlock, endBlock) + if err != nil { + return nil, 0, fmt.Errorf("failed to get blocks: %w", err) + } + + // Convert blocks to response type + result := make([]Block, len(blocks)) + for i, block := range blocks { + timestamp := time.Now() + for _, txData := range block.Data.Data { + env := &cb.Envelope{} + err = proto.Unmarshal(txData, env) + if err != nil { + return nil, 0, fmt.Errorf("failed to unmarshal envelope: %w", err) + } + payload := &cb.Payload{} + err = proto.Unmarshal(env.Payload, payload) + if err != nil { + return nil, 0, fmt.Errorf("failed to unmarshal payload: %w", err) + } + chdr := &cb.ChannelHeader{} + err = proto.Unmarshal(payload.Header.ChannelHeader, chdr) + if err != nil { + return nil, 0, fmt.Errorf("failed to unmarshal channel header: %w", err) + } + txDate, err := ptypes.Timestamp(chdr.Timestamp) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse timestamp: %w", err) + } + timestamp = txDate + break + } + buffer := &bytes.Buffer{} + err = protolator.DeepMarshalJSON(buffer, block) + if err != nil { + return nil, 0, fmt.Errorf("failed to marshal block data: %w", err) + } + blockDataJson := buffer.Bytes() + result[i] = Block{ + Number: block.Header.Number, + Hash: fmt.Sprintf("%x", block.Header.DataHash), + PreviousHash: fmt.Sprintf("%x", block.Header.PreviousHash), + Timestamp: timestamp, + TxCount: len(block.Data.Data), + Data: blockDataJson, + } + } + + return result, total, nil +} + +// GetBlockTransactions retrieves all transactions from a specific block +func (d *FabricDeployer) GetBlockTransactions(ctx context.Context, networkID int64, blockNum uint64) ([]Transaction, error) { + // Get network details + network, err := d.db.GetNetwork(ctx, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + + // Get a peer from the network + networkNodes, err := d.db.GetNetworkNodes(ctx, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get network nodes: %w", err) + } + + var peerNode *db.GetNetworkNodesRow + for _, node := range networkNodes { + if node.Role == "peer" && node.Status == "joined" { + peerNode = &node + break + } + } + + if peerNode == nil { + return nil, fmt.Errorf("no active peer found in network") + } + + // Get peer instance + peer, err := d.nodes.GetFabricPeer(ctx, peerNode.NodeID) + if err != nil { + return nil, fmt.Errorf("failed to get peer: %w", err) + } + + // Get transactions from block + envelopes, err := peer.GetBlockTransactions(ctx, network.Name, blockNum) + if err != nil { + return nil, fmt.Errorf("failed to get block transactions: %w", err) + } + + // Convert envelopes to transactions + transactions := make([]Transaction, len(envelopes)) + for i, env := range envelopes { + payload := &cb.Payload{} + if err := proto.Unmarshal(env.Payload, payload); err != nil { + continue + } + + chdr := &cb.ChannelHeader{} + if err := proto.Unmarshal(payload.Header.ChannelHeader, chdr); err != nil { + continue + } + + shdr := &cb.SignatureHeader{} + if err := proto.Unmarshal(payload.Header.SignatureHeader, shdr); err != nil { + continue + } + + transactions[i] = Transaction{ + ID: chdr.TxId, + BlockNum: blockNum, + Timestamp: time.Unix(chdr.Timestamp.Seconds, int64(chdr.Timestamp.Nanos)), + Type: cb.HeaderType_name[int32(chdr.Type)], + Creator: string(shdr.Creator), + Status: "success", + } + } + + return transactions, nil +} + +// GetTransaction retrieves a specific transaction by its ID +func (d *FabricDeployer) GetTransaction(ctx context.Context, networkID int64, txID string) (Transaction, error) { + // Get network details + network, err := d.db.GetNetwork(ctx, networkID) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get network: %w", err) + } + + // Get a peer from the network + networkNodes, err := d.db.GetNetworkNodes(ctx, networkID) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get network nodes: %w", err) + } + + var peerNode *db.GetNetworkNodesRow + for _, node := range networkNodes { + if node.Role == "peer" && node.Status == "joined" { + peerNode = &node + break + } + } + + if peerNode == nil { + return Transaction{}, fmt.Errorf("no active peer found in network") + } + + // Get peer instance + peer, err := d.nodes.GetFabricPeer(ctx, peerNode.NodeID) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get peer: %w", err) + } + + // Get channel info + channelInfo, err := peer.GetChannelBlockInfo(ctx, network.Name) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get channel info: %w", err) + } + + // Search for transaction in blocks + for blockNum := uint64(0); blockNum < channelInfo.Height; blockNum++ { + transactions, err := peer.GetBlockTransactions(ctx, network.Name, blockNum) + if err != nil { + continue + } + + for _, tx := range transactions { + payload := &cb.Payload{} + if err := proto.Unmarshal(tx.Payload, payload); err != nil { + continue + } + + chdr := &cb.ChannelHeader{} + if err := proto.Unmarshal(payload.Header.ChannelHeader, chdr); err != nil { + continue + } + + if chdr.TxId == txID { + shdr := &cb.SignatureHeader{} + if err := proto.Unmarshal(payload.Header.SignatureHeader, shdr); err != nil { + continue + } + + return Transaction{ + ID: chdr.TxId, + BlockNum: blockNum, + Timestamp: time.Unix(chdr.Timestamp.Seconds, int64(chdr.Timestamp.Nanos)), + Type: cb.HeaderType_name[int32(chdr.Type)], + Creator: string(shdr.Creator), + Status: "success", + }, nil + } + } + } + + return Transaction{}, fmt.Errorf("transaction not found") +} diff --git a/pkg/networks/service/fabric/org/org.go b/pkg/networks/service/fabric/org/org.go index c78b491..a2e614e 100644 --- a/pkg/networks/service/fabric/org/org.go +++ b/pkg/networks/service/fabric/org/org.go @@ -186,12 +186,12 @@ func (s *FabricOrg) getOrdererMSP(ctx context.Context) (identity.SigningIdentity return nil, fmt.Errorf("failed to get private key: %w", err) } - cert, err := identity.ReadCertificate(*adminSignKey.Certificate) + cert, err := gwidentity.CertificateFromPEM([]byte(*adminSignKey.Certificate)) if err != nil { return nil, fmt.Errorf("failed to read certificate: %w", err) } - priv, err := identity.ReadPrivateKey(privateKeyPEM) + priv, err := gwidentity.PrivateKeyFromPEM([]byte(privateKeyPEM)) if err != nil { return nil, fmt.Errorf("failed to read private key: %w", err) } diff --git a/pkg/networks/service/fabric/types.go b/pkg/networks/service/fabric/types.go index 0fbbfe0..a9150f4 100644 --- a/pkg/networks/service/fabric/types.go +++ b/pkg/networks/service/fabric/types.go @@ -1,5 +1,7 @@ package fabric +import "time" + // NodeType represents the type of a Fabric node type NodeType string @@ -62,3 +64,66 @@ type NetworkConfig struct { Capabilities map[string][]string `json:"capabilities"` Policies map[string]interface{} `json:"policies"` } + +// Block represents a block in the Fabric blockchain +type Block struct { + Number uint64 `json:"number"` + Hash string `json:"hash"` + PreviousHash string `json:"previousHash"` + DataHash string `json:"dataHash"` + Timestamp time.Time `json:"timestamp"` + TxCount int `json:"txCount"` + Data []byte `json:"data"` +} + +// Transaction represents a transaction in a Fabric block +type Transaction struct { + ID string `json:"id"` + Type string `json:"type"` + Timestamp time.Time `json:"timestamp"` + Creator string `json:"creator"` + Status string `json:"status"` + BlockNum uint64 `json:"blockNum"` +} + +// BlockInfo represents information about the blockchain +type BlockInfo struct { + Height uint64 `json:"height"` + CurrentBlockHash string `json:"currentBlockHash"` + PreviousBlockHash string `json:"previousBlockHash"` +} + +// DeployerOptions contains options for the Fabric deployer +type DeployerOptions struct { + NetworkID int64 `json:"networkId"` + ChannelID string `json:"channelId"` + ConsortiumName string `json:"consortiumName"` + OrdererEndpoint string `json:"ordererEndpoint"` + PeerEndpoint string `json:"peerEndpoint"` +} + +// NetworkQueryOptions contains options for querying network data +type NetworkQueryOptions struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +// BlockQueryOptions contains options for querying blocks +type BlockQueryOptions struct { + StartBlock uint64 `json:"startBlock"` + EndBlock uint64 `json:"endBlock"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +// PaginatedBlocks represents a paginated list of blocks +type PaginatedBlocks struct { + Items []Block `json:"items"` + TotalCount int64 `json:"totalCount"` +} + +// PaginatedTransactions represents a paginated list of transactions +type PaginatedTransactions struct { + Items []Transaction `json:"items"` + TotalCount int64 `json:"totalCount"` +} diff --git a/pkg/networks/service/service.go b/pkg/networks/service/service.go index 6bea074..777906f 100644 --- a/pkg/networks/service/service.go +++ b/pkg/networks/service/service.go @@ -1009,3 +1009,108 @@ func (s *NetworkService) getOrdererAddressAndCertForNetwork(ctx context.Context, return ordererAddress, ordererTLSCert, nil } + +// GetBlocks retrieves a paginated list of blocks from the network +func (s *NetworkService) GetBlocks(ctx context.Context, networkID int64, limit, offset int32, reverse bool) ([]Block, int64, error) { + // Get the fabric deployer for this network + fabricDeployer, err := s.getFabricDeployerForNetwork(ctx, networkID) + if err != nil { + return nil, 0, fmt.Errorf("failed to get fabric deployer: %w", err) + } + + // Use the fabric deployer to get blocks + fabricBlocks, total, err := fabricDeployer.GetBlocks(ctx, networkID, limit, offset, reverse) + if err != nil { + return nil, 0, fmt.Errorf("failed to get blocks: %w", err) + } + + // Map fabric.Block to service.Block + blocks := make([]Block, len(fabricBlocks)) + for i, fb := range fabricBlocks { + blocks[i] = Block{ + Number: fb.Number, + Hash: fb.Hash, + PreviousHash: fb.PreviousHash, + Timestamp: fb.Timestamp, + TxCount: fb.TxCount, + Data: fb.Data, + } + } + + return blocks, total, nil +} + +// GetBlockTransactions retrieves all transactions from a specific block +func (s *NetworkService) GetBlockTransactions(ctx context.Context, networkID int64, blockNum uint64) ([]Transaction, error) { + // Get the fabric deployer for this network + fabricDeployer, err := s.getFabricDeployerForNetwork(ctx, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get fabric deployer: %w", err) + } + + // Use the fabric deployer to get block transactions + fabricTransactions, err := fabricDeployer.GetBlockTransactions(ctx, networkID, blockNum) + if err != nil { + return nil, fmt.Errorf("failed to get block transactions: %w", err) + } + + // Map fabric.Transaction to service.Transaction + transactions := make([]Transaction, len(fabricTransactions)) + for i, ft := range fabricTransactions { + transactions[i] = Transaction{ + TxID: ft.ID, + BlockNumber: ft.BlockNum, + Timestamp: ft.Timestamp, + Type: ft.Type, + Creator: ft.Creator, + } + } + + return transactions, nil +} + +// GetTransaction retrieves a specific transaction by its ID +func (s *NetworkService) GetTransaction(ctx context.Context, networkID int64, txID string) (Transaction, error) { + // Get the fabric deployer for this network + fabricDeployer, err := s.getFabricDeployerForNetwork(ctx, networkID) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get fabric deployer: %w", err) + } + + // Use the fabric deployer to get transaction + ft, err := fabricDeployer.GetTransaction(ctx, networkID, txID) + if err != nil { + return Transaction{}, fmt.Errorf("failed to get transaction: %w", err) + } + + // Map fabric.Transaction to service.Transaction + transaction := Transaction{ + TxID: ft.ID, + BlockNumber: ft.BlockNum, + Timestamp: ft.Timestamp, + Type: ft.Type, + Creator: ft.Creator, + } + + return transaction, nil +} + +// getFabricDeployerForNetwork creates and returns a fabric deployer for the specified network +func (s *NetworkService) getFabricDeployerForNetwork(ctx context.Context, networkID int64) (*fabric.FabricDeployer, error) { + // Get network details to verify it exists and is a Fabric network + network, err := s.db.GetNetwork(ctx, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + deployer, err := s.deployerFactory.GetDeployer(network.Platform) + if err != nil { + return nil, fmt.Errorf("failed to get deployer: %w", err) + } + + fabricDeployer, ok := deployer.(*fabric.FabricDeployer) + if !ok { + return nil, fmt.Errorf("network %d is not a Fabric network", networkID) + } + + return fabricDeployer, nil +} diff --git a/pkg/networks/service/types.go b/pkg/networks/service/types.go index 94c6fe9..180c614 100644 --- a/pkg/networks/service/types.go +++ b/pkg/networks/service/types.go @@ -1,5 +1,9 @@ package service +import ( + "time" +) + type ImportNetworkParams struct { NetworkType string GenesisFile []byte @@ -66,3 +70,23 @@ type RemoveOrdererResponse struct { NetworkID int64 `json:"network_id"` OrdererID int64 `json:"orderer_id"` } + +// Block represents a block in the blockchain +type Block struct { + Number uint64 `json:"number"` + Hash string `json:"hash"` + PreviousHash string `json:"previous_hash"` + Timestamp time.Time `json:"timestamp"` + TxCount int `json:"tx_count"` + Data []byte `json:"data"` +} + +// Transaction represents a transaction in a block +type Transaction struct { + TxID string `json:"tx_id"` + BlockNumber uint64 `json:"block_number"` + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` + Creator string `json:"creator"` + Payload []byte `json:"payload"` +} diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index 1d806b1..37d57ff 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -23,6 +23,7 @@ import ( "github.com/hyperledger/fabric-admin-sdk/pkg/channel" "github.com/hyperledger/fabric-admin-sdk/pkg/identity" "github.com/hyperledger/fabric-admin-sdk/pkg/network" + "github.com/hyperledger/fabric-gateway/pkg/client" gwidentity "github.com/hyperledger/fabric-gateway/pkg/identity" cb "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/orderer" @@ -969,7 +970,7 @@ func (p *LocalPeer) JoinChannel(genesisBlock []byte) error { } defer peerConn.Close() - adminIdentity, err := p.GetAdminIdentity(ctx) + adminIdentity, _, err := p.GetAdminIdentity(ctx) if err != nil { return fmt.Errorf("failed to get admin identity: %w", err) } @@ -2181,7 +2182,7 @@ type SaveChannelConfigResponse struct { } func (p *LocalPeer) SaveChannelConfig(ctx context.Context, channelID string, ordererUrl string, ordererTlsCACert string, channelData *cb.Envelope) (*SaveChannelConfigResponse, error) { - adminIdentity, err := p.GetAdminIdentity(ctx) + adminIdentity, _, err := p.GetAdminIdentity(ctx) if err != nil { return nil, fmt.Errorf("failed to get admin identity: %w", err) } @@ -2320,36 +2321,40 @@ func (p *LocalPeer) CreatePeerConnection(ctx context.Context, peerURL string, pe func (p *LocalPeer) GetMSPID() string { return p.mspID } -func (p *LocalPeer) GetAdminIdentity(ctx context.Context) (identity.SigningIdentity, error) { +func (p *LocalPeer) GetAdminIdentity(ctx context.Context) (identity.SigningIdentity, gwidentity.Sign, error) { adminSignKeyDB, err := p.keyService.GetKey(ctx, int(p.org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to get TLS CA key: %w", err) + return nil, nil, fmt.Errorf("failed to get TLS CA key: %w", err) } if adminSignKeyDB.Certificate == nil { - return nil, fmt.Errorf("TLS CA key is not set") + return nil, nil, fmt.Errorf("TLS CA key is not set") } certificate := *adminSignKeyDB.Certificate privateKey, err := p.keyService.GetDecryptedPrivateKey(int(p.org.AdminSignKeyID.Int64)) if err != nil { - return nil, fmt.Errorf("failed to get decrypted private key: %w", err) + return nil, nil, fmt.Errorf("failed to get decrypted private key: %w", err) } cert, err := gwidentity.CertificateFromPEM([]byte(certificate)) if err != nil { - return nil, fmt.Errorf("failed to read certificate: %w", err) + return nil, nil, fmt.Errorf("failed to read certificate: %w", err) } priv, err := gwidentity.PrivateKeyFromPEM([]byte(privateKey)) if err != nil { - return nil, fmt.Errorf("failed to read private key: %w", err) + return nil, nil, fmt.Errorf("failed to read private key: %w", err) } signingIdentity, err := identity.NewPrivateKeySigningIdentity(p.mspID, cert, priv) if err != nil { - return nil, fmt.Errorf("failed to create signing identity: %w", err) + return nil, nil, fmt.Errorf("failed to create signing identity: %w", err) } - return signingIdentity, nil + signer, err := gwidentity.NewPrivateKeySign(priv) + if err != nil { + return nil, nil, fmt.Errorf("failed to create signer: %w", err) + } + return signingIdentity, signer, nil } // Add this struct near the top with other type definitions @@ -2365,7 +2370,7 @@ func (p *LocalPeer) GetChannelBlock(ctx context.Context, channelID string, order "ordererUrl", ordererUrl) // Get admin identity - adminIdentity, err := p.GetAdminIdentity(ctx) + adminIdentity, _, err := p.GetAdminIdentity(ctx) if err != nil { return nil, fmt.Errorf("failed to get admin identity: %w", err) } @@ -2498,7 +2503,7 @@ func (p *LocalPeer) GetChannels(ctx context.Context) ([]PeerChannel, error) { return nil, fmt.Errorf("failed to create peer connection: %w", err) } defer peerConn.Close() - adminIdentity, err := p.GetAdminIdentity(ctx) + adminIdentity, _, err := p.GetAdminIdentity(ctx) if err != nil { return nil, fmt.Errorf("failed to get admin identity: %w", err) } @@ -2535,7 +2540,7 @@ func (p *LocalPeer) getChannelBlockInfo(ctx context.Context, channelID string) ( return nil, fmt.Errorf("failed to create peer connection: %w", err) } defer peerConn.Close() - adminIdentity, err := p.GetAdminIdentity(ctx) + adminIdentity, _, err := p.GetAdminIdentity(ctx) if err != nil { return nil, fmt.Errorf("failed to get admin identity: %w", err) } @@ -2573,3 +2578,170 @@ func ExtractConfigFromBlock(block *cb.Block) (*cb.Config, error) { } return cfgEnv.Config, nil } +func (p *LocalPeer) GetBlockTransactions(ctx context.Context, channelID string, blockNum uint64) ([]*cb.Envelope, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + } + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + adminIdentity, signer, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) + } + gateway, err := client.Connect(adminIdentity, client.WithClientConnection(peerConn), client.WithSign(signer)) + if err != nil { + return nil, fmt.Errorf("failed to connect to gateway: %w", err) + } + defer gateway.Close() + network := gateway.GetNetwork(channelID) + blockEvents, err := network.BlockAndPrivateDataEvents(ctx, client.WithStartBlock(blockNum)) + if err != nil { + return nil, fmt.Errorf("failed to get block: %w", err) + } + + for blockEvent := range blockEvents { + var transactions []*cb.Envelope + for _, data := range blockEvent.Block.Data.Data { + envelope := &cb.Envelope{} + if err := proto.Unmarshal(data, envelope); err != nil { + return nil, fmt.Errorf("failed to unmarshal transaction envelope: %w", err) + } + transactions = append(transactions, envelope) + } + return transactions, nil + } + return nil, fmt.Errorf("block not found") +} + +// GetBlocksInRange retrieves blocks from startBlock to endBlock (inclusive) +func (p *LocalPeer) GetBlocksInRange(ctx context.Context, channelID string, startBlock, endBlock uint64) ([]*cb.Block, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + } + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + adminIdentity, signer, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) + } + gateway, err := client.Connect(adminIdentity, client.WithClientConnection(peerConn), client.WithSign(signer)) + if err != nil { + return nil, fmt.Errorf("failed to connect to gateway: %w", err) + } + defer gateway.Close() + + network := gateway.GetNetwork(channelID) + blockEvents, err := network.BlockEvents(ctx, client.WithStartBlock(startBlock)) + if err != nil { + return nil, fmt.Errorf("failed to get blocks: %w", err) + } + + var blocks []*cb.Block + blockCount := uint64(0) + maxBlocks := endBlock - startBlock + 1 + + for blockEvent := range blockEvents { + blocks = append(blocks, blockEvent) + blockCount++ + + if blockCount >= maxBlocks || blockEvent.Header.Number >= endBlock { + break + } + } + + if len(blocks) == 0 { + return nil, fmt.Errorf("no blocks found in range %d to %d", startBlock, endBlock) + } + + return blocks, nil +} + +// GetChannelBlockInfo retrieves information about the blockchain for a specific channel +func (p *LocalPeer) GetChannelBlockInfo(ctx context.Context, channelID string) (*BlockInfo, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + } + + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + + adminIdentity, _, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) + } + blockInfo, err := channel.GetBlockChainInfo(ctx, peerConn, adminIdentity, channelID) + if err != nil { + return nil, fmt.Errorf("failed to get block chain info: %w", err) + } + + return &BlockInfo{ + Height: blockInfo.Height, + CurrentBlockHash: fmt.Sprintf("%x", blockInfo.CurrentBlockHash), + PreviousBlockHash: fmt.Sprintf("%x", blockInfo.PreviousBlockHash), + }, nil + +} + +const ( + qscc = "qscc" + qsccTransactionByID = "GetTransactionByID" + qsccChannelInfo = "GetChainInfo" + qsccBlockByHash = "GetBlockByHash" + qsccBlockByNumber = "GetBlockByNumber" + qsccBlockByTxID = "GetBlockByTxID" +) + +// GetBlockByTxID retrieves a block containing the specified transaction ID +func (p *LocalPeer) GetBlockByTxID(ctx context.Context, channelID string, txID string) (*cb.Block, error) { + peerUrl := p.GetPeerAddress() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get TLS CA cert: %w", err) + } + + peerConn, err := p.CreatePeerConnection(ctx, peerUrl, tlsCACert) + if err != nil { + return nil, fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() + + adminIdentity, signer, err := p.GetAdminIdentity(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get admin identity: %w", err) + } + + gateway, err := client.Connect(adminIdentity, client.WithClientConnection(peerConn), client.WithSign(signer)) + if err != nil { + return nil, fmt.Errorf("failed to connect to gateway: %w", err) + } + defer gateway.Close() + network := gateway.GetNetwork(channelID) + contract := network.GetContract(qscc) + response, err := contract.EvaluateTransaction(qsccBlockByTxID, channelID, txID) + if err != nil { + return nil, fmt.Errorf("failed to query block by transaction ID: %w", err) + } + + // Unmarshal block + block := &cb.Block{} + if err := proto.Unmarshal(response, block); err != nil { + return nil, fmt.Errorf("failed to unmarshal block: %w", err) + } + + return block, nil +} diff --git a/pkg/nodes/peer/service.go b/pkg/nodes/peer/service.go index d417c20..8ce2de8 100644 --- a/pkg/nodes/peer/service.go +++ b/pkg/nodes/peer/service.go @@ -60,7 +60,7 @@ After=network.target [Service] Type=simple WorkingDirectory={{.DirPath}} -ExecStart={{.Cmd}} +ExecStart=/bin/bash -c "{{.Cmd}} > {{.LogPath}} 2>&1" Restart=on-failure RestartSec=10 LimitNOFILE=65536 diff --git a/pkg/nodes/peer/types.go b/pkg/nodes/peer/types.go index 16a99d4..c7a8c14 100644 --- a/pkg/nodes/peer/types.go +++ b/pkg/nodes/peer/types.go @@ -42,3 +42,9 @@ type StartDockerResponse struct { Mode string `json:"mode"` ContainerName string `json:"containerName"` } + +type BlockInfo struct { + Height uint64 `json:"height"` + CurrentBlockHash string `json:"currentBlockHash"` + PreviousBlockHash string `json:"previousBlockHash"` +} diff --git a/web/src/api/client/@tanstack/react-query.gen.ts b/web/src/api/client/@tanstack/react-query.gen.ts index e167c2e..54d77c7 100644 --- a/web/src/api/client/@tanstack/react-query.gen.ts +++ b/web/src/api/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import type { Options } from '@hey-api/client-fetch'; import { queryOptions, type UseMutationOptions, type DefaultError, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; -import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigError, PostNetworksFabricByIdUpdateConfigResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; -import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, postNetworksFabricByIdUpdateConfig, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; +import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNetworksFabricByIdTransactionsByTxIdData, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigError, PostNetworksFabricByIdUpdateConfigResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; +import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdBlocks, getNetworksFabricByIdBlocksByBlockNumTransactions, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNetworksFabricByIdTransactionsByTxId, postNetworksFabricByIdUpdateConfig, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; type QueryKey = [ Pick & { @@ -1164,6 +1164,72 @@ export const postNetworksFabricByIdAnchorPeersMutation = (options?: Partial) => [ + createQueryKey('getNetworksFabricByIdBlocks', options) +]; + +export const getNetworksFabricByIdBlocksOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getNetworksFabricByIdBlocks({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getNetworksFabricByIdBlocksQueryKey(options) + }); +}; + +export const getNetworksFabricByIdBlocksInfiniteQueryKey = (options: Options): QueryKey> => [ + createQueryKey('getNetworksFabricByIdBlocks', options, true) +]; + +export const getNetworksFabricByIdBlocksInfiniteOptions = (options: Options) => { + return infiniteQueryOptions, QueryKey>, number | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + offset: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getNetworksFabricByIdBlocks({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getNetworksFabricByIdBlocksInfiniteQueryKey(options) + }); +}; + +export const getNetworksFabricByIdBlocksByBlockNumTransactionsQueryKey = (options: Options) => [ + createQueryKey('getNetworksFabricByIdBlocksByBlockNumTransactions', options) +]; + +export const getNetworksFabricByIdBlocksByBlockNumTransactionsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getNetworksFabricByIdBlocksByBlockNumTransactions({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getNetworksFabricByIdBlocksByBlockNumTransactionsQueryKey(options) + }); +}; + export const getNetworksFabricByIdChannelConfigQueryKey = (options: Options) => [ createQueryKey('getNetworksFabricByIdChannelConfig', options) ]; @@ -1466,6 +1532,25 @@ export const postNetworksFabricByIdReloadBlockMutation = (options?: Partial) => [ + createQueryKey('getNetworksFabricByIdTransactionsByTxId', options) +]; + +export const getNetworksFabricByIdTransactionsByTxIdOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getNetworksFabricByIdTransactionsByTxId({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getNetworksFabricByIdTransactionsByTxIdQueryKey(options) + }); +}; + export const postNetworksFabricByIdUpdateConfigQueryKey = (options: Options) => [ createQueryKey('postNetworksFabricByIdUpdateConfig', options) ]; diff --git a/web/src/api/client/sdk.gen.ts b/web/src/api/client/sdk.gen.ts index c345208..abac460 100644 --- a/web/src/api/client/sdk.gen.ts +++ b/web/src/api/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigResponse, PostNetworksFabricByIdUpdateConfigError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; +import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdBlocksByBlockNumTransactionsResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNetworksFabricByIdTransactionsByTxIdData, GetNetworksFabricByIdTransactionsByTxIdResponse, GetNetworksFabricByIdTransactionsByTxIdError, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigResponse, PostNetworksFabricByIdUpdateConfigError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; export const client = createClient(createConfig()); @@ -555,6 +555,28 @@ export const postNetworksFabricByIdAnchorPeers = (options: Options) => { + return (options?.client ?? client).get({ + url: '/networks/fabric/{id}/blocks', + ...options + }); +}; + +/** + * Get transactions from a specific block + * Get all transactions from a specific block in a Fabric network + */ +export const getNetworksFabricByIdBlocksByBlockNumTransactions = (options: Options) => { + return (options?.client ?? client).get({ + url: '/networks/fabric/{id}/blocks/{blockNum}/transactions', + ...options + }); +}; + /** * Get Fabric network channel configuration * Retrieve the channel configuration for a Fabric network @@ -691,6 +713,17 @@ export const postNetworksFabricByIdReloadBlock = (options: Options) => { + return (options?.client ?? client).get({ + url: '/networks/fabric/{id}/transactions/{txId}', + ...options + }); +}; + /** * Prepare a config update for a Fabric network * Prepare a config update proposal for a Fabric network using the provided operations. diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts index 03a1393..8b71849 100644 --- a/web/src/api/client/types.gen.ts +++ b/web/src/api/client/types.gen.ts @@ -160,6 +160,16 @@ export type HttpBesuNetworkResponse = { updatedAt?: string; }; +export type HttpBlockListResponse = { + blocks?: Array; + total?: number; +}; + +export type HttpBlockTransactionsResponse = { + block_number?: number; + transactions?: Array; +}; + export type HttpChannelConfigResponse = { config?: { [key: string]: unknown; @@ -211,6 +221,17 @@ export type HttpConfigUpdateOperationRequest = { type: 'add_org' | 'remove_org' | 'update_org_msp' | 'set_anchor_peers' | 'add_consenter' | 'remove_consenter' | 'update_consenter' | 'update_etcd_raft_options' | 'update_batch_size' | 'update_batch_timeout'; }; +export type HttpConfigUpdateResponse = { + channel_name?: string; + created_at?: string; + created_by?: string; + id?: string; + network_id?: number; + operations?: Array; + preview_json?: string; + status?: string; +}; + export type HttpConsenterConfig = { id: string; }; @@ -542,21 +563,6 @@ export type HttpPaginatedNodesResponse = { total?: number; }; -export type HttpPrepareConfigUpdateRequest = { - operations: Array; -}; - -export type HttpPrepareConfigUpdateResponse = { - channel_name?: string; - created_at?: string; - created_by?: string; - id?: string; - network_id?: number; - operations?: Array; - preview_json?: string; - status?: string; -}; - export type HttpProviderResponse = { config?: unknown; createdAt?: string; @@ -610,6 +616,10 @@ export type HttpTestProviderResponse = { testedAt?: string; }; +export type HttpTransactionResponse = { + transaction?: ServiceTransaction; +}; + export type HttpUpdateBackupScheduleRequest = { cronExpression: string; description?: string; @@ -661,6 +671,10 @@ export type HttpUpdateEtcdRaftOptionsPayload = { tick_interval: string; }; +export type HttpUpdateFabricNetworkRequest = { + operations: Array; +}; + export type HttpUpdateOrgMspPayload = { msp_id: string; root_certs: Array; @@ -836,6 +850,15 @@ export type ServiceBesuNodeProperties = { rpcPort?: number; }; +export type ServiceBlock = { + data?: Array; + hash?: string; + number?: number; + previous_hash?: string; + timestamp?: string; + tx_count?: number; +}; + export type ServiceFabricOrdererProperties = { adminAddress?: string; domainNames?: Array; @@ -937,6 +960,15 @@ export type ServiceNodesDefaultsResult = { peers?: Array; }; +export type ServiceTransaction = { + block_number?: number; + creator?: string; + payload?: Array; + timestamp?: string; + tx_id?: string; + type?: string; +}; + export type TypesBesuNodeConfig = { bootNodes?: Array; env?: { @@ -2696,6 +2728,99 @@ export type PostNetworksFabricByIdAnchorPeersResponses = { export type PostNetworksFabricByIdAnchorPeersResponse = PostNetworksFabricByIdAnchorPeersResponses[keyof PostNetworksFabricByIdAnchorPeersResponses]; +export type GetNetworksFabricByIdBlocksData = { + body?: never; + path: { + /** + * Network ID + */ + id: number; + }; + query?: { + /** + * Number of blocks to return (default: 10) + */ + limit?: number; + /** + * Number of blocks to skip (default: 0) + */ + offset?: number; + /** + * Get blocks in reverse order (default: false) + */ + reverse?: boolean; + }; + url: '/networks/fabric/{id}/blocks'; +}; + +export type GetNetworksFabricByIdBlocksErrors = { + /** + * Bad Request + */ + 400: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Not Found + */ + 404: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Internal Server Error + */ + 500: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; +}; + +export type GetNetworksFabricByIdBlocksError = GetNetworksFabricByIdBlocksErrors[keyof GetNetworksFabricByIdBlocksErrors]; + +export type GetNetworksFabricByIdBlocksResponses = { + /** + * OK + */ + 200: HttpBlockListResponse; +}; + +export type GetNetworksFabricByIdBlocksResponse = GetNetworksFabricByIdBlocksResponses[keyof GetNetworksFabricByIdBlocksResponses]; + +export type GetNetworksFabricByIdBlocksByBlockNumTransactionsData = { + body?: never; + path: { + /** + * Network ID + */ + id: number; + /** + * Block Number + */ + blockNum: number; + }; + query?: never; + url: '/networks/fabric/{id}/blocks/{blockNum}/transactions'; +}; + +export type GetNetworksFabricByIdBlocksByBlockNumTransactionsErrors = { + /** + * Bad Request + */ + 400: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Not Found + */ + 404: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Internal Server Error + */ + 500: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; +}; + +export type GetNetworksFabricByIdBlocksByBlockNumTransactionsError = GetNetworksFabricByIdBlocksByBlockNumTransactionsErrors[keyof GetNetworksFabricByIdBlocksByBlockNumTransactionsErrors]; + +export type GetNetworksFabricByIdBlocksByBlockNumTransactionsResponses = { + /** + * OK + */ + 200: HttpBlockTransactionsResponse; +}; + +export type GetNetworksFabricByIdBlocksByBlockNumTransactionsResponse = GetNetworksFabricByIdBlocksByBlockNumTransactionsResponses[keyof GetNetworksFabricByIdBlocksByBlockNumTransactionsResponses]; + export type GetNetworksFabricByIdChannelConfigData = { body?: never; path: { @@ -3155,11 +3280,53 @@ export type PostNetworksFabricByIdReloadBlockResponses = { export type PostNetworksFabricByIdReloadBlockResponse = PostNetworksFabricByIdReloadBlockResponses[keyof PostNetworksFabricByIdReloadBlockResponses]; +export type GetNetworksFabricByIdTransactionsByTxIdData = { + body?: never; + path: { + /** + * Network ID + */ + id: number; + /** + * Transaction ID + */ + txId: string; + }; + query?: never; + url: '/networks/fabric/{id}/transactions/{txId}'; +}; + +export type GetNetworksFabricByIdTransactionsByTxIdErrors = { + /** + * Bad Request + */ + 400: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Not Found + */ + 404: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; + /** + * Internal Server Error + */ + 500: GithubComChainlaunchChainlaunchPkgNetworksHttpErrorResponse; +}; + +export type GetNetworksFabricByIdTransactionsByTxIdError = GetNetworksFabricByIdTransactionsByTxIdErrors[keyof GetNetworksFabricByIdTransactionsByTxIdErrors]; + +export type GetNetworksFabricByIdTransactionsByTxIdResponses = { + /** + * OK + */ + 200: HttpTransactionResponse; +}; + +export type GetNetworksFabricByIdTransactionsByTxIdResponse = GetNetworksFabricByIdTransactionsByTxIdResponses[keyof GetNetworksFabricByIdTransactionsByTxIdResponses]; + export type PostNetworksFabricByIdUpdateConfigData = { /** * Config update operations */ - body: HttpPrepareConfigUpdateRequest; + body: HttpUpdateFabricNetworkRequest; path: { /** * Network ID @@ -3187,7 +3354,7 @@ export type PostNetworksFabricByIdUpdateConfigResponses = { /** * OK */ - 200: HttpPrepareConfigUpdateResponse; + 200: HttpConfigUpdateResponse; }; export type PostNetworksFabricByIdUpdateConfigResponse = PostNetworksFabricByIdUpdateConfigResponses[keyof PostNetworksFabricByIdUpdateConfigResponses]; diff --git a/web/src/components/networks/FabricNetworkDetails.tsx b/web/src/components/networks/FabricNetworkDetails.tsx index cf9b940..f91cf15 100644 --- a/web/src/components/networks/FabricNetworkDetails.tsx +++ b/web/src/components/networks/FabricNetworkDetails.tsx @@ -25,7 +25,7 @@ import { Card } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { TimeAgo } from '@/components/ui/time-ago' import { useMutation, useQuery } from '@tanstack/react-query' -import { Activity, AlertTriangle, Anchor, ArrowLeft, Check, Code, Copy, Network, Plus, Settings } from 'lucide-react' +import { Activity, AlertTriangle, Anchor, ArrowLeft, Check, Code, Copy, Network, Plus, Settings, Blocks } from 'lucide-react' import { useMemo, useState } from 'react' import ReactMarkdown from 'react-markdown' import { Link, useParams, useSearchParams } from 'react-router-dom' @@ -35,6 +35,7 @@ import rehypeRaw from 'rehype-raw' import { toast } from 'sonner' import { AddMultipleNodesDialog } from './add-multiple-nodes-dialog' import { ChannelUpdateForm } from '../nodes/ChannelUpdateForm' +import { BlockExplorer } from './block-explorer' interface FabricNetworkDetailsProps { network: HttpNetworkResponse @@ -624,6 +625,21 @@ export default function FabricNetworkDetails({ network }: FabricNetworkDetailsPr
} share={

Pro Only

} + explorer={ +
+
+
+ +
+
+

Block Explorer

+

Explore blocks, transactions, and chaincode data

+
+
+ + +
+ } />
diff --git a/web/src/components/networks/block-explorer.tsx b/web/src/components/networks/block-explorer.tsx new file mode 100644 index 0000000..3d5687a --- /dev/null +++ b/web/src/components/networks/block-explorer.tsx @@ -0,0 +1,66 @@ +import { getNetworksFabricByIdBlocksOptions } from '@/api/client/@tanstack/react-query.gen' +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { Skeleton } from '@/components/ui/skeleton' +import { useQuery } from '@tanstack/react-query' +import { formatDistanceToNow } from 'date-fns' +import { useMemo } from 'react' + +interface BlockExplorerProps { + networkId: number +} + +export function BlockExplorer({ networkId }: BlockExplorerProps) { + const { data: blocksResponse, isLoading: blocksLoading } = useQuery({ + ...getNetworksFabricByIdBlocksOptions({ + path: { id: networkId }, + query: { + limit: 3, + offset: 0, + reverse: true, + }, + }), + }) + // Sort blocks in descending order by block number + const sortedBlocks = useMemo(() => [...(blocksResponse?.blocks || [])].sort((a, b) => (b.number ?? 0) - (a.number ?? 0)), [blocksResponse?.blocks]) + if (blocksLoading) { + return ( +
+ +
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+ ) + } + + return ( +
+
+

Recent Blocks

+ +
+
+ {sortedBlocks.map((block) => ( + +
+
+

Block #{block.number}

+

+ {block.tx_count} {block.tx_count === 1 ? 'transaction' : 'transactions'} • {formatDistanceToNow(new Date(block.timestamp || ''), { addSuffix: true })} +

+
+ +
+
+ ))} +
+
+ ) +} diff --git a/web/src/components/networks/network-tabs.tsx b/web/src/components/networks/network-tabs.tsx index dfe83aa..cdecbca 100644 --- a/web/src/components/networks/network-tabs.tsx +++ b/web/src/components/networks/network-tabs.tsx @@ -3,7 +3,7 @@ import { ProBadge } from '@/components/pro/ProBadge' import { ProFeatureGate } from '@/components/pro/ProFeatureGate' import { useState, useEffect } from 'react' -export type TabValue = 'details' | 'genesis' | 'anchor-peers' | 'consenters' | 'chaincode' | 'share' | 'channel-update' | 'proposals' +export type TabValue = 'details' | 'genesis' | 'anchor-peers' | 'consenters' | 'chaincode' | 'share' | 'channel-update' | 'proposals' | 'explorer' interface NetworkTabsProps { tab: TabValue @@ -15,9 +15,10 @@ interface NetworkTabsProps { share?: React.ReactNode channelUpdate?: React.ReactNode proposals?: React.ReactNode + explorer?: React.ReactNode } -export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consenters, chaincode, share, channelUpdate, proposals }: NetworkTabsProps) { +export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consenters, chaincode, share, channelUpdate, proposals, explorer }: NetworkTabsProps) { // Check if current tab is a pro feature and redirect to details if needed useEffect(() => { const proTabs: TabValue[] = ['share', 'proposals'] @@ -72,6 +73,12 @@ export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consente
)} + + {explorer && ( + + Explorer + + )} @@ -114,6 +121,12 @@ export function NetworkTabs({ tab, setTab, networkDetails, anchorPeers, consente )} )} + + {explorer && ( + + {explorer} + + )}
) } From 6e20bdc729fa7f9675f12bda1a7369386b753ad0 Mon Sep 17 00:00:00 2001 From: dviejokfs Date: Tue, 8 Apr 2025 11:37:04 +0200 Subject: [PATCH 13/31] Enhance service struct to include LogPath - Added LogPath field to the service struct in the peer service layer to facilitate logging functionality. - This change improves the ability to track and manage logs associated with service operations. Signed-off-by: dviejokfs --- pkg/nodes/peer/service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/nodes/peer/service.go b/pkg/nodes/peer/service.go index 8ce2de8..258ec00 100644 --- a/pkg/nodes/peer/service.go +++ b/pkg/nodes/peer/service.go @@ -76,11 +76,13 @@ WantedBy=multi-user.target DirPath string Cmd string EnvVars []string + LogPath string }{ ID: p.opts.ID, DirPath: dirPath, Cmd: cmd, EnvVars: envStrings, + LogPath: p.GetStdOutPath(), } var buf bytes.Buffer From f0a01939ba54feb2fb8cca16ff7f433841328600 Mon Sep 17 00:00:00 2001 From: David VIEJO Date: Wed, 9 Apr 2025 10:21:49 +0200 Subject: [PATCH 14/31] Fix import channel Signed-off-by: David VIEJO --- pkg/networks/service/fabric/org/org.go | 10 ++- .../network-import/ImportNetworkForm.tsx | 12 +++- web/src/lib/utils.ts | 12 ++++ web/src/pages/networks/besu/create.tsx | 64 +++++++++++++------ 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/pkg/networks/service/fabric/org/org.go b/pkg/networks/service/fabric/org/org.go index a2e614e..0f6bb83 100644 --- a/pkg/networks/service/fabric/org/org.go +++ b/pkg/networks/service/fabric/org/org.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "fmt" + "strings" "google.golang.org/grpc" "google.golang.org/protobuf/proto" @@ -209,7 +210,7 @@ func (s *FabricOrg) getOrdererConnection(ctx context.Context, ordererURL string, // Create orderer connection ordererConn, err := network.DialConnection(network.Node{ - Addr: ordererURL, + Addr: strings.TrimPrefix(ordererURL, "grpcs://"), TLSCACertByte: []byte(ordererTLSCert), }) if err != nil { @@ -264,9 +265,12 @@ func (s *FabricOrg) GetGenesisBlock(ctx context.Context, channelID string, order return nil, fmt.Errorf("failed to get orderer msp: %w", err) } - ordererTLSKeyPair, err := s.getOrdererTLSKeyPair(ctx, string(ordererTLSCert)) + // Create TLS certificate from orderer TLS cert + ordererTLSKeyPair := tls.Certificate{ + Certificate: [][]byte{ordererTLSCert}, + } if err != nil { - return nil, fmt.Errorf("failed to get orderer tls key pair: %w", err) + return nil, fmt.Errorf("failed to create orderer TLS certificate: %w", err) } genesisBlock, err := channel.GetGenesisBlock(ctx, ordererConn, ordererMSP, channelID, ordererTLSKeyPair) if err != nil { diff --git a/web/src/components/network-import/ImportNetworkForm.tsx b/web/src/components/network-import/ImportNetworkForm.tsx index 1dbd85a..72b1332 100644 --- a/web/src/components/network-import/ImportNetworkForm.tsx +++ b/web/src/components/network-import/ImportNetworkForm.tsx @@ -87,6 +87,13 @@ export function ImportNetworkForm() { toast.success('Network imported successfully') navigate('/networks') }, + onError: (error: Error) => { + const errorMessage = error.message || 'Failed to import Fabric network' + setError(errorMessage) + toast.error('Failed to import network', { + description: errorMessage, + }) + }, }) const importBesuNetwork = useMutation({ @@ -109,7 +116,7 @@ export function ImportNetworkForm() { defaultValues: { networkType: 'fabric', fabricImport: { - importMethod: 'genesis', + importMethod: 'organization', }, }, }) @@ -126,6 +133,7 @@ export function ImportNetworkForm() { setError(null) if (data.networkType === 'fabric') { + console.log('data.fabricImport', data.fabricImport) if (data.fabricImport.importMethod === 'genesis') { if (!data.fabricImport.genesisBlock) { setError('Genesis block is required') @@ -194,7 +202,7 @@ export function ImportNetworkForm() { } } - const isLoading = importFabricNetwork.isPending || importBesuNetwork.isPending + const isLoading = importFabricNetwork.isPending || importBesuNetwork.isPending || importFabricNetworkByOrg.isPending return ( diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 76243f8..b9222f6 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -15,3 +15,15 @@ export function slugify(text: string): string { .replace(/^-+/, '') .replace(/-+$/, '') } + +export function numberToHex(num: number): string { + return `0x${num.toString(16)}` +} + +export function hexToNumber(hex: string): number { + return parseInt(hex.replace('0x', ''), 16) +} + +export function isValidHex(hex: string): boolean { + return /^0x[0-9a-fA-F]+$/.test(hex) +} diff --git a/web/src/pages/networks/besu/create.tsx b/web/src/pages/networks/besu/create.tsx index 97ca89f..d18fffa 100644 --- a/web/src/pages/networks/besu/create.tsx +++ b/web/src/pages/networks/besu/create.tsx @@ -16,6 +16,7 @@ import { Progress } from '@/components/ui/progress' import { useState } from 'react' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { postKeys } from '@/api/client' +import { numberToHex, hexToNumber, isValidHex } from '@/lib/utils' const besuFormSchema = z .object({ @@ -28,14 +29,14 @@ const besuFormSchema = z chainId: z.number(), coinbase: z.string(), consensus: z.enum(['qbft']).default('qbft'), - difficulty: z.string(), + difficulty: z.string().refine((val) => isValidHex(val), { message: 'Must be a valid hex value starting with 0x' }), epochLength: z.number(), - gasLimit: z.string(), + gasLimit: z.string().refine((val) => isValidHex(val), { message: 'Must be a valid hex value starting with 0x' }), initialValidatorsKeyIds: z.array(z.number()), - mixHash: z.string(), - nonce: z.string(), + mixHash: z.string().refine((val) => isValidHex(val), { message: 'Must be a valid hex value starting with 0x' }), + nonce: z.string().refine((val) => isValidHex(val), { message: 'Must be a valid hex value starting with 0x' }), requestTimeout: z.number(), - timestamp: z.string(), + timestamp: z.string().refine((val) => isValidHex(val), { message: 'Must be a valid hex value starting with 0x' }), }) .refine( (data) => { @@ -61,14 +62,14 @@ const defaultValues: Partial = { chainId: 1337, coinbase: '0x0000000000000000000000000000000000000000', consensus: 'qbft', - difficulty: '0x1', + difficulty: numberToHex(1), epochLength: 30000, - gasLimit: '0x29b92700', + gasLimit: numberToHex(700000000), initialValidatorsKeyIds: [], mixHash: '0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365', - nonce: '0x0', + nonce: numberToHex(0), requestTimeout: 10, - timestamp: '0x67b1a088', + timestamp: numberToHex(1740000392), } export default function CreateBesuNetworkPage() { @@ -87,7 +88,10 @@ export default function CreateBesuNetworkPage() { const form = useForm({ resolver: zodResolver(besuFormSchema), - defaultValues, + defaultValues: { + ...defaultValues, + timestamp: numberToHex(new Date().getTime()), + }, }) const { data: providersData } = useQuery({ ...getKeyProvidersOptions({}), @@ -317,9 +321,14 @@ export default function CreateBesuNetworkPage() { Difficulty - + field.onChange(numberToHex(Number(e.target.value)))} + min={0} + /> - Initial mining difficulty + Initial mining difficulty (will be converted to hex) )} @@ -349,9 +358,14 @@ export default function CreateBesuNetworkPage() { Gas Limit - + field.onChange(numberToHex(Number(e.target.value)))} + min={0} + /> - Maximum gas per block + Maximum gas per block (will be converted to hex) )} @@ -383,7 +397,7 @@ export default function CreateBesuNetworkPage() { - Consensus-specific hash + Consensus-specific hash (Only used in PoW networks) )} @@ -398,9 +412,14 @@ export default function CreateBesuNetworkPage() { Nonce - + field.onChange(numberToHex(Number(e.target.value)))} + min={0} + /> - Genesis block nonce + Genesis block nonce (will be converted to hex) )} @@ -413,16 +432,21 @@ export default function CreateBesuNetworkPage() { Timestamp - + field.onChange(numberToHex(Number(e.target.value)))} + min={0} + /> - Genesis block timestamp + Genesis block timestamp (will be converted to hex) )} /> -
+
Date: Thu, 10 Apr 2025 02:49:51 +0200 Subject: [PATCH 15/31] Add address overrides Signed-off-by: David VIEJO --- docs/docs.go | 244 +- docs/swagger.json | 242 ++ docs/swagger.yaml | 161 + pkg/db/db.go | 20 + pkg/db/querier.go | 2 + pkg/db/queries.sql | 15 + pkg/db/queries.sql.go | 80 + pkg/nodes/http/handler.go | 134 + pkg/nodes/http/types.go | 78 +- pkg/nodes/orderer/orderer.go | 12 +- pkg/nodes/orderer/types.go | 35 +- pkg/nodes/peer/peer.go | 3403 +++++++++-------- pkg/nodes/peer/types.go | 21 +- pkg/nodes/service/config.go | 5 +- pkg/nodes/service/service.go | 672 +++- pkg/nodes/service/types.go | 29 + pkg/nodes/types/deployment.go | 110 + pkg/nodes/types/types.go | 6 + web/src/App.tsx | 2 + .../api/client/@tanstack/react-query.gen.ts | 18 +- web/src/api/client/sdk.gen.ts | 17 +- web/src/api/client/types.gen.ts | 126 + web/src/components/nodes/FabricPeerConfig.tsx | 84 + web/src/components/nodes/fabric-node-form.tsx | 125 +- web/src/pages/nodes/[id].tsx | 12 +- web/src/pages/nodes/fabric/edit.tsx | 157 + 26 files changed, 4084 insertions(+), 1726 deletions(-) create mode 100644 pkg/nodes/service/types.go create mode 100644 web/src/pages/nodes/fabric/edit.tsx diff --git a/docs/docs.go b/docs/docs.go index 82e9f88..a8fa9de 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Package docs Code generated by swaggo/swag at 2025-04-07 17:07:59.846415 +0200 CEST m=+1.603514084. DO NOT EDIT +// Package docs Code generated by swaggo/swag at 2025-04-10 02:45:48.086693 +0200 CEST m=+1.259022210. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -3325,6 +3325,63 @@ const docTemplate = `{ } } }, + "put": { + "description": "Updates an existing node's configuration based on its type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Update a node", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update node request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateNodeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, "delete": { "description": "Delete a node by ID", "consumes": [ @@ -5745,6 +5802,32 @@ const docTemplate = `{ } } }, + "http.UpdateBesuNodeRequest": { + "type": "object", + "properties": { + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "networkConfig": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "p2pPort": { + "type": "integer" + }, + "rpcPort": { + "type": "integer" + }, + "wsPort": { + "type": "integer" + } + } + }, "http.UpdateConsenterPayload": { "type": "object", "required": [ @@ -5826,6 +5909,99 @@ const docTemplate = `{ } } }, + "http.UpdateFabricOrdererRequest": { + "type": "object", + "properties": { + "adminAddress": { + "type": "string" + }, + "domainNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "externalEndpoint": { + "type": "string" + }, + "listenAddress": { + "type": "string" + }, + "operationsListenAddress": { + "type": "string" + } + } + }, + "http.UpdateFabricPeerRequest": { + "type": "object", + "properties": { + "addressOverrides": { + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, + "chaincodeAddress": { + "type": "string" + }, + "domainNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "eventsAddress": { + "type": "string" + }, + "externalEndpoint": { + "type": "string" + }, + "listenAddress": { + "type": "string" + }, + "operationsListenAddress": { + "type": "string" + } + } + }, + "http.UpdateNodeRequest": { + "type": "object", + "properties": { + "besuNode": { + "$ref": "#/definitions/http.UpdateBesuNodeRequest" + }, + "blockchainPlatform": { + "$ref": "#/definitions/types.BlockchainPlatform" + }, + "fabricOrderer": { + "$ref": "#/definitions/http.UpdateFabricOrdererRequest" + }, + "fabricPeer": { + "description": "Platform-specific configurations", + "allOf": [ + { + "$ref": "#/definitions/http.UpdateFabricPeerRequest" + } + ] + }, + "name": { + "description": "Common fields", + "type": "string" + } + } + }, "http.UpdateOrgMSPPayload": { "type": "object", "required": [ @@ -6455,6 +6631,12 @@ const docTemplate = `{ "service.FabricPeerProperties": { "type": "object", "properties": { + "addressOverrides": { + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "chaincodeAddress": { "type": "string" }, @@ -6504,6 +6686,9 @@ const docTemplate = `{ }, "tlsKeyId": { "type": "integer" + }, + "version": { + "type": "string" } } }, @@ -6675,6 +6860,20 @@ const docTemplate = `{ } } }, + "types.AddressOverride": { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "tlsCACert": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, "types.BesuNodeConfig": { "type": "object", "required": [ @@ -6755,6 +6954,13 @@ const docTemplate = `{ "organizationId" ], "properties": { + "addressOverrides": { + "description": "@Description Address overrides for the orderer", + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "adminAddress": { "type": "string" }, @@ -6813,6 +7019,13 @@ const docTemplate = `{ "organizationId" ], "properties": { + "addressOverrides": { + "description": "@Description Address overrides for the peer", + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "chaincodeAddress": { "description": "@Description Chaincode listen address", "type": "string", @@ -6867,6 +7080,13 @@ const docTemplate = `{ "type": "string", "example": "0.0.0.0:9443" }, + "ordererAddressOverrides": { + "description": "@Description Orderer address overrides for the peer", + "type": "array", + "items": { + "$ref": "#/definitions/types.OrdererAddressOverride" + } + }, "organizationId": { "description": "@Description Organization ID that owns this peer", "type": "integer", @@ -6918,6 +7138,28 @@ const docTemplate = `{ "NodeTypeBesuFullnode" ] }, + "types.OrdererAddressOverride": { + "type": "object", + "required": [ + "from", + "tlsCACert", + "to" + ], + "properties": { + "from": { + "description": "@Description Original orderer address", + "type": "string" + }, + "tlsCACert": { + "description": "@Description TLS CA certificate in PEM format", + "type": "string" + }, + "to": { + "description": "@Description New orderer address to use", + "type": "string" + } + } + }, "url.URL": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 425308b..624b514 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3323,6 +3323,63 @@ } } }, + "put": { + "description": "Updates an existing node's configuration based on its type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "nodes" + ], + "summary": "Update a node", + "parameters": [ + { + "type": "integer", + "description": "Node ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update node request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateNodeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/http.NodeResponse" + } + }, + "400": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, "delete": { "description": "Delete a node by ID", "consumes": [ @@ -5743,6 +5800,32 @@ } } }, + "http.UpdateBesuNodeRequest": { + "type": "object", + "properties": { + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "networkConfig": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "p2pPort": { + "type": "integer" + }, + "rpcPort": { + "type": "integer" + }, + "wsPort": { + "type": "integer" + } + } + }, "http.UpdateConsenterPayload": { "type": "object", "required": [ @@ -5824,6 +5907,99 @@ } } }, + "http.UpdateFabricOrdererRequest": { + "type": "object", + "properties": { + "adminAddress": { + "type": "string" + }, + "domainNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "externalEndpoint": { + "type": "string" + }, + "listenAddress": { + "type": "string" + }, + "operationsListenAddress": { + "type": "string" + } + } + }, + "http.UpdateFabricPeerRequest": { + "type": "object", + "properties": { + "addressOverrides": { + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, + "chaincodeAddress": { + "type": "string" + }, + "domainNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "eventsAddress": { + "type": "string" + }, + "externalEndpoint": { + "type": "string" + }, + "listenAddress": { + "type": "string" + }, + "operationsListenAddress": { + "type": "string" + } + } + }, + "http.UpdateNodeRequest": { + "type": "object", + "properties": { + "besuNode": { + "$ref": "#/definitions/http.UpdateBesuNodeRequest" + }, + "blockchainPlatform": { + "$ref": "#/definitions/types.BlockchainPlatform" + }, + "fabricOrderer": { + "$ref": "#/definitions/http.UpdateFabricOrdererRequest" + }, + "fabricPeer": { + "description": "Platform-specific configurations", + "allOf": [ + { + "$ref": "#/definitions/http.UpdateFabricPeerRequest" + } + ] + }, + "name": { + "description": "Common fields", + "type": "string" + } + } + }, "http.UpdateOrgMSPPayload": { "type": "object", "required": [ @@ -6453,6 +6629,12 @@ "service.FabricPeerProperties": { "type": "object", "properties": { + "addressOverrides": { + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "chaincodeAddress": { "type": "string" }, @@ -6502,6 +6684,9 @@ }, "tlsKeyId": { "type": "integer" + }, + "version": { + "type": "string" } } }, @@ -6673,6 +6858,20 @@ } } }, + "types.AddressOverride": { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "tlsCACert": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, "types.BesuNodeConfig": { "type": "object", "required": [ @@ -6753,6 +6952,13 @@ "organizationId" ], "properties": { + "addressOverrides": { + "description": "@Description Address overrides for the orderer", + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "adminAddress": { "type": "string" }, @@ -6811,6 +7017,13 @@ "organizationId" ], "properties": { + "addressOverrides": { + "description": "@Description Address overrides for the peer", + "type": "array", + "items": { + "$ref": "#/definitions/types.AddressOverride" + } + }, "chaincodeAddress": { "description": "@Description Chaincode listen address", "type": "string", @@ -6865,6 +7078,13 @@ "type": "string", "example": "0.0.0.0:9443" }, + "ordererAddressOverrides": { + "description": "@Description Orderer address overrides for the peer", + "type": "array", + "items": { + "$ref": "#/definitions/types.OrdererAddressOverride" + } + }, "organizationId": { "description": "@Description Organization ID that owns this peer", "type": "integer", @@ -6916,6 +7136,28 @@ "NodeTypeBesuFullnode" ] }, + "types.OrdererAddressOverride": { + "type": "object", + "required": [ + "from", + "tlsCACert", + "to" + ], + "properties": { + "from": { + "description": "@Description Original orderer address", + "type": "string" + }, + "tlsCACert": { + "description": "@Description TLS CA certificate in PEM format", + "type": "string" + }, + "to": { + "description": "@Description New orderer address to use", + "type": "string" + } + } + }, "url.URL": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c2a7aa6..b5c6245 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1043,6 +1043,23 @@ definitions: required: - timeout type: object + http.UpdateBesuNodeRequest: + properties: + env: + additionalProperties: + type: string + type: object + networkConfig: + additionalProperties: + type: string + type: object + p2pPort: + type: integer + rpcPort: + type: integer + wsPort: + type: integer + type: object http.UpdateConsenterPayload: properties: client_tls_cert: @@ -1102,6 +1119,66 @@ definitions: required: - operations type: object + http.UpdateFabricOrdererRequest: + properties: + adminAddress: + type: string + domainNames: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + externalEndpoint: + type: string + listenAddress: + type: string + operationsListenAddress: + type: string + type: object + http.UpdateFabricPeerRequest: + properties: + addressOverrides: + items: + $ref: '#/definitions/types.AddressOverride' + type: array + chaincodeAddress: + type: string + domainNames: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + eventsAddress: + type: string + externalEndpoint: + type: string + listenAddress: + type: string + operationsListenAddress: + type: string + type: object + http.UpdateNodeRequest: + properties: + besuNode: + $ref: '#/definitions/http.UpdateBesuNodeRequest' + blockchainPlatform: + $ref: '#/definitions/types.BlockchainPlatform' + fabricOrderer: + $ref: '#/definitions/http.UpdateFabricOrdererRequest' + fabricPeer: + allOf: + - $ref: '#/definitions/http.UpdateFabricPeerRequest' + description: Platform-specific configurations + name: + description: Common fields + type: string + type: object http.UpdateOrgMSPPayload: properties: msp_id: @@ -1535,6 +1612,10 @@ definitions: type: object service.FabricPeerProperties: properties: + addressOverrides: + items: + $ref: '#/definitions/types.AddressOverride' + type: array chaincodeAddress: type: string domainNames: @@ -1569,6 +1650,8 @@ definitions: type: string tlsKeyId: type: integer + version: + type: string type: object service.Mode: enum: @@ -1682,6 +1765,15 @@ definitions: type: type: string type: object + types.AddressOverride: + properties: + from: + type: string + tlsCACert: + type: string + to: + type: string + type: object types.BesuNodeConfig: properties: bootNodes: @@ -1737,6 +1829,11 @@ definitions: - PlatformBesu types.FabricOrdererConfig: properties: + addressOverrides: + description: '@Description Address overrides for the orderer' + items: + $ref: '#/definitions/types.AddressOverride' + type: array adminAddress: type: string domainNames: @@ -1779,6 +1876,11 @@ definitions: types.FabricPeerConfig: description: Configuration for creating a new Fabric peer node properties: + addressOverrides: + description: '@Description Address overrides for the peer' + items: + $ref: '#/definitions/types.AddressOverride' + type: array chaincodeAddress: description: '@Description Chaincode listen address' example: 0.0.0.0:7052 @@ -1821,6 +1923,11 @@ definitions: description: '@Description Operations listen address' example: 0.0.0.0:9443 type: string + ordererAddressOverrides: + description: '@Description Orderer address overrides for the peer' + items: + $ref: '#/definitions/types.OrdererAddressOverride' + type: array organizationId: description: '@Description Organization ID that owns this peer' example: 1 @@ -1867,6 +1974,22 @@ definitions: - NodeTypeFabricPeer - NodeTypeFabricOrderer - NodeTypeBesuFullnode + types.OrdererAddressOverride: + properties: + from: + description: '@Description Original orderer address' + type: string + tlsCACert: + description: '@Description TLS CA certificate in PEM format' + type: string + to: + description: '@Description New orderer address to use' + type: string + required: + - from + - tlsCACert + - to + type: object url.URL: properties: forceQuery: @@ -4064,6 +4187,44 @@ paths: summary: Get a node tags: - nodes + put: + consumes: + - application/json + description: Updates an existing node's configuration based on its type + parameters: + - description: Node ID + in: path + name: id + required: true + type: integer + - description: Update node request + in: body + name: request + required: true + schema: + $ref: '#/definitions/http.UpdateNodeRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/http.NodeResponse' + "400": + description: Validation error + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Node not found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Update a node + tags: + - nodes /nodes/{id}/certificates/renew: post: consumes: diff --git a/pkg/db/db.go b/pkg/db/db.go index 13c834d..def452c 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -366,6 +366,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateBackupTargetStmt, err = db.PrepareContext(ctx, updateBackupTarget); err != nil { return nil, fmt.Errorf("error preparing query UpdateBackupTarget: %w", err) } + if q.updateDeploymentConfigStmt, err = db.PrepareContext(ctx, updateDeploymentConfig); err != nil { + return nil, fmt.Errorf("error preparing query UpdateDeploymentConfig: %w", err) + } if q.updateFabricOrganizationStmt, err = db.PrepareContext(ctx, updateFabricOrganization); err != nil { return nil, fmt.Errorf("error preparing query UpdateFabricOrganization: %w", err) } @@ -390,6 +393,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateNetworkStatusStmt, err = db.PrepareContext(ctx, updateNetworkStatus); err != nil { return nil, fmt.Errorf("error preparing query UpdateNetworkStatus: %w", err) } + if q.updateNodeConfigStmt, err = db.PrepareContext(ctx, updateNodeConfig); err != nil { + return nil, fmt.Errorf("error preparing query UpdateNodeConfig: %w", err) + } if q.updateNodeDeploymentConfigStmt, err = db.PrepareContext(ctx, updateNodeDeploymentConfig); err != nil { return nil, fmt.Errorf("error preparing query UpdateNodeDeploymentConfig: %w", err) } @@ -989,6 +995,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateBackupTargetStmt: %w", cerr) } } + if q.updateDeploymentConfigStmt != nil { + if cerr := q.updateDeploymentConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateDeploymentConfigStmt: %w", cerr) + } + } if q.updateFabricOrganizationStmt != nil { if cerr := q.updateFabricOrganizationStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateFabricOrganizationStmt: %w", cerr) @@ -1029,6 +1040,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateNetworkStatusStmt: %w", cerr) } } + if q.updateNodeConfigStmt != nil { + if cerr := q.updateNodeConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateNodeConfigStmt: %w", cerr) + } + } if q.updateNodeDeploymentConfigStmt != nil { if cerr := q.updateNodeDeploymentConfigStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateNodeDeploymentConfigStmt: %w", cerr) @@ -1222,6 +1238,7 @@ type Queries struct { updateBackupSizeStmt *sql.Stmt updateBackupStatusStmt *sql.Stmt updateBackupTargetStmt *sql.Stmt + updateDeploymentConfigStmt *sql.Stmt updateFabricOrganizationStmt *sql.Stmt updateKeyStmt *sql.Stmt updateKeyProviderStmt *sql.Stmt @@ -1230,6 +1247,7 @@ type Queries struct { updateNetworkNodeRoleStmt *sql.Stmt updateNetworkNodeStatusStmt *sql.Stmt updateNetworkStatusStmt *sql.Stmt + updateNodeConfigStmt *sql.Stmt updateNodeDeploymentConfigStmt *sql.Stmt updateNodeEndpointStmt *sql.Stmt updateNodePublicEndpointStmt *sql.Stmt @@ -1358,6 +1376,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { updateBackupSizeStmt: q.updateBackupSizeStmt, updateBackupStatusStmt: q.updateBackupStatusStmt, updateBackupTargetStmt: q.updateBackupTargetStmt, + updateDeploymentConfigStmt: q.updateDeploymentConfigStmt, updateFabricOrganizationStmt: q.updateFabricOrganizationStmt, updateKeyStmt: q.updateKeyStmt, updateKeyProviderStmt: q.updateKeyProviderStmt, @@ -1366,6 +1385,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { updateNetworkNodeRoleStmt: q.updateNetworkNodeRoleStmt, updateNetworkNodeStatusStmt: q.updateNetworkNodeStatusStmt, updateNetworkStatusStmt: q.updateNetworkStatusStmt, + updateNodeConfigStmt: q.updateNodeConfigStmt, updateNodeDeploymentConfigStmt: q.updateNodeDeploymentConfigStmt, updateNodeEndpointStmt: q.updateNodeEndpointStmt, updateNodePublicEndpointStmt: q.updateNodePublicEndpointStmt, diff --git a/pkg/db/querier.go b/pkg/db/querier.go index a6b28b4..9914f24 100644 --- a/pkg/db/querier.go +++ b/pkg/db/querier.go @@ -125,6 +125,7 @@ type Querier interface { UpdateBackupSize(ctx context.Context, arg UpdateBackupSizeParams) (Backup, error) UpdateBackupStatus(ctx context.Context, arg UpdateBackupStatusParams) (Backup, error) UpdateBackupTarget(ctx context.Context, arg UpdateBackupTargetParams) (BackupTarget, error) + UpdateDeploymentConfig(ctx context.Context, arg UpdateDeploymentConfigParams) (Node, error) UpdateFabricOrganization(ctx context.Context, arg UpdateFabricOrganizationParams) (FabricOrganization, error) UpdateKey(ctx context.Context, arg UpdateKeyParams) (Key, error) UpdateKeyProvider(ctx context.Context, arg UpdateKeyProviderParams) (KeyProvider, error) @@ -133,6 +134,7 @@ type Querier interface { UpdateNetworkNodeRole(ctx context.Context, arg UpdateNetworkNodeRoleParams) (NetworkNode, error) UpdateNetworkNodeStatus(ctx context.Context, arg UpdateNetworkNodeStatusParams) (NetworkNode, error) UpdateNetworkStatus(ctx context.Context, arg UpdateNetworkStatusParams) error + UpdateNodeConfig(ctx context.Context, arg UpdateNodeConfigParams) (Node, error) UpdateNodeDeploymentConfig(ctx context.Context, arg UpdateNodeDeploymentConfigParams) (Node, error) UpdateNodeEndpoint(ctx context.Context, arg UpdateNodeEndpointParams) (Node, error) UpdateNodePublicEndpoint(ctx context.Context, arg UpdateNodePublicEndpointParams) (Node, error) diff --git a/pkg/db/queries.sql b/pkg/db/queries.sql index abc1c01..5943026 100644 --- a/pkg/db/queries.sql +++ b/pkg/db/queries.sql @@ -268,6 +268,21 @@ WHERE id = ? RETURNING *; +-- name: UpdateNodeConfig :one +UPDATE nodes +SET config = ?, + updated_at = CURRENT_TIMESTAMP +WHERE id = ? +RETURNING *; + +-- name: UpdateDeploymentConfig :one +UPDATE nodes +SET deployment_config = ?, + updated_at = CURRENT_TIMESTAMP +WHERE id = ? +RETURNING *; + + -- name: UpdateNodeStatus :one UPDATE nodes SET status = ?, diff --git a/pkg/db/queries.sql.go b/pkg/db/queries.sql.go index ac73a70..4cf23ce 100644 --- a/pkg/db/queries.sql.go +++ b/pkg/db/queries.sql.go @@ -3970,6 +3970,46 @@ func (q *Queries) UpdateBackupTarget(ctx context.Context, arg UpdateBackupTarget return i, err } +const updateDeploymentConfig = `-- name: UpdateDeploymentConfig :one +UPDATE nodes +SET deployment_config = ?, + updated_at = CURRENT_TIMESTAMP +WHERE id = ? +RETURNING id, name, slug, platform, status, description, network_id, config, resources, endpoint, public_endpoint, p2p_address, created_at, created_by, updated_at, fabric_organization_id, node_type, node_config, deployment_config +` + +type UpdateDeploymentConfigParams struct { + DeploymentConfig sql.NullString `json:"deployment_config"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateDeploymentConfig(ctx context.Context, arg UpdateDeploymentConfigParams) (Node, error) { + row := q.queryRow(ctx, q.updateDeploymentConfigStmt, updateDeploymentConfig, arg.DeploymentConfig, arg.ID) + var i Node + err := row.Scan( + &i.ID, + &i.Name, + &i.Slug, + &i.Platform, + &i.Status, + &i.Description, + &i.NetworkID, + &i.Config, + &i.Resources, + &i.Endpoint, + &i.PublicEndpoint, + &i.P2pAddress, + &i.CreatedAt, + &i.CreatedBy, + &i.UpdatedAt, + &i.FabricOrganizationID, + &i.NodeType, + &i.NodeConfig, + &i.DeploymentConfig, + ) + return i, err +} + const updateFabricOrganization = `-- name: UpdateFabricOrganization :one UPDATE fabric_organizations SET description = ? @@ -4268,6 +4308,46 @@ func (q *Queries) UpdateNetworkStatus(ctx context.Context, arg UpdateNetworkStat return err } +const updateNodeConfig = `-- name: UpdateNodeConfig :one +UPDATE nodes +SET config = ?, + updated_at = CURRENT_TIMESTAMP +WHERE id = ? +RETURNING id, name, slug, platform, status, description, network_id, config, resources, endpoint, public_endpoint, p2p_address, created_at, created_by, updated_at, fabric_organization_id, node_type, node_config, deployment_config +` + +type UpdateNodeConfigParams struct { + Config sql.NullString `json:"config"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateNodeConfig(ctx context.Context, arg UpdateNodeConfigParams) (Node, error) { + row := q.queryRow(ctx, q.updateNodeConfigStmt, updateNodeConfig, arg.Config, arg.ID) + var i Node + err := row.Scan( + &i.ID, + &i.Name, + &i.Slug, + &i.Platform, + &i.Status, + &i.Description, + &i.NetworkID, + &i.Config, + &i.Resources, + &i.Endpoint, + &i.PublicEndpoint, + &i.P2pAddress, + &i.CreatedAt, + &i.CreatedBy, + &i.UpdatedAt, + &i.FabricOrganizationID, + &i.NodeType, + &i.NodeConfig, + &i.DeploymentConfig, + ) + return i, err +} + const updateNodeDeploymentConfig = `-- name: UpdateNodeDeploymentConfig :one UPDATE nodes SET deployment_config = ?, diff --git a/pkg/nodes/http/handler.go b/pkg/nodes/http/handler.go index 28bfb17..f204042 100644 --- a/pkg/nodes/http/handler.go +++ b/pkg/nodes/http/handler.go @@ -61,6 +61,7 @@ func (h *NodeHandler) RegisterRoutes(r chi.Router) { r.Get("/{id}/events", response.Middleware(h.GetNodeEvents)) r.Get("/{id}/channels", response.Middleware(h.GetNodeChannels)) r.Post("/{id}/certificates/renew", response.Middleware(h.RenewCertificates)) + r.Put("/{id}", response.Middleware(h.UpdateNode)) }) } @@ -725,3 +726,136 @@ func (h *NodeHandler) RenewCertificates(w http.ResponseWriter, r *http.Request) return response.WriteJSON(w, http.StatusOK, toNodeResponse(node)) } + +// UpdateNode godoc +// @Summary Update a node +// @Description Updates an existing node's configuration based on its type +// @Tags nodes +// @Accept json +// @Produce json +// @Param id path int true "Node ID" +// @Param request body UpdateNodeRequest true "Update node request" +// @Success 200 {object} NodeResponse +// @Failure 400 {object} response.ErrorResponse "Validation error" +// @Failure 404 {object} response.ErrorResponse "Node not found" +// @Failure 500 {object} response.ErrorResponse "Internal server error" +// @Router /nodes/{id} [put] +func (h *NodeHandler) UpdateNode(w http.ResponseWriter, r *http.Request) error { + nodeID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + return errors.NewValidationError("invalid node ID", map[string]interface{}{ + "error": err.Error(), + }) + } + + var req UpdateNodeRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return errors.NewValidationError("invalid request body", map[string]interface{}{ + "error": err.Error(), + }) + } + + // Get the node to determine its type + node, err := h.service.GetNode(r.Context(), nodeID) + if err != nil { + if errors.IsType(err, errors.NotFoundError) { + return errors.NewNotFoundError("node not found", nil) + } + return errors.NewInternalError("failed to get node", err, nil) + } + + switch node.NodeType { + case types.NodeTypeFabricPeer: + if req.FabricPeer == nil { + return errors.NewValidationError("fabricPeer configuration is required for Fabric peer nodes", nil) + } + return h.updateFabricPeer(w, r, nodeID, req.FabricPeer) + case types.NodeTypeFabricOrderer: + if req.FabricOrderer == nil { + return errors.NewValidationError("fabricOrderer configuration is required for Fabric orderer nodes", nil) + } + return h.updateFabricOrderer(w, r, nodeID, req.FabricOrderer) + // TODO: Fix later + // case types.NodeTypeBesuFullnode: + // if req.BesuNode == nil { + // return errors.NewValidationError("besuNode configuration is required for Besu nodes", nil) + // } + // return h.updateBesuNode(w, r, nodeID, req.BesuNode) + default: + return errors.NewValidationError("unsupported node type", map[string]interface{}{ + "nodeType": node.NodeType, + }) + } +} + +// updateFabricPeer handles updating a Fabric peer node +func (h *NodeHandler) updateFabricPeer(w http.ResponseWriter, r *http.Request, nodeID int64, req *UpdateFabricPeerRequest) error { + opts := service.UpdateFabricPeerOpts{ + NodeID: nodeID, + } + + if req.ExternalEndpoint != nil { + opts.ExternalEndpoint = *req.ExternalEndpoint + } + if req.ListenAddress != nil { + opts.ListenAddress = *req.ListenAddress + } + if req.EventsAddress != nil { + opts.EventsAddress = *req.EventsAddress + } + if req.OperationsListenAddress != nil { + opts.OperationsListenAddress = *req.OperationsListenAddress + } + if req.ChaincodeAddress != nil { + opts.ChaincodeAddress = *req.ChaincodeAddress + } + if req.DomainNames != nil { + opts.DomainNames = req.DomainNames + } + if req.Env != nil { + opts.Env = req.Env + } + if req.AddressOverrides != nil { + opts.AddressOverrides = req.AddressOverrides + } + + updatedNode, err := h.service.UpdateFabricPeer(r.Context(), opts) + if err != nil { + return errors.NewInternalError("failed to update peer", err, nil) + } + + return response.WriteJSON(w, http.StatusOK, toNodeResponse(updatedNode)) +} + +// updateFabricOrderer handles updating a Fabric orderer node +func (h *NodeHandler) updateFabricOrderer(w http.ResponseWriter, r *http.Request, nodeID int64, req *UpdateFabricOrdererRequest) error { + opts := service.UpdateFabricOrdererOpts{ + NodeID: nodeID, + } + + if req.ExternalEndpoint != nil { + opts.ExternalEndpoint = *req.ExternalEndpoint + } + if req.ListenAddress != nil { + opts.ListenAddress = *req.ListenAddress + } + if req.AdminAddress != nil { + opts.AdminAddress = *req.AdminAddress + } + if req.OperationsListenAddress != nil { + opts.OperationsListenAddress = *req.OperationsListenAddress + } + if req.DomainNames != nil { + opts.DomainNames = req.DomainNames + } + if req.Env != nil { + opts.Env = req.Env + } + + updatedNode, err := h.service.UpdateFabricOrderer(r.Context(), opts) + if err != nil { + return errors.NewInternalError("failed to update orderer", err, nil) + } + + return response.WriteJSON(w, http.StatusOK, toNodeResponse(updatedNode)) +} diff --git a/pkg/nodes/http/types.go b/pkg/nodes/http/types.go index bfb9eec..e05cd23 100644 --- a/pkg/nodes/http/types.go +++ b/pkg/nodes/http/types.go @@ -4,6 +4,7 @@ import ( "time" "github.com/chainlaunch/chainlaunch/pkg/nodes/service" + "github.com/chainlaunch/chainlaunch/pkg/nodes/types" ) // NodeType represents the type of node @@ -38,19 +39,20 @@ type BaseNodeConfig struct { // FabricPeerConfig represents the configuration for a Fabric peer node type FabricPeerConfig struct { BaseNodeConfig - Name string `json:"name" validate:"required"` - OrganizationID int64 `json:"organizationId" validate:"required"` - MSPID string `json:"mspId" validate:"required"` - SignKeyID int64 `json:"signKeyId" validate:"required"` - TLSKeyID int64 `json:"tlsKeyId" validate:"required"` - ExternalEndpoint string `json:"externalEndpoint" validate:"required"` - ListenAddress string `json:"listenAddress" validate:"required"` - EventsAddress string `json:"eventsAddress" validate:"required"` - OperationsListenAddress string `json:"operationsListenAddress" validate:"required"` - ChaincodeAddress string `json:"chaincodeAddress" validate:"required"` - DomainNames []string `json:"domainNames"` - Env map[string]string `json:"env"` - Version string `json:"version"` // Fabric version to use + Name string `json:"name" validate:"required"` + OrganizationID int64 `json:"organizationId" validate:"required"` + MSPID string `json:"mspId" validate:"required"` + SignKeyID int64 `json:"signKeyId" validate:"required"` + TLSKeyID int64 `json:"tlsKeyId" validate:"required"` + ExternalEndpoint string `json:"externalEndpoint" validate:"required"` + ListenAddress string `json:"listenAddress" validate:"required"` + EventsAddress string `json:"eventsAddress" validate:"required"` + OperationsListenAddress string `json:"operationsListenAddress" validate:"required"` + ChaincodeAddress string `json:"chaincodeAddress" validate:"required"` + DomainNames []string `json:"domainNames"` + Env map[string]string `json:"env"` + Version string `json:"version"` // Fabric version to use + AddressOverrides []types.AddressOverride `json:"addressOverrides,omitempty"` } // FabricOrdererConfig represents the configuration for a Fabric orderer node @@ -166,3 +168,53 @@ type ListNodesResponse struct { PageCount int `json:"pageCount"` HasNextPage bool `json:"hasNextPage"` } + +// AddressOverride represents an address override configuration for Fabric nodes +type AddressOverride struct { + From string `json:"from"` + To string `json:"to"` + TLSCACert string `json:"tlsCACert"` +} + +// UpdateNodeRequest represents the request body for updating a node +type UpdateNodeRequest struct { + // Common fields + Name *string `json:"name,omitempty"` + BlockchainPlatform *types.BlockchainPlatform `json:"blockchainPlatform,omitempty"` + + // Platform-specific configurations + FabricPeer *UpdateFabricPeerRequest `json:"fabricPeer,omitempty"` + FabricOrderer *UpdateFabricOrdererRequest `json:"fabricOrderer,omitempty"` + BesuNode *UpdateBesuNodeRequest `json:"besuNode,omitempty"` +} + +// UpdateFabricPeerRequest represents the configuration for updating a Fabric peer node +type UpdateFabricPeerRequest struct { + ExternalEndpoint *string `json:"externalEndpoint,omitempty"` + ListenAddress *string `json:"listenAddress,omitempty"` + EventsAddress *string `json:"eventsAddress,omitempty"` + OperationsListenAddress *string `json:"operationsListenAddress,omitempty"` + ChaincodeAddress *string `json:"chaincodeAddress,omitempty"` + DomainNames []string `json:"domainNames,omitempty"` + Env map[string]string `json:"env,omitempty"` + AddressOverrides []types.AddressOverride `json:"addressOverrides,omitempty"` +} + +// UpdateFabricOrdererRequest represents the configuration for updating a Fabric orderer node +type UpdateFabricOrdererRequest struct { + ExternalEndpoint *string `json:"externalEndpoint,omitempty"` + ListenAddress *string `json:"listenAddress,omitempty"` + AdminAddress *string `json:"adminAddress,omitempty"` + OperationsListenAddress *string `json:"operationsListenAddress,omitempty"` + DomainNames []string `json:"domainNames,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +// UpdateBesuNodeRequest represents the configuration for updating a Besu node +type UpdateBesuNodeRequest struct { + P2PPort *int `json:"p2pPort,omitempty"` + RpcPort *int `json:"rpcPort,omitempty"` + WsPort *int `json:"wsPort,omitempty"` + NetworkConfig map[string]string `json:"networkConfig,omitempty"` + Env map[string]string `json:"env,omitempty"` +} diff --git a/pkg/nodes/orderer/orderer.go b/pkg/nodes/orderer/orderer.go index 102da26..dfee54e 100644 --- a/pkg/nodes/orderer/orderer.go +++ b/pkg/nodes/orderer/orderer.go @@ -203,7 +203,7 @@ func (o *LocalOrderer) buildOrdererEnvironment(mspConfigPath string) map[string] env["ORDERER_GENERAL_TLS_CERTIFICATE"] = filepath.Join(mspConfigPath, "tls.crt") env["ORDERER_GENERAL_TLS_PRIVATEKEY"] = filepath.Join(mspConfigPath, "tls.key") env["ORDERER_GENERAL_TLS_ROOTCAS"] = filepath.Join(mspConfigPath, "tlscacerts/cacert.pem") - env["ORDERER_ADMIN_LISTENADDRESS"] = o.opts.AdminAddress + env["ORDERER_ADMIN_LISTENADDRESS"] = o.opts.AdminListenAddress env["ORDERER_GENERAL_LISTENADDRESS"] = strings.Split(o.opts.ListenAddress, ":")[0] env["ORDERER_OPERATIONS_LISTENADDRESS"] = o.opts.OperationsListenAddress env["ORDERER_GENERAL_LOCALMSPID"] = o.mspID @@ -466,7 +466,7 @@ func (o *LocalOrderer) Init() (interface{}, error) { OrganizationID: o.organizationID, MSPID: o.mspID, ListenAddress: o.opts.ListenAddress, - AdminAddress: o.opts.AdminAddress, + AdminAddress: o.opts.AdminListenAddress, OperationsListenAddress: o.opts.OperationsListenAddress, ExternalEndpoint: o.opts.ExternalEndpoint, DomainNames: o.opts.DomainNames, @@ -901,7 +901,7 @@ Consensus: ListenAddress: strings.Split(o.opts.ListenAddress, ":")[0], ListenPort: strings.Split(o.opts.ListenAddress, ":")[1], OperationsListenAddress: o.opts.OperationsListenAddress, - AdminAddress: o.opts.AdminAddress, + AdminAddress: o.opts.AdminListenAddress, DataPath: dataConfigPath, MSPID: o.mspID, } @@ -951,7 +951,7 @@ func (o *LocalOrderer) JoinChannel(genesisBlock []byte) error { if !ok { return fmt.Errorf("couldn't append certs") } - ordererAdminUrl := fmt.Sprintf("https://%s", strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1)) + ordererAdminUrl := fmt.Sprintf("https://%s", strings.Replace(o.opts.AdminListenAddress, "0.0.0.0", "127.0.0.1", 1)) channelInfo, err := channel.JoinOrderer(ordererAdminUrl, genesisBlock, certPool, adminTlsCertX509) if err != nil { @@ -1010,7 +1010,7 @@ func (o *LocalOrderer) LeaveChannel(channelID string) error { if err != nil { return fmt.Errorf("failed to load client certificate: %w", err) } - adminAddress := strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1) + adminAddress := strings.Replace(o.opts.AdminListenAddress, "0.0.0.0", "127.0.0.1", 1) // Call osnadmin Remove API err = channel.RemoveChannelFromOrderer(fmt.Sprintf("https://%s", adminAddress), channelID, caCertPool, cert) if err != nil { @@ -1135,7 +1135,7 @@ func (o *LocalOrderer) GetChannels(ctx context.Context) ([]OrdererChannel, error } // Call osnadmin List API - adminAddress := strings.Replace(o.opts.AdminAddress, "0.0.0.0", "127.0.0.1", 1) + adminAddress := strings.Replace(o.opts.AdminListenAddress, "0.0.0.0", "127.0.0.1", 1) channelList, err := channel.ListChannel(fmt.Sprintf("https://%s", adminAddress), certPool, cert) if err != nil { return nil, fmt.Errorf("failed to list channels: %w", err) diff --git a/pkg/nodes/orderer/types.go b/pkg/nodes/orderer/types.go index ea3d190..541a00d 100644 --- a/pkg/nodes/orderer/types.go +++ b/pkg/nodes/orderer/types.go @@ -1,15 +1,25 @@ package orderer +import "github.com/chainlaunch/chainlaunch/pkg/nodes/types" + // StartOrdererOpts represents the options for starting an orderer type StartOrdererOpts struct { - ID string `json:"id"` - ListenAddress string `json:"listenAddress"` - AdminAddress string `json:"adminAddress"` - OperationsListenAddress string `json:"operationsListenAddress"` - ExternalEndpoint string `json:"externalEndpoint"` - DomainNames []string `json:"domainNames"` - Env map[string]string `json:"env"` - Version string `json:"version"` // Fabric version to use + ID string `json:"id"` + ListenAddress string `json:"listenAddress"` + OperationsListenAddress string `json:"operationsListenAddress"` + AdminListenAddress string `json:"adminListenAddress"` + ExternalEndpoint string `json:"externalEndpoint"` + DomainNames []string `json:"domainNames"` + Env map[string]string `json:"env"` + Version string `json:"version"` // Fabric version to use + AddressOverrides []types.AddressOverride `json:"addressOverrides,omitempty"` +} + +// AddressOverride represents an address override configuration +type AddressOverride struct { + From string `json:"from"` + To string `json:"to"` + TLSCACert string `json:"tlsCACert"` } // OrdererConfig represents the configuration for an orderer node @@ -17,7 +27,7 @@ type OrdererConfig struct { Mode string `json:"mode"` ListenAddress string `json:"listenAddress"` OperationsListenAddress string `json:"operationsListenAddress"` - AdminAddress string `json:"adminAddress"` + AdminListenAddress string `json:"adminListenAddress"` ExternalEndpoint string `json:"externalEndpoint"` SignCert string `json:"signCert"` SignCACert string `json:"signCACert"` @@ -40,3 +50,10 @@ type StartDockerResponse struct { Mode string `json:"mode"` ContainerName string `json:"containerName"` } + +// BlockInfo represents information about a block in the orderer +type BlockInfo struct { + Height uint64 `json:"height"` + CurrentBlockHash string `json:"currentBlockHash"` + PreviousBlockHash string `json:"previousBlockHash"` +} diff --git a/pkg/nodes/peer/peer.go b/pkg/nodes/peer/peer.go index 37d57ff..8d87d71 100644 --- a/pkg/nodes/peer/peer.go +++ b/pkg/nodes/peer/peer.go @@ -39,1868 +39,1903 @@ import ( "github.com/chainlaunch/chainlaunch/pkg/nodes/types" ) -// LocalPeer represents a local Fabric peer node -type LocalPeer struct { - mspID string - db *db.Queries - opts StartPeerOpts - mode string - org *fabricservice.OrganizationDTO - organizationID int64 - orgService *fabricservice.OrganizationService - keyService *keymanagement.KeyManagementService - nodeID int64 - logger *logger.Logger -} - -// NewLocalPeer creates a new LocalPeer instance -func NewLocalPeer( - mspID string, - db *db.Queries, - opts StartPeerOpts, - mode string, - org *fabricservice.OrganizationDTO, - organizationID int64, - orgService *fabricservice.OrganizationService, - keyService *keymanagement.KeyManagementService, - nodeID int64, - logger *logger.Logger, -) *LocalPeer { - return &LocalPeer{ - mspID: mspID, - db: db, - opts: opts, - mode: mode, - org: org, - organizationID: organizationID, - orgService: orgService, - keyService: keyService, - nodeID: nodeID, - logger: logger, - } -} - -// getServiceName returns the systemd service name -func (p *LocalPeer) getServiceName() string { - return fmt.Sprintf("fabric-peer-%s", strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) -} - -// getLaunchdServiceName returns the launchd service name -func (p *LocalPeer) getLaunchdServiceName() string { - return fmt.Sprintf("ai.chainlaunch.peer.%s.%s", - strings.ToLower(p.org.MspID), - strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) -} - -// getServiceFilePath returns the systemd service file path -func (p *LocalPeer) getServiceFilePath() string { - return fmt.Sprintf("/etc/systemd/system/%s.service", p.getServiceName()) +type AddressOverridePath struct { + From string + To string + TLSCAPath string } -// getLaunchdPlistPath returns the launchd plist file path -func (p *LocalPeer) getLaunchdPlistPath() string { - homeDir, _ := os.UserHomeDir() - return filepath.Join(homeDir, "Library/LaunchAgents", p.getLaunchdServiceName()+".plist") -} +const coreYamlTemplate = ` +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# -// GetStdOutPath returns the path to the stdout log file -func (p *LocalPeer) GetStdOutPath() string { - homeDir, _ := os.UserHomeDir() - dirPath := filepath.Join(homeDir, ".chainlaunch/peers", - strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) - return filepath.Join(dirPath, p.getServiceName()+".log") -} +############################################################################### +# +# Peer section +# +############################################################################### +peer: -func (p *LocalPeer) getPeerPath() string { - homeDir, _ := os.UserHomeDir() - return filepath.Join(homeDir, ".chainlaunch/peers", - strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) -} + # The peer id provides a name for this peer instance and is used when + # naming docker resources. + id: jdoe -// getContainerName returns the docker container name -func (p *LocalPeer) getContainerName() (string, error) { - org, err := p.orgService.GetOrganization(context.Background(), p.organizationID) - if err != nil { - return "", fmt.Errorf("failed to get organization: %w", err) - } - return fmt.Sprintf("%s-%s", - strings.ToLower(org.MspID), - strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")), nil -} + # The networkId allows for logical separation of networks and is used when + # naming docker resources. + networkId: dev -// findPeerBinary finds the peer binary in PATH -func (p *LocalPeer) findPeerBinary() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("failed to get home directory: %w", err) - } + # The Address at local network interface this Peer will listen on. + # By default, it will listen on all network interfaces + listenAddress: 0.0.0.0:7051 - downloader, err := binaries.NewBinaryDownloader(homeDir) - if err != nil { - return "", fmt.Errorf("failed to create binary downloader: %w", err) - } + # The endpoint this peer uses to listen for inbound chaincode connections. + # If this is commented-out, the listen address is selected to be + # the peer's address (see below) with port 7052 + # chaincodeListenAddress: 0.0.0.0:7052 - return downloader.GetBinaryPath(binaries.PeerBinary, p.opts.Version) -} + # The endpoint the chaincode for this peer uses to connect to the peer. + # If this is not specified, the chaincodeListenAddress address is selected. + # And if chaincodeListenAddress is not specified, address is selected from + # peer address (see below). If specified peer address is invalid then it + # will fallback to the auto detected IP (local IP) regardless of the peer + # addressAutoDetect value. + # chaincodeAddress: 0.0.0.0:7052 -// Init initializes the peer configuration -func (p *LocalPeer) Init() (types.NodeDeploymentConfig, error) { - ctx := context.Background() - // Get node from database - node, err := p.db.GetNode(ctx, p.nodeID) - if err != nil { - return nil, fmt.Errorf("failed to get node: %w", err) - } + # When used as peer config, this represents the endpoint to other peers + # in the same organization. For peers in other organization, see + # gossip.externalEndpoint for more info. + # When used as CLI config, this means the peer's endpoint to interact with + address: 0.0.0.0:7051 - p.logger.Info("Initializing peer", - "opts", p.opts, - "node", node, - "orgID", p.organizationID, - "nodeID", p.nodeID, - ) + # Whether the Peer should programmatically determine its address + # This case is useful for docker containers. + # When set to true, will override peer address. + addressAutoDetect: false - // Get organization - org, err := p.orgService.GetOrganization(ctx, p.organizationID) - if err != nil { - return nil, fmt.Errorf("failed to get organization: %w", err) - } + # Keepalive settings for peer server and clients + keepalive: + # Interval is the duration after which if the server does not see + # any activity from the client it pings the client to see if it's alive + interval: 7200s + # Timeout is the duration the server waits for a response + # from the client after sending a ping before closing the connection + timeout: 20s + # MinInterval is the minimum permitted time between client pings. + # If clients send pings more frequently, the peer server will + # disconnect them + minInterval: 60s + # Client keepalive settings for communicating with other peer nodes + client: + # Interval is the time between pings to peer nodes. This must + # greater than or equal to the minInterval specified by peer + # nodes + interval: 60s + # Timeout is the duration the client waits for a response from + # peer nodes before closing the connection + timeout: 20s + # DeliveryClient keepalive settings for communication with ordering + # nodes. + deliveryClient: + # Interval is the time between pings to ordering nodes. This must + # greater than or equal to the minInterval specified by ordering + # nodes. + interval: 60s + # Timeout is the duration the client waits for a response from + # ordering nodes before closing the connection + timeout: 20s - signCAKeyDB, err := p.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) - if err != nil { - return nil, fmt.Errorf("failed to retrieve sign CA cert: %w", err) - } - tlsCAKeyDB, err := p.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) - if err != nil { - return nil, fmt.Errorf("failed to retrieve TLS CA cert: %w", err) - } - isCA := 0 - description := "Sign key for " + p.opts.ID - curveP256 := kmodels.ECCurveP256 - providerID := 1 + # Gossip related configuration + gossip: + # Bootstrap set to initialize gossip with. + # This is a list of other peers that this peer reaches out to at startup. + # Important: The endpoints here have to be endpoints of peers in the same + # organization, because the peer would refuse connecting to these endpoints + # unless they are in the same organization as the peer. + bootstrap: 127.0.0.1:7051 - // Create Sign Key - signKeyDB, err := p.keyService.CreateKey(ctx, kmodels.CreateKeyRequest{ - Algorithm: kmodels.KeyAlgorithmEC, - Name: p.opts.ID, - IsCA: &isCA, - Description: &description, - Curve: &curveP256, - ProviderID: &providerID, - }, int(org.SignKeyID.Int64)) - if err != nil { - return nil, fmt.Errorf("failed to create sign key: %w", err) - } + # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. + # Setting both to true would result in the termination of the peer + # since this is undefined state. If the peers are configured with + # useLeaderElection=false, make sure there is at least 1 peer in the + # organization that its orgLeader is set to true. - // Sign Sign Key - signKeyDB, err = p.keyService.SignCertificate(ctx, signKeyDB.ID, signCAKeyDB.ID, kmodels.CertificateRequest{ - CommonName: p.opts.ID, - Organization: []string{org.MspID}, - OrganizationalUnit: []string{"peer"}, - DNSNames: []string{p.opts.ID}, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - }) - if err != nil { - return nil, fmt.Errorf("failed to sign sign key: %w", err) - } + # Defines whenever peer will initialize dynamic algorithm for + # "leader" selection, where leader is the peer to establish + # connection with ordering service and use delivery protocol + # to pull ledger blocks from ordering service. + useLeaderElection: false + # Statically defines peer to be an organization "leader", + # where this means that current peer will maintain connection + # with ordering service and disseminate block across peers in + # its own organization. Multiple peers or all peers in an organization + # may be configured as org leaders, so that they all pull + # blocks directly from ordering service. + orgLeader: true - signKey, err := p.keyService.GetDecryptedPrivateKey(int(signKeyDB.ID)) - if err != nil { - return nil, fmt.Errorf("failed to get sign private key: %w", err) - } + # Interval for membershipTracker polling + membershipTrackerInterval: 5s - // Create TLS key - tlsKeyDB, err := p.keyService.CreateKey(ctx, kmodels.CreateKeyRequest{ - Algorithm: kmodels.KeyAlgorithmEC, - Name: p.opts.ID, - IsCA: &isCA, - Description: &description, - Curve: &curveP256, - ProviderID: &providerID, - }, int(org.SignKeyID.Int64)) - if err != nil { - return nil, fmt.Errorf("failed to create sign key: %w", err) - } - domainNames := p.opts.DomainNames - - // Ensure localhost and 127.0.0.1 are included in domain names - hasLocalhost := false - hasLoopback := false - var ipAddresses []net.IP - var domains []string - for _, domain := range domainNames { - if domain == "localhost" { - hasLocalhost = true - domains = append(domains, domain) - continue - } - if domain == "127.0.0.1" { - hasLoopback = true - ipAddresses = append(ipAddresses, net.ParseIP(domain)) - continue - } - if ip := net.ParseIP(domain); ip != nil { - ipAddresses = append(ipAddresses, ip) - } else { - domains = append(domains, domain) - } - } - if !hasLocalhost { - domains = append(domains, "localhost") - } - if !hasLoopback { - ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) - } - p.opts.DomainNames = domains + # Overrides the endpoint that the peer publishes to peers + # in its organization. For peers in foreign organizations + # see 'externalEndpoint' + endpoint: + # Maximum count of blocks stored in memory + maxBlockCountToStore: 10 + # Max time between consecutive message pushes(unit: millisecond) + maxPropagationBurstLatency: 10ms + # Max number of messages stored until a push is triggered to remote peers + maxPropagationBurstSize: 10 + # Number of times a message is pushed to remote peers + propagateIterations: 1 + # Number of peers selected to push messages to + propagatePeerNum: 3 + # Determines frequency of pull phases(unit: second) + # Must be greater than digestWaitTime + responseWaitTime + pullInterval: 4s + # Number of peers to pull from + pullPeerNum: 3 + # Determines frequency of pulling state info messages from peers(unit: second) + requestStateInfoInterval: 4s + # Determines frequency of pushing state info messages to peers(unit: second) + publishStateInfoInterval: 4s + # Maximum time a stateInfo message is kept until expired + stateInfoRetentionInterval: + # Time from startup certificates are included in Alive messages(unit: second) + publishCertPeriod: 10s + # Should we skip verifying block messages or not (currently not in use) + skipBlockVerification: false + # Dial timeout(unit: second) + dialTimeout: 3s + # Connection timeout(unit: second) + connTimeout: 2s + # Buffer size of received messages + recvBuffSize: 20 + # Buffer size of sending messages + sendBuffSize: 200 + # Time to wait before pull engine processes incoming digests (unit: second) + # Should be slightly smaller than requestWaitTime + digestWaitTime: 1s + # Time to wait before pull engine removes incoming nonce (unit: milliseconds) + # Should be slightly bigger than digestWaitTime + requestWaitTime: 1500ms + # Time to wait before pull engine ends pull (unit: second) + responseWaitTime: 2s + # Alive check interval(unit: second) + aliveTimeInterval: 5s + # Alive expiration timeout(unit: second) + aliveExpirationTimeout: 25s + # Reconnect interval(unit: second) + reconnectInterval: 25s + # Max number of attempts to connect to a peer + maxConnectionAttempts: 120 + # Message expiration factor for alive messages + msgExpirationFactor: 20 + # This is an endpoint that is published to peers outside of the organization. + # If this isn't set, the peer will not be known to other organizations. + externalEndpoint: + # Leader election service configuration + election: + # Longest time peer waits for stable membership during leader election startup (unit: second) + startupGracePeriod: 15s + # Interval gossip membership samples to check its stability (unit: second) + membershipSampleInterval: 1s + # Time passes since last declaration message before peer decides to perform leader election (unit: second) + leaderAliveThreshold: 10s + # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) + leaderElectionDuration: 5s - // Sign TLS certificates - validFor := kmodels.Duration(time.Hour * 24 * 365) - tlsKeyDB, err = p.keyService.SignCertificate(ctx, tlsKeyDB.ID, tlsCAKeyDB.ID, kmodels.CertificateRequest{ - CommonName: p.opts.ID, - Organization: []string{org.MspID}, - OrganizationalUnit: []string{"peer"}, - DNSNames: domains, - IPAddresses: ipAddresses, - IsCA: true, - ValidFor: validFor, - KeyUsage: x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - }) - if err != nil { - return nil, fmt.Errorf("failed to sign TLS certificate: %w", err) - } - tlsKey, err := p.keyService.GetDecryptedPrivateKey(int(tlsKeyDB.ID)) - if err != nil { - return nil, fmt.Errorf("failed to get TLS private key: %w", err) - } - // Create directory structure - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("failed to get home directory: %w", err) - } + pvtData: + # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block + # would be attempted to be pulled from peers until the block would be committed without the private data + pullRetryThreshold: 60s + # As private data enters the transient store, it is associated with the peer's ledger's height at that time. + # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, + # and the private data residing inside the transient store that is guaranteed not to be purged. + # Private data is purged from the transient store when blocks with sequences that are multiples + # of transientstoreMaxBlockRetention are committed. + transientstoreMaxBlockRetention: 1000 + # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer + # at private data push at endorsement time. + pushAckTimeout: 3s + # Block to live pulling margin, used as a buffer + # to prevent peer from trying to pull private data + # from peers that is soon to be purged in next N blocks. + # This helps a newly joined peer catch up to current + # blockchain height quicker. + btlPullMargin: 10 + # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to + # pull from the other peers the most recent missing blocks with a maximum batch size limitation. + # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a + # single iteration. + reconcileBatchSize: 10 + # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning + # of the next reconciliation iteration. + reconcileSleepInterval: 1m + # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. + reconciliationEnabled: true + # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid + # transaction's private data from other peers need to be skipped during the commit time and pulled + # only through reconciler. + skipPullingInvalidTransactionsDuringCommit: false + # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. + # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values + # for disseminating private data. + # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to + # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. + implicitCollectionDisseminationPolicy: + # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully + # disseminate private data for its own implicit collection during endorsement. Default value is 0. + requiredPeerCount: 0 + # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to + # disseminate private data for its own implicit collection during endorsement. Default value is 1. + maxPeerCount: 1 - slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") - dirPath := filepath.Join(homeDir, ".chainlaunch", "peers", slugifiedID) - dataConfigPath := filepath.Join(dirPath, "data") - mspConfigPath := filepath.Join(dirPath, "config") + # Gossip state transfer related configuration + state: + # indicates whenever state transfer is enabled or not + # default value is true, i.e. state transfer is active + # and takes care to sync up missing blocks allowing + # lagging peer to catch up to speed with rest network + enabled: false + # checkInterval interval to check whether peer is lagging behind enough to + # request blocks via state transfer from another peer. + checkInterval: 10s + # responseTimeout amount of time to wait for state transfer response from + # other peers + responseTimeout: 3s + # batchSize the number of blocks to request via state transfer from another peer + batchSize: 10 + # blockBufferSize reflects the size of the re-ordering buffer + # which captures blocks and takes care to deliver them in order + # down to the ledger layer. The actual buffer size is bounded between + # 0 and 2*blockBufferSize, each channel maintains its own buffer + blockBufferSize: 20 + # maxRetries maximum number of re-tries to ask + # for single state transfer request + maxRetries: 3 - // Create directories - if err := os.MkdirAll(dataConfigPath, 0755); err != nil { - return nil, fmt.Errorf("failed to create data directory: %w", err) - } - if err := os.MkdirAll(mspConfigPath, 0755); err != nil { - return nil, fmt.Errorf("failed to create msp directory: %w", err) - } + # TLS Settings + tls: + # Require server-side TLS + enabled: false + # Require client certificates / mutual TLS. + # Note that clients that are not configured to use a certificate will + # fail to connect to the peer. + clientAuthRequired: false + # X.509 certificate used for TLS server + cert: + file: tls/server.crt + # Private key used for TLS server (and client if clientAuthEnabled + # is set to true + key: + file: tls/server.key + # Trusted root certificate chain for tls.cert + rootcert: + file: tls/ca.crt + # Set of root certificate authorities used to verify client certificates + clientRootCAs: + files: + - tls/ca.crt + # Private key used for TLS when making client connections. If + # not set, peer.tls.key.file will be used instead + clientKey: + file: + # X.509 certificate used for TLS when making client connections. + # If not set, peer.tls.cert.file will be used instead + clientCert: + file: - // Write certificates and keys - if err := p.writeCertificatesAndKeys(mspConfigPath, tlsKeyDB, signKeyDB, tlsKey, signKey, signCAKeyDB, tlsCAKeyDB); err != nil { - return nil, fmt.Errorf("failed to write certificates and keys: %w", err) - } + # Authentication contains configuration parameters related to authenticating + # client messages + authentication: + # the acceptable difference between the current server time and the + # client's time as specified in a client request message + timewindow: 15m - // Create external builders - if err := p.setupExternalBuilders(mspConfigPath); err != nil { - return nil, fmt.Errorf("failed to setup external builders: %w", err) - } + # Path on the file system where peer will store data (eg ledger). This + # location must be access control protected to prevent unintended + # modification that might corrupt the peer operations. + fileSystemPath: {{.DataPath}} - // Write config files - if err := p.writeConfigFiles(mspConfigPath, dataConfigPath); err != nil { - return nil, fmt.Errorf("failed to write config files: %w", err) - } + # BCCSP (Blockchain crypto provider): Select which crypto implementation or + # library to use + BCCSP: + Default: SW + # Settings for the SW crypto provider (i.e. when DEFAULT: SW) + SW: + # TODO: The default Hash and Security level needs refactoring to be + # fully configurable. Changing these defaults requires coordination + # SHA2 is hardcoded in several places, not only BCCSP + Hash: SHA2 + Security: 256 + # Location of Key Store + FileKeyStore: + # If "", defaults to 'mspConfigPath'/keystore + KeyStore: + # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) + PKCS11: + # Location of the PKCS11 module library + Library: + # Token Label + Label: + # User PIN + Pin: + Hash: + Security: - return &types.FabricPeerDeploymentConfig{ - BaseDeploymentConfig: types.BaseDeploymentConfig{ - Type: "fabric-peer", - Mode: p.mode, - }, - OrganizationID: p.organizationID, - MSPID: p.mspID, - SignKeyID: int64(signKeyDB.ID), - TLSKeyID: int64(tlsKeyDB.ID), - ListenAddress: p.opts.ListenAddress, - ChaincodeAddress: p.opts.ChaincodeAddress, - EventsAddress: p.opts.EventsAddress, - OperationsListenAddress: p.opts.OperationsListenAddress, - ExternalEndpoint: p.opts.ExternalEndpoint, - DomainNames: p.opts.DomainNames, - SignCert: *signKeyDB.Certificate, - TLSCert: *tlsKeyDB.Certificate, - CACert: *signCAKeyDB.Certificate, - TLSCACert: *tlsCAKeyDB.Certificate, - }, nil -} + # Path on the file system where peer will find MSP local configurations + mspConfigPath: msp -// Start starts the peer node -func (p *LocalPeer) Start() (interface{}, error) { - p.logger.Info("Starting peer", "opts", p.opts) - slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("failed to get home directory: %w", err) - } + # Identifier of the local MSP + # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- + # Deployers need to change the value of the localMspId string. + # In particular, the name of the local MSP ID of a peer needs + # to match the name of one of the MSPs in each of the channel + # that this peer is a member of. Otherwise this peer's messages + # will not be identified as valid by other nodes. + localMspId: SampleOrg - dirPath := filepath.Join(homeDir, ".chainlaunch/peers", slugifiedID) - mspConfigPath := filepath.Join(dirPath, "config") - dataConfigPath := filepath.Join(dirPath, "data") + # CLI common client config options + client: + # connection timeout + connTimeout: 3s - // Find peer binary - peerBinary, err := p.findPeerBinary() - if err != nil { - return nil, fmt.Errorf("failed to find peer binary: %w", err) - } + # Delivery service related config + deliveryclient: + # It sets the total time the delivery service may spend in reconnection + # attempts until its retry logic gives up and returns an error + reconnectTotalTimeThreshold: 3600s - // Build command and environment - cmd := fmt.Sprintf("%s node start", peerBinary) - env := p.buildPeerEnvironment(mspConfigPath) + # It sets the delivery service <-> ordering service node connection timeout + connTimeout: 3s - p.logger.Debug("Starting peer", - "mode", p.mode, - "cmd", cmd, - "env", env, - "dirPath", dirPath, - ) + # It sets the delivery service maximal delay between consecutive retries + reConnectBackoffThreshold: 3600s - switch p.mode { - case "service": - return p.startService(cmd, env, dirPath) - case "docker": - return p.startDocker(env, mspConfigPath, dataConfigPath) - default: - return nil, fmt.Errorf("invalid mode: %s", p.mode) - } -} + # A list of orderer endpoint addresses which should be overridden + # when found in channel configurations. +{{- if .AddressOverrides }} + addressOverrides: +{{- range $i, $override := .AddressOverrides }} + - from: {{ $override.From }} + to: {{ $override.To }} + caCertsFile: {{ $override.TLSCAPath }} +{{- end }} +{{- else }} + addressOverrides: [] +{{- end }} -// buildPeerEnvironment builds the environment variables for the peer -func (p *LocalPeer) buildPeerEnvironment(mspConfigPath string) map[string]string { - env := make(map[string]string) + # Type for the local MSP - by default it's of type bccsp + localMspType: bccsp - // Add custom environment variables from opts - for k, v := range p.opts.Env { - env[k] = v - } + # Used with Go profiling tools only in none production environment. In + # production, it should be disabled (eg enabled: false) + profile: + enabled: false + listenAddress: 0.0.0.0:6060 - // Add required environment variables - env["CORE_PEER_MSPCONFIGPATH"] = mspConfigPath - env["FABRIC_CFG_PATH"] = mspConfigPath - env["CORE_PEER_TLS_ROOTCERT_FILE"] = filepath.Join(mspConfigPath, "tlscacerts/cacert.pem") - env["CORE_PEER_TLS_KEY_FILE"] = filepath.Join(mspConfigPath, "tls.key") - env["CORE_PEER_TLS_CLIENTCERT_FILE"] = filepath.Join(mspConfigPath, "tls.crt") - env["CORE_PEER_TLS_CLIENTKEY_FILE"] = filepath.Join(mspConfigPath, "tls.key") - env["CORE_PEER_TLS_CERT_FILE"] = filepath.Join(mspConfigPath, "tls.crt") - env["CORE_PEER_TLS_CLIENTAUTHREQUIRED"] = "false" - env["CORE_PEER_TLS_CLIENTROOTCAS_FILES"] = filepath.Join(mspConfigPath, "tlscacerts/cacert.pem") - env["CORE_PEER_ADDRESS"] = p.opts.ExternalEndpoint - env["CORE_PEER_GOSSIP_EXTERNALENDPOINT"] = p.opts.ExternalEndpoint - env["CORE_PEER_GOSSIP_ENDPOINT"] = p.opts.ExternalEndpoint - env["CORE_PEER_LISTENADDRESS"] = p.opts.ListenAddress - env["CORE_PEER_CHAINCODELISTENADDRESS"] = p.opts.ChaincodeAddress - env["CORE_PEER_EVENTS_ADDRESS"] = p.opts.EventsAddress - env["CORE_OPERATIONS_LISTENADDRESS"] = p.opts.OperationsListenAddress - env["CORE_PEER_NETWORKID"] = "peer01-nid" - env["CORE_PEER_LOCALMSPID"] = p.mspID - env["CORE_PEER_ID"] = p.opts.ID - env["CORE_OPERATIONS_TLS_ENABLED"] = "false" - env["CORE_OPERATIONS_TLS_CLIENTAUTHREQUIRED"] = "false" - env["CORE_PEER_GOSSIP_ORGLEADER"] = "true" - env["CORE_PEER_GOSSIP_BOOTSTRAP"] = p.opts.ExternalEndpoint - env["CORE_PEER_PROFILE_ENABLED"] = "true" - env["CORE_PEER_ADDRESSAUTODETECT"] = "false" - env["CORE_LOGGING_GOSSIP"] = "info" - env["FABRIC_LOGGING_SPEC"] = "info" - env["CORE_LOGGING_LEDGER"] = "info" - env["CORE_LOGGING_MSP"] = "info" - env["CORE_PEER_COMMITTER_ENABLED"] = "true" - env["CORE_PEER_DISCOVERY_TOUCHPERIOD"] = "60s" - env["CORE_PEER_GOSSIP_USELEADERELECTION"] = "false" - env["CORE_PEER_DISCOVERY_PERIOD"] = "60s" - env["CORE_METRICS_PROVIDER"] = "prometheus" - env["CORE_LOGGING_CAUTHDSL"] = "info" - env["CORE_LOGGING_POLICIES"] = "info" - env["CORE_LEDGER_STATE_STATEDATABASE"] = "goleveldb" - env["CORE_PEER_TLS_ENABLED"] = "true" - env["CORE_LOGGING_GRPC"] = "info" - env["CORE_LOGGING_PEER"] = "info" + # Handlers defines custom handlers that can filter and mutate + # objects passing within the peer, such as: + # Auth filter - reject or forward proposals from clients + # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation + # Valid handler definition contains: + # - A name which is a factory method name defined in + # core/handlers/library/library.go for statically compiled handlers + # - library path to shared object binary for pluggable filters + # Auth filters and decorators are chained and executed in the order that + # they are defined. For example: + # authFilters: + # - + # name: FilterOne + # library: /opt/lib/filter.so + # - + # name: FilterTwo + # decorators: + # - + # name: DecoratorOne + # - + # name: DecoratorTwo + # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so + handlers: + authFilters: + - + name: DefaultAuth + - + name: ExpirationCheck # This filter checks identity x509 certificate expiration + decorators: + - + name: DefaultDecorator + endorsers: + escc: + name: DefaultEndorsement + library: + validators: + vscc: + name: DefaultValidation + library: - return env -} + # library: /etc/hyperledger/fabric/plugin/escc.so + # Number of goroutines that will execute transaction validation in parallel. + # By default, the peer chooses the number of CPUs on the machine. Set this + # variable to override that choice. + # NOTE: overriding this value might negatively influence the performance of + # the peer so please change this value only if you know what you're doing + validatorPoolSize: -// startDocker starts the peer in a docker container -func (p *LocalPeer) startDocker(env map[string]string, mspConfigPath, dataConfigPath string) (*StartDockerResponse, error) { - // Convert env map to array of "-e KEY=VALUE" arguments - var envArgs []string - for k, v := range env { - envArgs = append(envArgs, "-e", fmt.Sprintf("%s=%s", k, v)) - } + # The discovery service is used by clients to query information about peers, + # such as - which peers have joined a certain channel, what is the latest + # channel config, and most importantly - given a chaincode and a channel, + # what possible sets of peers satisfy the endorsement policy. + discovery: + enabled: true + # Whether the authentication cache is enabled or not. + authCacheEnabled: true + # The maximum size of the cache, after which a purge takes place + authCacheMaxSize: 1000 + # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation + authCachePurgeRetentionRatio: 0.75 + # Whether to allow non-admins to perform non channel scoped queries. + # When this is false, it means that only peer admins can perform non channel scoped queries. + orgMembersAllowedAccess: false - containerName, err := p.getContainerName() - if err != nil { - return nil, fmt.Errorf("failed to get container name: %w", err) - } + # Limits is used to configure some internal resource limits. + limits: + # Concurrency limits the number of concurrently running requests to a service on each peer. + # Currently this option is only applied to endorser service and deliver service. + # When the property is missing or the value is 0, the concurrency limit is disabled for the service. + concurrency: + # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, + # including both user chaincodes and system chaincodes. + endorserService: 2500 + # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. + deliverService: 2500 - // Prepare docker run command arguments - args := []string{ - "run", - "-d", - "--name", containerName, - } - args = append(args, envArgs...) - args = append(args, - "-v", fmt.Sprintf("%s:/etc/hyperledger/fabric/msp", mspConfigPath), - "-v", fmt.Sprintf("%s:/var/hyperledger/production", dataConfigPath), - "-p", fmt.Sprintf("%s:7051", strings.Split(p.opts.ListenAddress, ":")[1]), - "-p", fmt.Sprintf("%s:7052", strings.Split(p.opts.ChaincodeAddress, ":")[1]), - "-p", fmt.Sprintf("%s:7053", strings.Split(p.opts.EventsAddress, ":")[1]), - "-p", fmt.Sprintf("%s:9443", strings.Split(p.opts.OperationsListenAddress, ":")[1]), - "hyperledger/fabric-peer:2.5.9", - "peer", - "node", - "start", - ) +############################################################################### +# +# VM section +# +############################################################################### +vm: - cmd := exec.Command("docker", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + # Endpoint of the vm management system. For docker can be one of the following in general + # unix:///var/run/docker.sock + # http://localhost:2375 + # https://localhost:2376 + endpoint: "" - if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("failed to start docker container: %w", err) - } + # settings for docker vms + docker: + tls: + enabled: false + ca: + file: docker/ca.crt + cert: + file: docker/tls.crt + key: + file: docker/tls.key - return &StartDockerResponse{ - Mode: "docker", - ContainerName: containerName, - }, nil -} + # Enables/disables the standard out/err from chaincode containers for + # debugging purposes + attachStdout: false -// Stop stops the peer node -func (p *LocalPeer) Stop() error { - p.logger.Info("Stopping peer", "opts", p.opts) + # Parameters on creating docker container. + # Container may be efficiently created using ipam & dns-server for cluster + # NetworkMode - sets the networking mode for the container. Supported + # Dns - a list of DNS servers for the container to use. + # Docker Host Config are not supported and will not be used if set. + # LogConfig - sets the logging driver (Type) and related options + # (Config) for Docker. For more info, + # https://docs.docker.com/engine/admin/logging/overview/ + # Note: Set LogConfig using Environment Variables is not supported. + hostConfig: + NetworkMode: host + Dns: + # - 192.168.0.1 + LogConfig: + Type: json-file + Config: + max-size: "50m" + max-file: "5" + Memory: 2147483648 - switch p.mode { - case "service": - platform := runtime.GOOS - switch platform { - case "linux": - return p.stopSystemdService() - case "darwin": - return p.stopLaunchdService() - default: - return fmt.Errorf("unsupported platform for service mode: %s", platform) - } - case "docker": - return p.stopDocker() - default: - return fmt.Errorf("invalid mode: %s", p.mode) - } -} +############################################################################### +# +# Chaincode section +# +############################################################################### +chaincode: -// stopDocker stops the peer docker container -func (p *LocalPeer) stopDocker() error { - containerName, err := p.getContainerName() - if err != nil { - return fmt.Errorf("failed to get container name: %w", err) - } + # The id is used by the Chaincode stub to register the executing Chaincode + # ID with the Peer and is generally supplied through ENV variables + id: + path: + name: - // Stop the container - stopCmd := exec.Command("docker", "stop", containerName) - if err := stopCmd.Run(); err != nil { - return fmt.Errorf("failed to stop docker container: %w", err) - } + # Generic builder environment, suitable for most chaincode types + builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) - // Remove the container - rmCmd := exec.Command("docker", "rm", "-f", containerName) - if err := rmCmd.Run(); err != nil { - p.logger.Warn("Failed to remove docker container", "error", err) - // Don't return error as the container might not exist - } + pull: false - return nil -} + golang: + # golang will never need more than baseos + runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) -// stopSystemdService stops the systemd service -func (p *LocalPeer) stopSystemdService() error { - serviceName := p.getServiceName() + # whether or not golang chaincode should be linked dynamically + dynamicLink: false - // Stop the service - if err := p.execSystemctl("stop", serviceName); err != nil { - return fmt.Errorf("failed to stop systemd service: %w", err) - } + java: + # This is an image based on java:openjdk-8 with addition compiler + # tools added for java shim layer packaging. + # This image is packed with shim layer libraries that are necessary + # for Java chaincode runtime. + runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) - // Disable the service - if err := p.execSystemctl("disable", serviceName); err != nil { - p.logger.Warn("Failed to disable systemd service", "error", err) - // Don't return error as this is not critical - } + node: + # This is an image based on node:$(NODE_VER)-alpine + runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) - // Remove the service file - if err := os.Remove(p.getServiceFilePath()); err != nil { - if !os.IsNotExist(err) { - p.logger.Warn("Failed to remove service file", "error", err) - // Don't return error as this is not critical - } - } + # List of directories to treat as external builders and launchers for + # chaincode. The external builder detection processing will iterate over the + # builders in the order specified below. + externalBuilders: + - name: ccaas_builder + path: {{.ExternalBuilderPath}} + # The maximum duration to wait for the chaincode build and install process + # to complete. + installTimeout: 8m0s - // Reload systemd daemon - if err := p.execSystemctl("daemon-reload"); err != nil { - p.logger.Warn("Failed to reload systemd daemon", "error", err) - // Don't return error as this is not critical - } + # Timeout duration for starting up a container and waiting for Register + # to come through. + startuptimeout: 5m0s - return nil -} + # Timeout duration for Invoke and Init calls to prevent runaway. + # This timeout is used by all chaincodes in all the channels, including + # system chaincodes. + # Note that during Invoke, if the image is not available (e.g. being + # cleaned up when in development environment), the peer will automatically + # build the image, which might take more time. In production environment, + # the chaincode image is unlikely to be deleted, so the timeout could be + # reduced accordingly. + executetimeout: 30s -// stopLaunchdService stops the launchd service -func (p *LocalPeer) stopLaunchdService() error { - // Stop the service - stopCmd := exec.Command("launchctl", "stop", p.getLaunchdServiceName()) - if err := stopCmd.Run(); err != nil { - p.logger.Warn("Failed to stop launchd service", "error", err) - // Continue anyway as we want to make sure it's unloaded - } + # There are 2 modes: "dev" and "net". + # In dev mode, user runs the chaincode after starting peer from + # command line on local machine. + # In net mode, peer will run chaincode in a docker container. + mode: net - // Unload the service - unloadCmd := exec.Command("launchctl", "unload", p.getLaunchdPlistPath()) - if err := unloadCmd.Run(); err != nil { - return fmt.Errorf("failed to unload launchd service: %w", err) - } + # keepalive in seconds. In situations where the communication goes through a + # proxy that does not support keep-alive, this parameter will maintain connection + # between peer and chaincode. + # A value <= 0 turns keepalive off + keepalive: 0 - return nil -} + # enabled system chaincodes + system: + _lifecycle: enable + cscc: enable + lscc: enable + escc: enable + vscc: enable + qscc: enable -// execSystemctl executes a systemctl command -func (p *LocalPeer) execSystemctl(command string, args ...string) error { - cmdArgs := append([]string{command}, args...) + # Logging section for the chaincode container + logging: + # Default level for all loggers within the chaincode container + level: info + # Override default level for the 'shim' logger + shim: warning + # Format for the chaincode container logs + format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' - // Check if sudo is available - sudoPath, err := exec.LookPath("sudo") - if err == nil { - // sudo is available, use it - cmdArgs = append([]string{"systemctl"}, cmdArgs...) - cmd := exec.Command(sudoPath, cmdArgs...) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl %s failed: %w", command, err) - } - } else { - // sudo is not available, run directly - cmd := exec.Command("systemctl", cmdArgs...) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl %s failed: %w", command, err) - } - } +############################################################################### +# +# Ledger section - ledger configuration encompasses both the blockchain +# and the state +# +############################################################################### +ledger: - return nil -} + blockchain: + snapshots: + rootDir: {{.DataPath}}/snapshots -// RenewCertificates renews the peer's TLS and signing certificates -func (p *LocalPeer) RenewCertificates(peerDeploymentConfig *types.FabricPeerDeploymentConfig) error { + state: + # stateDatabase - options are "goleveldb", "CouchDB" + # goleveldb - default state database stored in goleveldb. + # CouchDB - store state database in CouchDB + stateDatabase: goleveldb + # Limit on the number of records to return per query + totalQueryLimit: 100000 + couchDBConfig: + # It is recommended to run CouchDB on the same server as the peer, and + # not map the CouchDB container port to a server port in docker-compose. + # Otherwise proper security must be provided on the connection between + # CouchDB client (on the peer) and server. + couchDBAddress: 127.0.0.1:5984 + # This username must have read and write authority on CouchDB + username: + # The password is recommended to pass as an environment variable + # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). + # If it is stored here, the file must be access control protected + # to prevent unintended users from discovering the password. + password: + # Number of retries for CouchDB errors + maxRetries: 3 + # Number of retries for CouchDB errors during peer startup. + # The delay between retries doubles for each attempt. + # Default of 10 retries results in 11 attempts over 2 minutes. + maxRetriesOnStartup: 10 + # CouchDB request timeout (unit: duration, e.g. 20s) + requestTimeout: 35s + # Limit on the number of records per each CouchDB query + # Note that chaincode queries are only bound by totalQueryLimit. + # Internally the chaincode may execute multiple CouchDB queries, + # each of size internalQueryLimit. + internalQueryLimit: 1000 + # Limit on the number of records per CouchDB bulk update batch + maxBatchUpdateSize: 1000 + # Warm indexes after every N blocks. + # This option warms any indexes that have been + # deployed to CouchDB after every N blocks. + # A value of 1 will warm indexes after every block commit, + # to ensure fast selector queries. + # Increasing the value may improve write efficiency of peer and CouchDB, + # but may degrade query response time. + warmIndexesAfterNBlocks: 1 + # Create the _global_changes system database + # This is optional. Creating the global changes database will require + # additional system resources to track changes and maintain the database + createGlobalChangesDB: false + # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state + # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple + # of 32 MB, the peer would round the size to the next multiple of 32 MB. + # To disable the cache, 0 MB needs to be assigned to the cacheSize. + cacheSize: 64 - ctx := context.Background() - p.logger.Info("Starting certificate renewal for peer", "peerID", p.opts.ID) + history: + # enableHistoryDatabase - options are true or false + # Indicates if the history of key updates should be stored. + # All history 'index' will be stored in goleveldb, regardless if using + # CouchDB or alternate database for the state. + enableHistoryDatabase: true - // Get organization details - org, err := p.orgService.GetOrganization(ctx, p.organizationID) - if err != nil { - return fmt.Errorf("failed to get organization: %w", err) - } + pvtdataStore: + # the maximum db batch size for converting + # the ineligible missing data entries to eligible missing data entries + collElgProcMaxDbBatchSize: 5000 + # the minimum duration (in milliseconds) between writing + # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries + collElgProcDbBatchesInterval: 1000 - if peerDeploymentConfig.SignKeyID == 0 || peerDeploymentConfig.TLSKeyID == 0 { - return fmt.Errorf("peer node does not have required key IDs") - } +############################################################################### +# +# Operations section +# +############################################################################### +operations: + # host and port for the operations server + listenAddress: 127.0.0.1:9443 - // Get the CA certificates - signCAKey, err := p.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) - if err != nil { - return fmt.Errorf("failed to get sign CA key: %w", err) - } + # TLS configuration for the operations endpoint + tls: + # TLS enabled + enabled: false - tlsCAKey, err := p.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) - if err != nil { - return fmt.Errorf("failed to get TLS CA key: %w", err) - } - // In case the sign key is not signed by the CA, set the signing key ID to the CA key ID - signKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.SignKeyID)) - if err != nil { - return fmt.Errorf("failed to get sign private key: %w", err) - } - if signKeyDB.SigningKeyID == nil || *signKeyDB.SigningKeyID == 0 { - // Set the signing key ID to the organization's sign CA key ID - err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.SignKeyID), int(signCAKey.ID)) - if err != nil { - return fmt.Errorf("failed to set signing key ID for sign key: %w", err) - } - } + # path to PEM encoded server certificate for the operations server + cert: + file: - tlsKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.TLSKeyID)) - if err != nil { - return fmt.Errorf("failed to get TLS private key: %w", err) - } + # path to PEM encoded server key for the operations server + key: + file: - if tlsKeyDB.SigningKeyID == nil || *tlsKeyDB.SigningKeyID == 0 { - // Set the signing key ID to the organization's sign CA key ID - err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.TLSKeyID), int(tlsCAKey.ID)) - if err != nil { - return fmt.Errorf("failed to set signing key ID for TLS key: %w", err) - } - } - // Renew signing certificate - validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity - _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ - CommonName: p.opts.ID, - Organization: []string{org.MspID}, - OrganizationalUnit: []string{"peer"}, - DNSNames: []string{p.opts.ID}, - IsCA: false, - ValidFor: validFor, - KeyUsage: x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - }) - if err != nil { - return fmt.Errorf("failed to renew signing certificate: %w", err) - } + # most operations service endpoints require client authentication when TLS + # is enabled. clientAuthRequired requires client certificate authentication + # at the TLS layer to access all resources. + clientAuthRequired: false - // Renew TLS certificate - domainNames := p.opts.DomainNames - var ipAddresses []net.IP - var domains []string + # paths to PEM encoded ca certificates to trust for client authentication + clientRootCAs: + files: [] - // Ensure localhost and 127.0.0.1 are included - hasLocalhost := false - hasLoopback := false - for _, domain := range domainNames { - if domain == "localhost" { - hasLocalhost = true - domains = append(domains, domain) - continue - } - if domain == "127.0.0.1" { - hasLoopback = true - ipAddresses = append(ipAddresses, net.ParseIP(domain)) - continue - } - if ip := net.ParseIP(domain); ip != nil { - ipAddresses = append(ipAddresses, ip) - } else { - domains = append(domains, domain) - } - } - if !hasLocalhost { - domains = append(domains, "localhost") - } - if !hasLoopback { - ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) - } +############################################################################### +# +# Metrics section +# +############################################################################### +metrics: + # metrics provider is one of statsd, prometheus, or disabled + provider: disabled - _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ - CommonName: p.opts.ID, - Organization: []string{org.MspID}, - OrganizationalUnit: []string{"peer"}, - DNSNames: domains, - IPAddresses: ipAddresses, - IsCA: false, - ValidFor: validFor, - KeyUsage: x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - }) - if err != nil { - return fmt.Errorf("failed to renew TLS certificate: %w", err) - } + # statsd configuration + statsd: + # network type: tcp or udp + network: udp - // Get the private keys - signKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.SignKeyID)) - if err != nil { - return fmt.Errorf("failed to get sign private key: %w", err) - } + # statsd server address + address: 127.0.0.1:8125 - tlsKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.TLSKeyID)) - if err != nil { - return fmt.Errorf("failed to get TLS private key: %w", err) - } + # the interval at which locally cached counters and gauges are pushed + # to statsd; timings are pushed immediately + writeInterval: 10s - // Update the certificates in the MSP directory - peerPath := p.getPeerPath() - mspConfigPath := filepath.Join(peerPath, "config") + # prefix is prepended to all emitted statsd metrics + prefix: - err = p.writeCertificatesAndKeys( - mspConfigPath, - tlsKeyDB, - signKeyDB, - tlsKey, - signKey, - signCAKey, - tlsCAKey, - ) - if err != nil { - return fmt.Errorf("failed to write renewed certificates: %w", err) - } +` - // Restart the peer - _, err = p.Start() - if err != nil { - return fmt.Errorf("failed to restart peer after certificate renewal: %w", err) - } +// LocalPeer represents a local Fabric peer node +type LocalPeer struct { + mspID string + db *db.Queries + opts StartPeerOpts + mode string + org *fabricservice.OrganizationDTO + organizationID int64 + orgService *fabricservice.OrganizationService + keyService *keymanagement.KeyManagementService + nodeID int64 + logger *logger.Logger +} - p.logger.Info("Successfully renewed peer certificates", "peerID", p.opts.ID) - p.logger.Info("Restarting peer after certificate renewal") - // Stop the peer before renewing certificates - if err := p.Stop(); err != nil { - return fmt.Errorf("failed to stop peer before certificate renewal: %w", err) - } - p.logger.Info("Successfully stopped peer before certificate renewal") - p.logger.Info("Starting peer after certificate renewal") - _, err = p.Start() - if err != nil { - return fmt.Errorf("failed to start peer after certificate renewal: %w", err) +// NewLocalPeer creates a new LocalPeer instance +func NewLocalPeer( + mspID string, + db *db.Queries, + opts StartPeerOpts, + mode string, + org *fabricservice.OrganizationDTO, + organizationID int64, + orgService *fabricservice.OrganizationService, + keyService *keymanagement.KeyManagementService, + nodeID int64, + logger *logger.Logger, +) *LocalPeer { + return &LocalPeer{ + mspID: mspID, + db: db, + opts: opts, + mode: mode, + org: org, + organizationID: organizationID, + orgService: orgService, + keyService: keyService, + nodeID: nodeID, + logger: logger, } - p.logger.Info("Successfully started peer after certificate renewal") - return nil } -type NetworkConfigResponse struct { - NetworkConfig string -} -type Org struct { - MSPID string - CertAuths []string - Peers []string - Orderers []string -} -type Peer struct { - Name string - URL string - TLSCACert string -} -type CA struct { - Name string - URL string - TLSCert string - EnrollID string - EnrollSecret string +// getServiceName returns the systemd service name +func (p *LocalPeer) getServiceName() string { + return fmt.Sprintf("fabric-peer-%s", strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) } -type Orderer struct { - URL string - Name string - TLSCACert string +// getLaunchdServiceName returns the launchd service name +func (p *LocalPeer) getLaunchdServiceName() string { + return fmt.Sprintf("ai.chainlaunch.peer.%s.%s", + strings.ToLower(p.org.MspID), + strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) } -const tmplGoConfig = ` -name: hlf-network -version: 1.0.0 -client: - organization: "{{ .Organization }}" -{{- if not .Organizations }} -organizations: {} -{{- else }} -organizations: - {{ range $org := .Organizations }} - {{ $org.MSPID }}: - mspid: {{ $org.MSPID }} - cryptoPath: /tmp/cryptopath - users: {} -{{- if not $org.CertAuths }} - certificateAuthorities: [] -{{- else }} - certificateAuthorities: - {{- range $ca := $org.CertAuths }} - - {{ $ca.Name }} - {{- end }} -{{- end }} -{{- if not $org.Peers }} - peers: [] -{{- else }} - peers: - {{- range $peer := $org.Peers }} - - {{ $peer }} - {{- end }} -{{- end }} -{{- if not $org.Orderers }} - orderers: [] -{{- else }} - orderers: - {{- range $orderer := $org.Orderers }} - - {{ $orderer }} - {{- end }} - - {{- end }} -{{- end }} -{{- end }} - -{{- if not .Orderers }} -{{- else }} -orderers: -{{- range $orderer := .Orderers }} - {{$orderer.Name}}: - url: {{ $orderer.URL }} - grpcOptions: - allow-insecure: false - tlsCACerts: - pem: | -{{ $orderer.TLSCACert | indent 8 }} -{{- end }} -{{- end }} - -{{- if not .Peers }} -{{- else }} -peers: - {{- range $peer := .Peers }} - {{$peer.Name}}: - url: {{ $peer.URL }} - tlsCACerts: - pem: | -{{ $peer.TLSCACert | indent 8 }} -{{- end }} -{{- end }} - -{{- if not .CertAuths }} -{{- else }} -certificateAuthorities: -{{- range $ca := .CertAuths }} - {{ $ca.Name }}: - url: https://{{ $ca.URL }} -{{if $ca.EnrollID }} - registrar: - enrollId: {{ $ca.EnrollID }} - enrollSecret: "{{ $ca.EnrollSecret }}" -{{ end }} - caName: {{ $ca.CAName }} - tlsCACerts: - pem: - - | -{{ $ca.TLSCert | indent 12 }} - -{{- end }} -{{- end }} +// getServiceFilePath returns the systemd service file path +func (p *LocalPeer) getServiceFilePath() string { + return fmt.Sprintf("/etc/systemd/system/%s.service", p.getServiceName()) +} -channels: - _default: -{{- if not .Orderers }} - orderers: [] -{{- else }} - orderers: -{{- range $orderer := .Orderers }} - - {{$orderer.Name}} -{{- end }} -{{- end }} -{{- if not .Peers }} - peers: {} -{{- else }} - peers: -{{- range $peer := .Peers }} - {{$peer.Name}}: - discover: true - endorsingPeer: true - chaincodeQuery: true - ledgerQuery: true - eventSource: true -{{- end }} -{{- end }} +// getLaunchdPlistPath returns the launchd plist file path +func (p *LocalPeer) getLaunchdPlistPath() string { + homeDir, _ := os.UserHomeDir() + return filepath.Join(homeDir, "Library/LaunchAgents", p.getLaunchdServiceName()+".plist") +} -` +// GetStdOutPath returns the path to the stdout log file +func (p *LocalPeer) GetStdOutPath() string { + homeDir, _ := os.UserHomeDir() + dirPath := filepath.Join(homeDir, ".chainlaunch/peers", + strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) + return filepath.Join(dirPath, p.getServiceName()+".log") +} -func (p *LocalPeer) generateNetworkConfigForPeer( - peerUrl string, peerMspID string, peerTlsCACert string, ordererUrl string, ordererTlsCACert string) (*NetworkConfigResponse, error) { +func (p *LocalPeer) getPeerPath() string { + homeDir, _ := os.UserHomeDir() + return filepath.Join(homeDir, ".chainlaunch/peers", + strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")) +} - tmpl, err := template.New("networkConfig").Funcs(sprig.HermeticTxtFuncMap()).Parse(tmplGoConfig) +// getContainerName returns the docker container name +func (p *LocalPeer) getContainerName() (string, error) { + org, err := p.orgService.GetOrganization(context.Background(), p.organizationID) if err != nil { - return nil, err + return "", fmt.Errorf("failed to get organization: %w", err) } - var buf bytes.Buffer - orgs := []*Org{} - var peers []*Peer - var certAuths []*CA - var ordererNodes []*Orderer + return fmt.Sprintf("%s-%s", + strings.ToLower(org.MspID), + strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-")), nil +} - org := &Org{ - MSPID: peerMspID, - CertAuths: []string{}, - Peers: []string{}, - Orderers: []string{}, - } - orgs = append(orgs, org) - if peerTlsCACert != "" { - peer := &Peer{ - Name: "peer0", - URL: peerUrl, - TLSCACert: peerTlsCACert, - } - org.Peers = append(org.Peers, "peer0") - peers = append(peers, peer) - } - if ordererTlsCACert != "" && ordererUrl != "" { - orderer := &Orderer{ - URL: ordererUrl, - Name: "orderer0", - TLSCACert: ordererTlsCACert, - } - ordererNodes = append(ordererNodes, orderer) - } - err = tmpl.Execute(&buf, map[string]interface{}{ - "Peers": peers, - "Orderers": ordererNodes, - "Organizations": orgs, - "CertAuths": certAuths, - "Organization": peerMspID, - "Internal": false, - }) +// findPeerBinary finds the peer binary in PATH +func (p *LocalPeer) findPeerBinary() (string, error) { + homeDir, err := os.UserHomeDir() if err != nil { - return nil, err + return "", fmt.Errorf("failed to get home directory: %w", err) } - p.logger.Debugf("Network config: %s", buf.String()) - return &NetworkConfigResponse{ - NetworkConfig: buf.String(), - }, nil -} -// JoinChannel joins the peer to a channel -func (p *LocalPeer) JoinChannel(genesisBlock []byte) error { - p.logger.Info("Joining peer to channel", "peer", p.opts.ID) - var genesisBlockProto cb.Block - err := proto.Unmarshal(genesisBlock, &genesisBlockProto) + downloader, err := binaries.NewBinaryDownloader(homeDir) if err != nil { - return fmt.Errorf("failed to unmarshal genesis block: %w", err) + return "", fmt.Errorf("failed to create binary downloader: %w", err) } + + return downloader.GetBinaryPath(binaries.PeerBinary, p.opts.Version) +} + +// Init initializes the peer configuration +func (p *LocalPeer) Init() (types.NodeDeploymentConfig, error) { ctx := context.Background() - tlsCACert, err := p.GetTLSRootCACert(ctx) + // Get node from database + node, err := p.db.GetNode(ctx, p.nodeID) if err != nil { - return fmt.Errorf("failed to get TLS root CA cert: %w", err) + return nil, fmt.Errorf("failed to get node: %w", err) } - peerConn, err := p.CreatePeerConnection(ctx, p.opts.ExternalEndpoint, tlsCACert) + + p.logger.Info("Initializing peer", + "opts", p.opts, + "node", node, + "orgID", p.organizationID, + "nodeID", p.nodeID, + ) + + // Get organization + org, err := p.orgService.GetOrganization(ctx, p.organizationID) if err != nil { - return fmt.Errorf("failed to create peer connection: %w", err) + return nil, fmt.Errorf("failed to get organization: %w", err) } - defer peerConn.Close() - adminIdentity, _, err := p.GetAdminIdentity(ctx) + signCAKeyDB, err := p.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) if err != nil { - return fmt.Errorf("failed to get admin identity: %w", err) + return nil, fmt.Errorf("failed to retrieve sign CA cert: %w", err) } - err = channel.JoinChannel(ctx, peerConn, adminIdentity, &genesisBlockProto) + tlsCAKeyDB, err := p.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) if err != nil { - return fmt.Errorf("failed to join channel: %w", err) + return nil, fmt.Errorf("failed to retrieve TLS CA cert: %w", err) } + isCA := 0 + description := "Sign key for " + p.opts.ID + curveP256 := kmodels.ECCurveP256 + providerID := 1 - return nil - -} - -// writeCertificatesAndKeys writes the certificates and keys to the MSP directory structure -func (p *LocalPeer) writeCertificatesAndKeys( - mspConfigPath string, - tlsCert *kmodels.KeyResponse, - signCert *kmodels.KeyResponse, - tlsKey string, - signKey string, - signCACert *kmodels.KeyResponse, - tlsCACert *kmodels.KeyResponse, -) error { - // Write TLS certificates and keys - if err := os.WriteFile(filepath.Join(mspConfigPath, "tls.crt"), []byte(*tlsCert.Certificate), 0644); err != nil { - return fmt.Errorf("failed to write TLS certificate: %w", err) - } - if err := os.WriteFile(filepath.Join(mspConfigPath, "tls.key"), []byte(tlsKey), 0600); err != nil { - return fmt.Errorf("failed to write TLS key: %w", err) + // Create Sign Key + signKeyDB, err := p.keyService.CreateKey(ctx, kmodels.CreateKeyRequest{ + Algorithm: kmodels.KeyAlgorithmEC, + Name: p.opts.ID, + IsCA: &isCA, + Description: &description, + Curve: &curveP256, + ProviderID: &providerID, + }, int(org.SignKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to create sign key: %w", err) } - // Create and write to signcerts directory - signcertsPath := filepath.Join(mspConfigPath, "signcerts") - if err := os.MkdirAll(signcertsPath, 0755); err != nil { - return fmt.Errorf("failed to create signcerts directory: %w", err) - } - if err := os.WriteFile(filepath.Join(signcertsPath, "cert.pem"), []byte(*signCert.Certificate), 0644); err != nil { - return fmt.Errorf("failed to write signing certificate: %w", err) + // Sign Sign Key + signKeyDB, err = p.keyService.SignCertificate(ctx, signKeyDB.ID, signCAKeyDB.ID, kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: []string{p.opts.ID}, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return nil, fmt.Errorf("failed to sign sign key: %w", err) } - // Write root CA certificate - if err := os.WriteFile(filepath.Join(mspConfigPath, "cacert.pem"), []byte(*signCACert.Certificate), 0644); err != nil { - return fmt.Errorf("failed to write CA certificate: %w", err) + signKey, err := p.keyService.GetDecryptedPrivateKey(int(signKeyDB.ID)) + if err != nil { + return nil, fmt.Errorf("failed to get sign private key: %w", err) } - // Create and write to cacerts directory - cacertsPath := filepath.Join(mspConfigPath, "cacerts") - if err := os.MkdirAll(cacertsPath, 0755); err != nil { - return fmt.Errorf("failed to create cacerts directory: %w", err) - } - if err := os.WriteFile(filepath.Join(cacertsPath, "cacert.pem"), []byte(*signCACert.Certificate), 0644); err != nil { - return fmt.Errorf("failed to write CA certificate to cacerts: %w", err) + // Create TLS key + tlsKeyDB, err := p.keyService.CreateKey(ctx, kmodels.CreateKeyRequest{ + Algorithm: kmodels.KeyAlgorithmEC, + Name: p.opts.ID, + IsCA: &isCA, + Description: &description, + Curve: &curveP256, + ProviderID: &providerID, + }, int(org.SignKeyID.Int64)) + if err != nil { + return nil, fmt.Errorf("failed to create sign key: %w", err) } + domainNames := p.opts.DomainNames - // Create and write to tlscacerts directory - tlscacertsPath := filepath.Join(mspConfigPath, "tlscacerts") - if err := os.MkdirAll(tlscacertsPath, 0755); err != nil { - return fmt.Errorf("failed to create tlscacerts directory: %w", err) + // Ensure localhost and 127.0.0.1 are included in domain names + hasLocalhost := false + hasLoopback := false + var ipAddresses []net.IP + var domains []string + for _, domain := range domainNames { + if domain == "localhost" { + hasLocalhost = true + domains = append(domains, domain) + continue + } + if domain == "127.0.0.1" { + hasLoopback = true + ipAddresses = append(ipAddresses, net.ParseIP(domain)) + continue + } + if ip := net.ParseIP(domain); ip != nil { + ipAddresses = append(ipAddresses, ip) + } else { + domains = append(domains, domain) + } } - if err := os.WriteFile(filepath.Join(tlscacertsPath, "cacert.pem"), []byte(*tlsCACert.Certificate), 0644); err != nil { - return fmt.Errorf("failed to write TLS CA certificate: %w", err) + if !hasLocalhost { + domains = append(domains, "localhost") + } + if !hasLoopback { + ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) } + p.opts.DomainNames = domains - // Create and write to keystore directory - keystorePath := filepath.Join(mspConfigPath, "keystore") - if err := os.MkdirAll(keystorePath, 0755); err != nil { - return fmt.Errorf("failed to create keystore directory: %w", err) + // Sign TLS certificates + validFor := kmodels.Duration(time.Hour * 24 * 365) + tlsKeyDB, err = p.keyService.SignCertificate(ctx, tlsKeyDB.ID, tlsCAKeyDB.ID, kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: domains, + IPAddresses: ipAddresses, + IsCA: true, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return nil, fmt.Errorf("failed to sign TLS certificate: %w", err) } - if err := os.WriteFile(filepath.Join(keystorePath, "key.pem"), []byte(signKey), 0600); err != nil { - return fmt.Errorf("failed to write signing key: %w", err) + tlsKey, err := p.keyService.GetDecryptedPrivateKey(int(tlsKeyDB.ID)) + if err != nil { + return nil, fmt.Errorf("failed to get TLS private key: %w", err) } - - return nil -} - -// setupExternalBuilders creates and configures the external builders for chaincode -func (p *LocalPeer) setupExternalBuilders(mspConfigPath string) error { - // Create external builder directory structure - rootExternalBuilderPath := filepath.Join(mspConfigPath, "ccaas") - binExternalBuilderPath := filepath.Join(rootExternalBuilderPath, "bin") - if err := os.MkdirAll(binExternalBuilderPath, 0755); err != nil { - return fmt.Errorf("failed to create external builder directory: %w", err) + // Create directory structure + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %w", err) } - // Create build script - buildScript := `#!/bin/bash + slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") + dirPath := filepath.Join(homeDir, ".chainlaunch", "peers", slugifiedID) + dataConfigPath := filepath.Join(dirPath, "data") + mspConfigPath := filepath.Join(dirPath, "config") -SOURCE=$1 -OUTPUT=$3 + // Create directories + if err := os.MkdirAll(dataConfigPath, 0755); err != nil { + return nil, fmt.Errorf("failed to create data directory: %w", err) + } + if err := os.MkdirAll(mspConfigPath, 0755); err != nil { + return nil, fmt.Errorf("failed to create msp directory: %w", err) + } -#external chaincodes expect connection.json file in the chaincode package -if [ ! -f "$SOURCE/connection.json" ]; then - >&2 echo "$SOURCE/connection.json not found" - exit 1 -fi + // Write certificates and keys + if err := p.writeCertificatesAndKeys(mspConfigPath, tlsKeyDB, signKeyDB, tlsKey, signKey, signCAKeyDB, tlsCAKeyDB); err != nil { + return nil, fmt.Errorf("failed to write certificates and keys: %w", err) + } -#simply copy the endpoint information to specified output location -cp $SOURCE/connection.json $OUTPUT/connection.json + // Create external builders + if err := p.setupExternalBuilders(mspConfigPath); err != nil { + return nil, fmt.Errorf("failed to setup external builders: %w", err) + } -if [ -d "$SOURCE/metadata" ]; then - cp -a $SOURCE/metadata $OUTPUT/metadata -fi + // Write config files + if err := p.writeConfigFiles(mspConfigPath, dataConfigPath); err != nil { + return nil, fmt.Errorf("failed to write config files: %w", err) + } -exit 0` + return &types.FabricPeerDeploymentConfig{ + BaseDeploymentConfig: types.BaseDeploymentConfig{ + Type: "fabric-peer", + Mode: p.mode, + }, + OrganizationID: p.organizationID, + MSPID: p.mspID, + SignKeyID: int64(signKeyDB.ID), + TLSKeyID: int64(tlsKeyDB.ID), + ListenAddress: p.opts.ListenAddress, + ChaincodeAddress: p.opts.ChaincodeAddress, + EventsAddress: p.opts.EventsAddress, + OperationsListenAddress: p.opts.OperationsListenAddress, + ExternalEndpoint: p.opts.ExternalEndpoint, + DomainNames: p.opts.DomainNames, + SignCert: *signKeyDB.Certificate, + TLSCert: *tlsKeyDB.Certificate, + CACert: *signCAKeyDB.Certificate, + TLSCACert: *tlsCAKeyDB.Certificate, + }, nil +} - if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "build"), []byte(buildScript), 0755); err != nil { - return fmt.Errorf("failed to write build script: %w", err) +// Start starts the peer node +func (p *LocalPeer) Start() (interface{}, error) { + p.logger.Info("Starting peer", "opts", p.opts) + slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %w", err) } - // Create detect script - detectScript := `#!/bin/bash + dirPath := filepath.Join(homeDir, ".chainlaunch/peers", slugifiedID) + mspConfigPath := filepath.Join(dirPath, "config") + dataConfigPath := filepath.Join(dirPath, "data") -METADIR=$2 -# check if the "type" field is set to "external" -# crude way without jq which is not in the default fabric peer image -TYPE=$(tr -d '\n' < "$METADIR/metadata.json" | awk -F':' '{ for (i = 1; i < NF; i++){ if ($i~/type/) { print $(i+1); break }}}'| cut -d\" -f2) + // Find peer binary + peerBinary, err := p.findPeerBinary() + if err != nil { + return nil, fmt.Errorf("failed to find peer binary: %w", err) + } -if [ "$TYPE" = "ccaas" ]; then - exit 0 -fi + // Build command and environment + cmd := fmt.Sprintf("%s node start", peerBinary) + env := p.buildPeerEnvironment(mspConfigPath) -exit 1` + p.logger.Debug("Starting peer", + "mode", p.mode, + "cmd", cmd, + "env", env, + "dirPath", dirPath, + ) - if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "detect"), []byte(detectScript), 0755); err != nil { - return fmt.Errorf("failed to write detect script: %w", err) + switch p.mode { + case "service": + return p.startService(cmd, env, dirPath) + case "docker": + return p.startDocker(env, mspConfigPath, dataConfigPath) + default: + return nil, fmt.Errorf("invalid mode: %s", p.mode) } +} - // Create release script - releaseScript := `#!/bin/bash - -BLD="$1" -RELEASE="$2" - -if [ -d "$BLD/metadata" ]; then - cp -a "$BLD/metadata/"* "$RELEASE/" -fi +// buildPeerEnvironment builds the environment variables for the peer +func (p *LocalPeer) buildPeerEnvironment(mspConfigPath string) map[string]string { + env := make(map[string]string) -#external chaincodes expect artifacts to be placed under "$RELEASE"/chaincode/server -if [ -f $BLD/connection.json ]; then - mkdir -p "$RELEASE"/chaincode/server - cp $BLD/connection.json "$RELEASE"/chaincode/server + // Add custom environment variables from opts + for k, v := range p.opts.Env { + env[k] = v + } - #if tls_required is true, copy TLS files (using above example, the fully qualified path for these fils would be "$RELEASE"/chaincode/server/tls) + // Add required environment variables + env["CORE_PEER_MSPCONFIGPATH"] = mspConfigPath + env["FABRIC_CFG_PATH"] = mspConfigPath + env["CORE_PEER_TLS_ROOTCERT_FILE"] = filepath.Join(mspConfigPath, "tlscacerts/cacert.pem") + env["CORE_PEER_TLS_KEY_FILE"] = filepath.Join(mspConfigPath, "tls.key") + env["CORE_PEER_TLS_CLIENTCERT_FILE"] = filepath.Join(mspConfigPath, "tls.crt") + env["CORE_PEER_TLS_CLIENTKEY_FILE"] = filepath.Join(mspConfigPath, "tls.key") + env["CORE_PEER_TLS_CERT_FILE"] = filepath.Join(mspConfigPath, "tls.crt") + env["CORE_PEER_TLS_CLIENTAUTHREQUIRED"] = "false" + env["CORE_PEER_TLS_CLIENTROOTCAS_FILES"] = filepath.Join(mspConfigPath, "tlscacerts/cacert.pem") + env["CORE_PEER_ADDRESS"] = p.opts.ExternalEndpoint + env["CORE_PEER_GOSSIP_EXTERNALENDPOINT"] = p.opts.ExternalEndpoint + env["CORE_PEER_GOSSIP_ENDPOINT"] = p.opts.ExternalEndpoint + env["CORE_PEER_LISTENADDRESS"] = p.opts.ListenAddress + env["CORE_PEER_CHAINCODELISTENADDRESS"] = p.opts.ChaincodeAddress + env["CORE_PEER_EVENTS_ADDRESS"] = p.opts.EventsAddress + env["CORE_OPERATIONS_LISTENADDRESS"] = p.opts.OperationsListenAddress + env["CORE_PEER_NETWORKID"] = "peer01-nid" + env["CORE_PEER_LOCALMSPID"] = p.mspID + env["CORE_PEER_ID"] = p.opts.ID + env["CORE_OPERATIONS_TLS_ENABLED"] = "false" + env["CORE_OPERATIONS_TLS_CLIENTAUTHREQUIRED"] = "false" + env["CORE_PEER_GOSSIP_ORGLEADER"] = "true" + env["CORE_PEER_GOSSIP_BOOTSTRAP"] = p.opts.ExternalEndpoint + env["CORE_PEER_PROFILE_ENABLED"] = "true" + env["CORE_PEER_ADDRESSAUTODETECT"] = "false" + env["CORE_LOGGING_GOSSIP"] = "info" + env["FABRIC_LOGGING_SPEC"] = "info" + env["CORE_LOGGING_LEDGER"] = "info" + env["CORE_LOGGING_MSP"] = "info" + env["CORE_PEER_COMMITTER_ENABLED"] = "true" + env["CORE_PEER_DISCOVERY_TOUCHPERIOD"] = "60s" + env["CORE_PEER_GOSSIP_USELEADERELECTION"] = "false" + env["CORE_PEER_DISCOVERY_PERIOD"] = "60s" + env["CORE_METRICS_PROVIDER"] = "prometheus" + env["CORE_LOGGING_CAUTHDSL"] = "info" + env["CORE_LOGGING_POLICIES"] = "info" + env["CORE_LEDGER_STATE_STATEDATABASE"] = "goleveldb" + env["CORE_PEER_TLS_ENABLED"] = "true" + env["CORE_LOGGING_GRPC"] = "info" + env["CORE_LOGGING_PEER"] = "info" - exit 0 -fi + // Handle orderer address overrides if present + if len(p.opts.AddressOverrides) > 0 { + convertedOverrides, err := p.convertAddressOverrides(mspConfigPath, p.opts.AddressOverrides) + if err != nil { + p.logger.Error("Failed to convert address overrides", "error", err) + return env + } -exit 1` + var overrides []string + for _, override := range convertedOverrides { + overrides = append(overrides, fmt.Sprintf("%s %s %s", + override.From, override.To, override.TLSCAPath)) + } - if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "release"), []byte(releaseScript), 0755); err != nil { - return fmt.Errorf("failed to write release script: %w", err) + // Set the address overrides environment variable + if len(overrides) > 0 { + env["CORE_PEER_DELIVERYCLIENT_ADDRESSOVERRIDES"] = strings.Join(overrides, ";") + } } - return nil + return env } -const configYamlContent = `NodeOUs: - Enable: true - ClientOUIdentifier: - Certificate: cacerts/cacert.pem - OrganizationalUnitIdentifier: client - PeerOUIdentifier: - Certificate: cacerts/cacert.pem - OrganizationalUnitIdentifier: peer - AdminOUIdentifier: - Certificate: cacerts/cacert.pem - OrganizationalUnitIdentifier: admin - OrdererOUIdentifier: - Certificate: cacerts/cacert.pem - OrganizationalUnitIdentifier: orderer -` +// startDocker starts the peer in a docker container +func (p *LocalPeer) startDocker(env map[string]string, mspConfigPath, dataConfigPath string) (*StartDockerResponse, error) { + // Convert env map to array of "-e KEY=VALUE" arguments + var envArgs []string + for k, v := range env { + envArgs = append(envArgs, "-e", fmt.Sprintf("%s=%s", k, v)) + } -// writeConfigFiles writes the config.yaml and core.yaml files -func (p *LocalPeer) writeConfigFiles(mspConfigPath, dataConfigPath string) error { - // Write config.yaml - if err := os.WriteFile(filepath.Join(mspConfigPath, "config.yaml"), []byte(configYamlContent), 0644); err != nil { - return fmt.Errorf("failed to write config.yaml: %w", err) + containerName, err := p.getContainerName() + if err != nil { + return nil, fmt.Errorf("failed to get container name: %w", err) } - // Define template data - data := struct { - PeerID string - ListenAddress string - ChaincodeAddress string - ExternalEndpoint string - DataPath string - MSPID string - ExternalBuilderPath string - OperationsListenAddress string - }{ - PeerID: p.opts.ID, - ListenAddress: p.opts.ListenAddress, - ChaincodeAddress: p.opts.ChaincodeAddress, - ExternalEndpoint: p.opts.ExternalEndpoint, - DataPath: dataConfigPath, - MSPID: p.mspID, - ExternalBuilderPath: filepath.Join(mspConfigPath, "ccaas"), - OperationsListenAddress: p.opts.OperationsListenAddress, + // Prepare docker run command arguments + args := []string{ + "run", + "-d", + "--name", containerName, } + args = append(args, envArgs...) + args = append(args, + "-v", fmt.Sprintf("%s:/etc/hyperledger/fabric/msp", mspConfigPath), + "-v", fmt.Sprintf("%s:/var/hyperledger/production", dataConfigPath), + "-p", fmt.Sprintf("%s:7051", strings.Split(p.opts.ListenAddress, ":")[1]), + "-p", fmt.Sprintf("%s:7052", strings.Split(p.opts.ChaincodeAddress, ":")[1]), + "-p", fmt.Sprintf("%s:7053", strings.Split(p.opts.EventsAddress, ":")[1]), + "-p", fmt.Sprintf("%s:9443", strings.Split(p.opts.OperationsListenAddress, ":")[1]), + "hyperledger/fabric-peer:2.5.9", + "peer", + "node", + "start", + ) - const coreYamlTemplate = ` -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -# + cmd := exec.Command("docker", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr -############################################################################### -# -# Peer section -# -############################################################################### -peer: + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("failed to start docker container: %w", err) + } - # The peer id provides a name for this peer instance and is used when - # naming docker resources. - id: jdoe + return &StartDockerResponse{ + Mode: "docker", + ContainerName: containerName, + }, nil +} - # The networkId allows for logical separation of networks and is used when - # naming docker resources. - networkId: dev +// Stop stops the peer node +func (p *LocalPeer) Stop() error { + p.logger.Info("Stopping peer", "opts", p.opts) - # The Address at local network interface this Peer will listen on. - # By default, it will listen on all network interfaces - listenAddress: 0.0.0.0:7051 + switch p.mode { + case "service": + platform := runtime.GOOS + switch platform { + case "linux": + return p.stopSystemdService() + case "darwin": + return p.stopLaunchdService() + default: + return fmt.Errorf("unsupported platform for service mode: %s", platform) + } + case "docker": + return p.stopDocker() + default: + return fmt.Errorf("invalid mode: %s", p.mode) + } +} + +// stopDocker stops the peer docker container +func (p *LocalPeer) stopDocker() error { + containerName, err := p.getContainerName() + if err != nil { + return fmt.Errorf("failed to get container name: %w", err) + } + + // Stop the container + stopCmd := exec.Command("docker", "stop", containerName) + if err := stopCmd.Run(); err != nil { + return fmt.Errorf("failed to stop docker container: %w", err) + } + + // Remove the container + rmCmd := exec.Command("docker", "rm", "-f", containerName) + if err := rmCmd.Run(); err != nil { + p.logger.Warn("Failed to remove docker container", "error", err) + // Don't return error as the container might not exist + } - # The endpoint this peer uses to listen for inbound chaincode connections. - # If this is commented-out, the listen address is selected to be - # the peer's address (see below) with port 7052 - # chaincodeListenAddress: 0.0.0.0:7052 + return nil +} - # The endpoint the chaincode for this peer uses to connect to the peer. - # If this is not specified, the chaincodeListenAddress address is selected. - # And if chaincodeListenAddress is not specified, address is selected from - # peer address (see below). If specified peer address is invalid then it - # will fallback to the auto detected IP (local IP) regardless of the peer - # addressAutoDetect value. - # chaincodeAddress: 0.0.0.0:7052 +// stopSystemdService stops the systemd service +func (p *LocalPeer) stopSystemdService() error { + serviceName := p.getServiceName() - # When used as peer config, this represents the endpoint to other peers - # in the same organization. For peers in other organization, see - # gossip.externalEndpoint for more info. - # When used as CLI config, this means the peer's endpoint to interact with - address: 0.0.0.0:7051 + // Stop the service + if err := p.execSystemctl("stop", serviceName); err != nil { + return fmt.Errorf("failed to stop systemd service: %w", err) + } - # Whether the Peer should programmatically determine its address - # This case is useful for docker containers. - # When set to true, will override peer address. - addressAutoDetect: false + // Disable the service + if err := p.execSystemctl("disable", serviceName); err != nil { + p.logger.Warn("Failed to disable systemd service", "error", err) + // Don't return error as this is not critical + } - # Keepalive settings for peer server and clients - keepalive: - # Interval is the duration after which if the server does not see - # any activity from the client it pings the client to see if it's alive - interval: 7200s - # Timeout is the duration the server waits for a response - # from the client after sending a ping before closing the connection - timeout: 20s - # MinInterval is the minimum permitted time between client pings. - # If clients send pings more frequently, the peer server will - # disconnect them - minInterval: 60s - # Client keepalive settings for communicating with other peer nodes - client: - # Interval is the time between pings to peer nodes. This must - # greater than or equal to the minInterval specified by peer - # nodes - interval: 60s - # Timeout is the duration the client waits for a response from - # peer nodes before closing the connection - timeout: 20s - # DeliveryClient keepalive settings for communication with ordering - # nodes. - deliveryClient: - # Interval is the time between pings to ordering nodes. This must - # greater than or equal to the minInterval specified by ordering - # nodes. - interval: 60s - # Timeout is the duration the client waits for a response from - # ordering nodes before closing the connection - timeout: 20s + // Remove the service file + if err := os.Remove(p.getServiceFilePath()); err != nil { + if !os.IsNotExist(err) { + p.logger.Warn("Failed to remove service file", "error", err) + // Don't return error as this is not critical + } + } + // Reload systemd daemon + if err := p.execSystemctl("daemon-reload"); err != nil { + p.logger.Warn("Failed to reload systemd daemon", "error", err) + // Don't return error as this is not critical + } - # Gossip related configuration - gossip: - # Bootstrap set to initialize gossip with. - # This is a list of other peers that this peer reaches out to at startup. - # Important: The endpoints here have to be endpoints of peers in the same - # organization, because the peer would refuse connecting to these endpoints - # unless they are in the same organization as the peer. - bootstrap: 127.0.0.1:7051 + return nil +} - # NOTE: orgLeader and useLeaderElection parameters are mutual exclusive. - # Setting both to true would result in the termination of the peer - # since this is undefined state. If the peers are configured with - # useLeaderElection=false, make sure there is at least 1 peer in the - # organization that its orgLeader is set to true. +// stopLaunchdService stops the launchd service +func (p *LocalPeer) stopLaunchdService() error { + // Stop the service + stopCmd := exec.Command("launchctl", "stop", p.getLaunchdServiceName()) + if err := stopCmd.Run(); err != nil { + p.logger.Warn("Failed to stop launchd service", "error", err) + // Continue anyway as we want to make sure it's unloaded + } - # Defines whenever peer will initialize dynamic algorithm for - # "leader" selection, where leader is the peer to establish - # connection with ordering service and use delivery protocol - # to pull ledger blocks from ordering service. - useLeaderElection: false - # Statically defines peer to be an organization "leader", - # where this means that current peer will maintain connection - # with ordering service and disseminate block across peers in - # its own organization. Multiple peers or all peers in an organization - # may be configured as org leaders, so that they all pull - # blocks directly from ordering service. - orgLeader: true + // Unload the service + unloadCmd := exec.Command("launchctl", "unload", p.getLaunchdPlistPath()) + if err := unloadCmd.Run(); err != nil { + return fmt.Errorf("failed to unload launchd service: %w", err) + } - # Interval for membershipTracker polling - membershipTrackerInterval: 5s + return nil +} - # Overrides the endpoint that the peer publishes to peers - # in its organization. For peers in foreign organizations - # see 'externalEndpoint' - endpoint: - # Maximum count of blocks stored in memory - maxBlockCountToStore: 10 - # Max time between consecutive message pushes(unit: millisecond) - maxPropagationBurstLatency: 10ms - # Max number of messages stored until a push is triggered to remote peers - maxPropagationBurstSize: 10 - # Number of times a message is pushed to remote peers - propagateIterations: 1 - # Number of peers selected to push messages to - propagatePeerNum: 3 - # Determines frequency of pull phases(unit: second) - # Must be greater than digestWaitTime + responseWaitTime - pullInterval: 4s - # Number of peers to pull from - pullPeerNum: 3 - # Determines frequency of pulling state info messages from peers(unit: second) - requestStateInfoInterval: 4s - # Determines frequency of pushing state info messages to peers(unit: second) - publishStateInfoInterval: 4s - # Maximum time a stateInfo message is kept until expired - stateInfoRetentionInterval: - # Time from startup certificates are included in Alive messages(unit: second) - publishCertPeriod: 10s - # Should we skip verifying block messages or not (currently not in use) - skipBlockVerification: false - # Dial timeout(unit: second) - dialTimeout: 3s - # Connection timeout(unit: second) - connTimeout: 2s - # Buffer size of received messages - recvBuffSize: 20 - # Buffer size of sending messages - sendBuffSize: 200 - # Time to wait before pull engine processes incoming digests (unit: second) - # Should be slightly smaller than requestWaitTime - digestWaitTime: 1s - # Time to wait before pull engine removes incoming nonce (unit: milliseconds) - # Should be slightly bigger than digestWaitTime - requestWaitTime: 1500ms - # Time to wait before pull engine ends pull (unit: second) - responseWaitTime: 2s - # Alive check interval(unit: second) - aliveTimeInterval: 5s - # Alive expiration timeout(unit: second) - aliveExpirationTimeout: 25s - # Reconnect interval(unit: second) - reconnectInterval: 25s - # Max number of attempts to connect to a peer - maxConnectionAttempts: 120 - # Message expiration factor for alive messages - msgExpirationFactor: 20 - # This is an endpoint that is published to peers outside of the organization. - # If this isn't set, the peer will not be known to other organizations. - externalEndpoint: - # Leader election service configuration - election: - # Longest time peer waits for stable membership during leader election startup (unit: second) - startupGracePeriod: 15s - # Interval gossip membership samples to check its stability (unit: second) - membershipSampleInterval: 1s - # Time passes since last declaration message before peer decides to perform leader election (unit: second) - leaderAliveThreshold: 10s - # Time between peer sends propose message and declares itself as a leader (sends declaration message) (unit: second) - leaderElectionDuration: 5s +// execSystemctl executes a systemctl command +func (p *LocalPeer) execSystemctl(command string, args ...string) error { + cmdArgs := append([]string{command}, args...) - pvtData: - # pullRetryThreshold determines the maximum duration of time private data corresponding for a given block - # would be attempted to be pulled from peers until the block would be committed without the private data - pullRetryThreshold: 60s - # As private data enters the transient store, it is associated with the peer's ledger's height at that time. - # transientstoreMaxBlockRetention defines the maximum difference between the current ledger's height upon commit, - # and the private data residing inside the transient store that is guaranteed not to be purged. - # Private data is purged from the transient store when blocks with sequences that are multiples - # of transientstoreMaxBlockRetention are committed. - transientstoreMaxBlockRetention: 1000 - # pushAckTimeout is the maximum time to wait for an acknowledgement from each peer - # at private data push at endorsement time. - pushAckTimeout: 3s - # Block to live pulling margin, used as a buffer - # to prevent peer from trying to pull private data - # from peers that is soon to be purged in next N blocks. - # This helps a newly joined peer catch up to current - # blockchain height quicker. - btlPullMargin: 10 - # the process of reconciliation is done in an endless loop, while in each iteration reconciler tries to - # pull from the other peers the most recent missing blocks with a maximum batch size limitation. - # reconcileBatchSize determines the maximum batch size of missing private data that will be reconciled in a - # single iteration. - reconcileBatchSize: 10 - # reconcileSleepInterval determines the time reconciler sleeps from end of an iteration until the beginning - # of the next reconciliation iteration. - reconcileSleepInterval: 1m - # reconciliationEnabled is a flag that indicates whether private data reconciliation is enable or not. - reconciliationEnabled: true - # skipPullingInvalidTransactionsDuringCommit is a flag that indicates whether pulling of invalid - # transaction's private data from other peers need to be skipped during the commit time and pulled - # only through reconciler. - skipPullingInvalidTransactionsDuringCommit: false - # implicitCollectionDisseminationPolicy specifies the dissemination policy for the peer's own implicit collection. - # When a peer endorses a proposal that writes to its own implicit collection, below values override the default values - # for disseminating private data. - # Note that it is applicable to all channels the peer has joined. The implication is that requiredPeerCount has to - # be smaller than the number of peers in a channel that has the lowest numbers of peers from the organization. - implicitCollectionDisseminationPolicy: - # requiredPeerCount defines the minimum number of eligible peers to which the peer must successfully - # disseminate private data for its own implicit collection during endorsement. Default value is 0. - requiredPeerCount: 0 - # maxPeerCount defines the maximum number of eligible peers to which the peer will attempt to - # disseminate private data for its own implicit collection during endorsement. Default value is 1. - maxPeerCount: 1 + // Check if sudo is available + sudoPath, err := exec.LookPath("sudo") + if err == nil { + // sudo is available, use it + cmdArgs = append([]string{"systemctl"}, cmdArgs...) + cmd := exec.Command(sudoPath, cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } + } else { + // sudo is not available, run directly + cmd := exec.Command("systemctl", cmdArgs...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemctl %s failed: %w", command, err) + } + } + + return nil +} - # Gossip state transfer related configuration - state: - # indicates whenever state transfer is enabled or not - # default value is true, i.e. state transfer is active - # and takes care to sync up missing blocks allowing - # lagging peer to catch up to speed with rest network - enabled: false - # checkInterval interval to check whether peer is lagging behind enough to - # request blocks via state transfer from another peer. - checkInterval: 10s - # responseTimeout amount of time to wait for state transfer response from - # other peers - responseTimeout: 3s - # batchSize the number of blocks to request via state transfer from another peer - batchSize: 10 - # blockBufferSize reflects the size of the re-ordering buffer - # which captures blocks and takes care to deliver them in order - # down to the ledger layer. The actual buffer size is bounded between - # 0 and 2*blockBufferSize, each channel maintains its own buffer - blockBufferSize: 20 - # maxRetries maximum number of re-tries to ask - # for single state transfer request - maxRetries: 3 +// RenewCertificates renews the peer's TLS and signing certificates +func (p *LocalPeer) RenewCertificates(peerDeploymentConfig *types.FabricPeerDeploymentConfig) error { - # TLS Settings - tls: - # Require server-side TLS - enabled: false - # Require client certificates / mutual TLS. - # Note that clients that are not configured to use a certificate will - # fail to connect to the peer. - clientAuthRequired: false - # X.509 certificate used for TLS server - cert: - file: tls/server.crt - # Private key used for TLS server (and client if clientAuthEnabled - # is set to true - key: - file: tls/server.key - # Trusted root certificate chain for tls.cert - rootcert: - file: tls/ca.crt - # Set of root certificate authorities used to verify client certificates - clientRootCAs: - files: - - tls/ca.crt - # Private key used for TLS when making client connections. If - # not set, peer.tls.key.file will be used instead - clientKey: - file: - # X.509 certificate used for TLS when making client connections. - # If not set, peer.tls.cert.file will be used instead - clientCert: - file: + ctx := context.Background() + p.logger.Info("Starting certificate renewal for peer", "peerID", p.opts.ID) - # Authentication contains configuration parameters related to authenticating - # client messages - authentication: - # the acceptable difference between the current server time and the - # client's time as specified in a client request message - timewindow: 15m + // Get organization details + org, err := p.orgService.GetOrganization(ctx, p.organizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } - # Path on the file system where peer will store data (eg ledger). This - # location must be access control protected to prevent unintended - # modification that might corrupt the peer operations. - fileSystemPath: {{.DataPath}} + if peerDeploymentConfig.SignKeyID == 0 || peerDeploymentConfig.TLSKeyID == 0 { + return fmt.Errorf("peer node does not have required key IDs") + } - # BCCSP (Blockchain crypto provider): Select which crypto implementation or - # library to use - BCCSP: - Default: SW - # Settings for the SW crypto provider (i.e. when DEFAULT: SW) - SW: - # TODO: The default Hash and Security level needs refactoring to be - # fully configurable. Changing these defaults requires coordination - # SHA2 is hardcoded in several places, not only BCCSP - Hash: SHA2 - Security: 256 - # Location of Key Store - FileKeyStore: - # If "", defaults to 'mspConfigPath'/keystore - KeyStore: - # Settings for the PKCS#11 crypto provider (i.e. when DEFAULT: PKCS11) - PKCS11: - # Location of the PKCS11 module library - Library: - # Token Label - Label: - # User PIN - Pin: - Hash: - Security: + // Get the CA certificates + signCAKey, err := p.keyService.GetKey(ctx, int(org.SignKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get sign CA key: %w", err) + } - # Path on the file system where peer will find MSP local configurations - mspConfigPath: msp + tlsCAKey, err := p.keyService.GetKey(ctx, int(org.TlsRootKeyID.Int64)) + if err != nil { + return fmt.Errorf("failed to get TLS CA key: %w", err) + } + // In case the sign key is not signed by the CA, set the signing key ID to the CA key ID + signKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + if signKeyDB.SigningKeyID == nil || *signKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.SignKeyID), int(signCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for sign key: %w", err) + } + } - # Identifier of the local MSP - # ----!!!!IMPORTANT!!!-!!!IMPORTANT!!!-!!!IMPORTANT!!!!---- - # Deployers need to change the value of the localMspId string. - # In particular, the name of the local MSP ID of a peer needs - # to match the name of one of the MSPs in each of the channel - # that this peer is a member of. Otherwise this peer's messages - # will not be identified as valid by other nodes. - localMspId: SampleOrg + tlsKeyDB, err := p.keyService.GetKey(ctx, int(peerDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } - # CLI common client config options - client: - # connection timeout - connTimeout: 3s + if tlsKeyDB.SigningKeyID == nil || *tlsKeyDB.SigningKeyID == 0 { + // Set the signing key ID to the organization's sign CA key ID + err = p.keyService.SetSigningKeyIDForKey(ctx, int(peerDeploymentConfig.TLSKeyID), int(tlsCAKey.ID)) + if err != nil { + return fmt.Errorf("failed to set signing key ID for TLS key: %w", err) + } + } + // Renew signing certificate + validFor := kmodels.Duration(time.Hour * 24 * 365) // 1 year validity + _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.SignKeyID), kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: []string{p.opts.ID}, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew signing certificate: %w", err) + } - # Delivery service related config - deliveryclient: - # It sets the total time the delivery service may spend in reconnection - # attempts until its retry logic gives up and returns an error - reconnectTotalTimeThreshold: 3600s + // Renew TLS certificate + domainNames := p.opts.DomainNames + var ipAddresses []net.IP + var domains []string - # It sets the delivery service <-> ordering service node connection timeout - connTimeout: 3s + // Ensure localhost and 127.0.0.1 are included + hasLocalhost := false + hasLoopback := false + for _, domain := range domainNames { + if domain == "localhost" { + hasLocalhost = true + domains = append(domains, domain) + continue + } + if domain == "127.0.0.1" { + hasLoopback = true + ipAddresses = append(ipAddresses, net.ParseIP(domain)) + continue + } + if ip := net.ParseIP(domain); ip != nil { + ipAddresses = append(ipAddresses, ip) + } else { + domains = append(domains, domain) + } + } + if !hasLocalhost { + domains = append(domains, "localhost") + } + if !hasLoopback { + ipAddresses = append(ipAddresses, net.ParseIP("127.0.0.1")) + } + + _, err = p.keyService.RenewCertificate(ctx, int(peerDeploymentConfig.TLSKeyID), kmodels.CertificateRequest{ + CommonName: p.opts.ID, + Organization: []string{org.MspID}, + OrganizationalUnit: []string{"peer"}, + DNSNames: domains, + IPAddresses: ipAddresses, + IsCA: false, + ValidFor: validFor, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }) + if err != nil { + return fmt.Errorf("failed to renew TLS certificate: %w", err) + } + + // Get the private keys + signKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.SignKeyID)) + if err != nil { + return fmt.Errorf("failed to get sign private key: %w", err) + } + + tlsKey, err := p.keyService.GetDecryptedPrivateKey(int(peerDeploymentConfig.TLSKeyID)) + if err != nil { + return fmt.Errorf("failed to get TLS private key: %w", err) + } + + // Update the certificates in the MSP directory + peerPath := p.getPeerPath() + mspConfigPath := filepath.Join(peerPath, "config") + + err = p.writeCertificatesAndKeys( + mspConfigPath, + tlsKeyDB, + signKeyDB, + tlsKey, + signKey, + signCAKey, + tlsCAKey, + ) + if err != nil { + return fmt.Errorf("failed to write renewed certificates: %w", err) + } + + // Restart the peer + _, err = p.Start() + if err != nil { + return fmt.Errorf("failed to restart peer after certificate renewal: %w", err) + } + + p.logger.Info("Successfully renewed peer certificates", "peerID", p.opts.ID) + p.logger.Info("Restarting peer after certificate renewal") + // Stop the peer before renewing certificates + if err := p.Stop(); err != nil { + return fmt.Errorf("failed to stop peer before certificate renewal: %w", err) + } + p.logger.Info("Successfully stopped peer before certificate renewal") + p.logger.Info("Starting peer after certificate renewal") + _, err = p.Start() + if err != nil { + return fmt.Errorf("failed to start peer after certificate renewal: %w", err) + } + p.logger.Info("Successfully started peer after certificate renewal") + return nil +} - # It sets the delivery service maximal delay between consecutive retries - reConnectBackoffThreshold: 3600s +type NetworkConfigResponse struct { + NetworkConfig string +} +type Org struct { + MSPID string + CertAuths []string + Peers []string + Orderers []string +} +type Peer struct { + Name string + URL string + TLSCACert string +} +type CA struct { + Name string + URL string + TLSCert string + EnrollID string + EnrollSecret string +} - # A list of orderer endpoint addresses which should be overridden - # when found in channel configurations. - addressOverrides: - # - from: - # to: - # caCertsFile: - # - from: - # to: - # caCertsFile: +type Orderer struct { + URL string + Name string + TLSCACert string +} - # Type for the local MSP - by default it's of type bccsp - localMspType: bccsp +const tmplGoConfig = ` +name: hlf-network +version: 1.0.0 +client: + organization: "{{ .Organization }}" +{{- if not .Organizations }} +organizations: {} +{{- else }} +organizations: + {{ range $org := .Organizations }} + {{ $org.MSPID }}: + mspid: {{ $org.MSPID }} + cryptoPath: /tmp/cryptopath + users: {} +{{- if not $org.CertAuths }} + certificateAuthorities: [] +{{- else }} + certificateAuthorities: + {{- range $ca := $org.CertAuths }} + - {{ $ca.Name }} + {{- end }} +{{- end }} +{{- if not $org.Peers }} + peers: [] +{{- else }} + peers: + {{- range $peer := $org.Peers }} + - {{ $peer }} + {{- end }} +{{- end }} +{{- if not $org.Orderers }} + orderers: [] +{{- else }} + orderers: + {{- range $orderer := $org.Orderers }} + - {{ $orderer }} + {{- end }} - # Used with Go profiling tools only in none production environment. In - # production, it should be disabled (eg enabled: false) - profile: - enabled: false - listenAddress: 0.0.0.0:6060 + {{- end }} +{{- end }} +{{- end }} - # Handlers defines custom handlers that can filter and mutate - # objects passing within the peer, such as: - # Auth filter - reject or forward proposals from clients - # Decorators - append or mutate the chaincode input passed to the chaincode - # Endorsers - Custom signing over proposal response payload and its mutation - # Valid handler definition contains: - # - A name which is a factory method name defined in - # core/handlers/library/library.go for statically compiled handlers - # - library path to shared object binary for pluggable filters - # Auth filters and decorators are chained and executed in the order that - # they are defined. For example: - # authFilters: - # - - # name: FilterOne - # library: /opt/lib/filter.so - # - - # name: FilterTwo - # decorators: - # - - # name: DecoratorOne - # - - # name: DecoratorTwo - # library: /opt/lib/decorator.so - # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. - # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality - # as the default ESCC. - # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar - # to auth filters and decorators. - # endorsers: - # escc: - # name: DefaultESCC - # library: /etc/hyperledger/fabric/plugin/escc.so - handlers: - authFilters: - - - name: DefaultAuth - - - name: ExpirationCheck # This filter checks identity x509 certificate expiration - decorators: - - - name: DefaultDecorator - endorsers: - escc: - name: DefaultEndorsement - library: - validators: - vscc: - name: DefaultValidation - library: +{{- if not .Orderers }} +{{- else }} +orderers: +{{- range $orderer := .Orderers }} + {{$orderer.Name}}: + url: {{ $orderer.URL }} + grpcOptions: + allow-insecure: false + tlsCACerts: + pem: | +{{ $orderer.TLSCACert | indent 8 }} +{{- end }} +{{- end }} - # library: /etc/hyperledger/fabric/plugin/escc.so - # Number of goroutines that will execute transaction validation in parallel. - # By default, the peer chooses the number of CPUs on the machine. Set this - # variable to override that choice. - # NOTE: overriding this value might negatively influence the performance of - # the peer so please change this value only if you know what you're doing - validatorPoolSize: +{{- if not .Peers }} +{{- else }} +peers: + {{- range $peer := .Peers }} + {{$peer.Name}}: + url: {{ $peer.URL }} + tlsCACerts: + pem: | +{{ $peer.TLSCACert | indent 8 }} +{{- end }} +{{- end }} - # The discovery service is used by clients to query information about peers, - # such as - which peers have joined a certain channel, what is the latest - # channel config, and most importantly - given a chaincode and a channel, - # what possible sets of peers satisfy the endorsement policy. - discovery: - enabled: true - # Whether the authentication cache is enabled or not. - authCacheEnabled: true - # The maximum size of the cache, after which a purge takes place - authCacheMaxSize: 1000 - # The proportion (0 to 1) of entries that remain in the cache after the cache is purged due to overpopulation - authCachePurgeRetentionRatio: 0.75 - # Whether to allow non-admins to perform non channel scoped queries. - # When this is false, it means that only peer admins can perform non channel scoped queries. - orgMembersAllowedAccess: false +{{- if not .CertAuths }} +{{- else }} +certificateAuthorities: +{{- range $ca := .CertAuths }} + {{ $ca.Name }}: + url: https://{{ $ca.URL }} +{{if $ca.EnrollID }} + registrar: + enrollId: {{ $ca.EnrollID }} + enrollSecret: "{{ $ca.EnrollSecret }}" +{{ end }} + caName: {{ $ca.CAName }} + tlsCACerts: + pem: + - | +{{ $ca.TLSCert | indent 12 }} - # Limits is used to configure some internal resource limits. - limits: - # Concurrency limits the number of concurrently running requests to a service on each peer. - # Currently this option is only applied to endorser service and deliver service. - # When the property is missing or the value is 0, the concurrency limit is disabled for the service. - concurrency: - # endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation, - # including both user chaincodes and system chaincodes. - endorserService: 2500 - # deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events. - deliverService: 2500 +{{- end }} +{{- end }} -############################################################################### -# -# VM section -# -############################################################################### -vm: +channels: + _default: +{{- if not .Orderers }} + orderers: [] +{{- else }} + orderers: +{{- range $orderer := .Orderers }} + - {{$orderer.Name}} +{{- end }} +{{- end }} +{{- if not .Peers }} + peers: {} +{{- else }} + peers: +{{- range $peer := .Peers }} + {{$peer.Name}}: + discover: true + endorsingPeer: true + chaincodeQuery: true + ledgerQuery: true + eventSource: true +{{- end }} +{{- end }} - # Endpoint of the vm management system. For docker can be one of the following in general - # unix:///var/run/docker.sock - # http://localhost:2375 - # https://localhost:2376 - endpoint: "" +` - # settings for docker vms - docker: - tls: - enabled: false - ca: - file: docker/ca.crt - cert: - file: docker/tls.crt - key: - file: docker/tls.key +func (p *LocalPeer) generateNetworkConfigForPeer( + peerUrl string, peerMspID string, peerTlsCACert string, ordererUrl string, ordererTlsCACert string) (*NetworkConfigResponse, error) { - # Enables/disables the standard out/err from chaincode containers for - # debugging purposes - attachStdout: false + tmpl, err := template.New("networkConfig").Funcs(sprig.HermeticTxtFuncMap()).Parse(tmplGoConfig) + if err != nil { + return nil, err + } + var buf bytes.Buffer + orgs := []*Org{} + var peers []*Peer + var certAuths []*CA + var ordererNodes []*Orderer - # Parameters on creating docker container. - # Container may be efficiently created using ipam & dns-server for cluster - # NetworkMode - sets the networking mode for the container. Supported - # Dns - a list of DNS servers for the container to use. - # Docker Host Config are not supported and will not be used if set. - # LogConfig - sets the logging driver (Type) and related options - # (Config) for Docker. For more info, - # https://docs.docker.com/engine/admin/logging/overview/ - # Note: Set LogConfig using Environment Variables is not supported. - hostConfig: - NetworkMode: host - Dns: - # - 192.168.0.1 - LogConfig: - Type: json-file - Config: - max-size: "50m" - max-file: "5" - Memory: 2147483648 + org := &Org{ + MSPID: peerMspID, + CertAuths: []string{}, + Peers: []string{}, + Orderers: []string{}, + } + orgs = append(orgs, org) + if peerTlsCACert != "" { + peer := &Peer{ + Name: "peer0", + URL: peerUrl, + TLSCACert: peerTlsCACert, + } + org.Peers = append(org.Peers, "peer0") + peers = append(peers, peer) + } + if ordererTlsCACert != "" && ordererUrl != "" { + orderer := &Orderer{ + URL: ordererUrl, + Name: "orderer0", + TLSCACert: ordererTlsCACert, + } + ordererNodes = append(ordererNodes, orderer) + } + err = tmpl.Execute(&buf, map[string]interface{}{ + "Peers": peers, + "Orderers": ordererNodes, + "Organizations": orgs, + "CertAuths": certAuths, + "Organization": peerMspID, + "Internal": false, + }) + if err != nil { + return nil, err + } + p.logger.Debugf("Network config: %s", buf.String()) + return &NetworkConfigResponse{ + NetworkConfig: buf.String(), + }, nil +} -############################################################################### -# -# Chaincode section -# -############################################################################### -chaincode: +// JoinChannel joins the peer to a channel +func (p *LocalPeer) JoinChannel(genesisBlock []byte) error { + p.logger.Info("Joining peer to channel", "peer", p.opts.ID) + var genesisBlockProto cb.Block + err := proto.Unmarshal(genesisBlock, &genesisBlockProto) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis block: %w", err) + } + ctx := context.Background() + tlsCACert, err := p.GetTLSRootCACert(ctx) + if err != nil { + return fmt.Errorf("failed to get TLS root CA cert: %w", err) + } + peerConn, err := p.CreatePeerConnection(ctx, p.opts.ExternalEndpoint, tlsCACert) + if err != nil { + return fmt.Errorf("failed to create peer connection: %w", err) + } + defer peerConn.Close() - # The id is used by the Chaincode stub to register the executing Chaincode - # ID with the Peer and is generally supplied through ENV variables - id: - path: - name: + adminIdentity, _, err := p.GetAdminIdentity(ctx) + if err != nil { + return fmt.Errorf("failed to get admin identity: %w", err) + } - # Generic builder environment, suitable for most chaincode types - builder: $(DOCKER_NS)/fabric-ccenv:$(TWO_DIGIT_VERSION) + err = channel.JoinChannel(ctx, peerConn, adminIdentity, &genesisBlockProto) + if err != nil { + return fmt.Errorf("failed to join channel: %w", err) + } - pull: false + return nil - golang: - # golang will never need more than baseos - runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION) +} - # whether or not golang chaincode should be linked dynamically - dynamicLink: false +// writeCertificatesAndKeys writes the certificates and keys to the MSP directory structure +func (p *LocalPeer) writeCertificatesAndKeys( + mspConfigPath string, + tlsCert *kmodels.KeyResponse, + signCert *kmodels.KeyResponse, + tlsKey string, + signKey string, + signCACert *kmodels.KeyResponse, + tlsCACert *kmodels.KeyResponse, +) error { + // Write TLS certificates and keys + if err := os.WriteFile(filepath.Join(mspConfigPath, "tls.crt"), []byte(*tlsCert.Certificate), 0644); err != nil { + return fmt.Errorf("failed to write TLS certificate: %w", err) + } + if err := os.WriteFile(filepath.Join(mspConfigPath, "tls.key"), []byte(tlsKey), 0600); err != nil { + return fmt.Errorf("failed to write TLS key: %w", err) + } - java: - # This is an image based on java:openjdk-8 with addition compiler - # tools added for java shim layer packaging. - # This image is packed with shim layer libraries that are necessary - # for Java chaincode runtime. - runtime: $(DOCKER_NS)/fabric-javaenv:$(TWO_DIGIT_VERSION) + // Create and write to signcerts directory + signcertsPath := filepath.Join(mspConfigPath, "signcerts") + if err := os.MkdirAll(signcertsPath, 0755); err != nil { + return fmt.Errorf("failed to create signcerts directory: %w", err) + } + if err := os.WriteFile(filepath.Join(signcertsPath, "cert.pem"), []byte(*signCert.Certificate), 0644); err != nil { + return fmt.Errorf("failed to write signing certificate: %w", err) + } - node: - # This is an image based on node:$(NODE_VER)-alpine - runtime: $(DOCKER_NS)/fabric-nodeenv:$(TWO_DIGIT_VERSION) + // Write root CA certificate + if err := os.WriteFile(filepath.Join(mspConfigPath, "cacert.pem"), []byte(*signCACert.Certificate), 0644); err != nil { + return fmt.Errorf("failed to write CA certificate: %w", err) + } - # List of directories to treat as external builders and launchers for - # chaincode. The external builder detection processing will iterate over the - # builders in the order specified below. - externalBuilders: - - name: ccaas_builder - path: {{.ExternalBuilderPath}} - # The maximum duration to wait for the chaincode build and install process - # to complete. - installTimeout: 8m0s + // Create and write to cacerts directory + cacertsPath := filepath.Join(mspConfigPath, "cacerts") + if err := os.MkdirAll(cacertsPath, 0755); err != nil { + return fmt.Errorf("failed to create cacerts directory: %w", err) + } + if err := os.WriteFile(filepath.Join(cacertsPath, "cacert.pem"), []byte(*signCACert.Certificate), 0644); err != nil { + return fmt.Errorf("failed to write CA certificate to cacerts: %w", err) + } - # Timeout duration for starting up a container and waiting for Register - # to come through. - startuptimeout: 5m0s + // Create and write to tlscacerts directory + tlscacertsPath := filepath.Join(mspConfigPath, "tlscacerts") + if err := os.MkdirAll(tlscacertsPath, 0755); err != nil { + return fmt.Errorf("failed to create tlscacerts directory: %w", err) + } + if err := os.WriteFile(filepath.Join(tlscacertsPath, "cacert.pem"), []byte(*tlsCACert.Certificate), 0644); err != nil { + return fmt.Errorf("failed to write TLS CA certificate: %w", err) + } - # Timeout duration for Invoke and Init calls to prevent runaway. - # This timeout is used by all chaincodes in all the channels, including - # system chaincodes. - # Note that during Invoke, if the image is not available (e.g. being - # cleaned up when in development environment), the peer will automatically - # build the image, which might take more time. In production environment, - # the chaincode image is unlikely to be deleted, so the timeout could be - # reduced accordingly. - executetimeout: 30s + // Create and write to keystore directory + keystorePath := filepath.Join(mspConfigPath, "keystore") + if err := os.MkdirAll(keystorePath, 0755); err != nil { + return fmt.Errorf("failed to create keystore directory: %w", err) + } + if err := os.WriteFile(filepath.Join(keystorePath, "key.pem"), []byte(signKey), 0600); err != nil { + return fmt.Errorf("failed to write signing key: %w", err) + } - # There are 2 modes: "dev" and "net". - # In dev mode, user runs the chaincode after starting peer from - # command line on local machine. - # In net mode, peer will run chaincode in a docker container. - mode: net + return nil +} - # keepalive in seconds. In situations where the communication goes through a - # proxy that does not support keep-alive, this parameter will maintain connection - # between peer and chaincode. - # A value <= 0 turns keepalive off - keepalive: 0 +// setupExternalBuilders creates and configures the external builders for chaincode +func (p *LocalPeer) setupExternalBuilders(mspConfigPath string) error { + // Create external builder directory structure + rootExternalBuilderPath := filepath.Join(mspConfigPath, "ccaas") + binExternalBuilderPath := filepath.Join(rootExternalBuilderPath, "bin") + if err := os.MkdirAll(binExternalBuilderPath, 0755); err != nil { + return fmt.Errorf("failed to create external builder directory: %w", err) + } - # enabled system chaincodes - system: - _lifecycle: enable - cscc: enable - lscc: enable - escc: enable - vscc: enable - qscc: enable + // Create build script + buildScript := `#!/bin/bash - # Logging section for the chaincode container - logging: - # Default level for all loggers within the chaincode container - level: info - # Override default level for the 'shim' logger - shim: warning - # Format for the chaincode container logs - format: '%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}' +SOURCE=$1 +OUTPUT=$3 -############################################################################### -# -# Ledger section - ledger configuration encompasses both the blockchain -# and the state -# -############################################################################### -ledger: +#external chaincodes expect connection.json file in the chaincode package +if [ ! -f "$SOURCE/connection.json" ]; then + >&2 echo "$SOURCE/connection.json not found" + exit 1 +fi + +#simply copy the endpoint information to specified output location +cp $SOURCE/connection.json $OUTPUT/connection.json - blockchain: - snapshots: - rootDir: {{.DataPath}}/snapshots +if [ -d "$SOURCE/metadata" ]; then + cp -a $SOURCE/metadata $OUTPUT/metadata +fi - state: - # stateDatabase - options are "goleveldb", "CouchDB" - # goleveldb - default state database stored in goleveldb. - # CouchDB - store state database in CouchDB - stateDatabase: goleveldb - # Limit on the number of records to return per query - totalQueryLimit: 100000 - couchDBConfig: - # It is recommended to run CouchDB on the same server as the peer, and - # not map the CouchDB container port to a server port in docker-compose. - # Otherwise proper security must be provided on the connection between - # CouchDB client (on the peer) and server. - couchDBAddress: 127.0.0.1:5984 - # This username must have read and write authority on CouchDB - username: - # The password is recommended to pass as an environment variable - # during start up (eg CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD). - # If it is stored here, the file must be access control protected - # to prevent unintended users from discovering the password. - password: - # Number of retries for CouchDB errors - maxRetries: 3 - # Number of retries for CouchDB errors during peer startup. - # The delay between retries doubles for each attempt. - # Default of 10 retries results in 11 attempts over 2 minutes. - maxRetriesOnStartup: 10 - # CouchDB request timeout (unit: duration, e.g. 20s) - requestTimeout: 35s - # Limit on the number of records per each CouchDB query - # Note that chaincode queries are only bound by totalQueryLimit. - # Internally the chaincode may execute multiple CouchDB queries, - # each of size internalQueryLimit. - internalQueryLimit: 1000 - # Limit on the number of records per CouchDB bulk update batch - maxBatchUpdateSize: 1000 - # Warm indexes after every N blocks. - # This option warms any indexes that have been - # deployed to CouchDB after every N blocks. - # A value of 1 will warm indexes after every block commit, - # to ensure fast selector queries. - # Increasing the value may improve write efficiency of peer and CouchDB, - # but may degrade query response time. - warmIndexesAfterNBlocks: 1 - # Create the _global_changes system database - # This is optional. Creating the global changes database will require - # additional system resources to track changes and maintain the database - createGlobalChangesDB: false - # CacheSize denotes the maximum mega bytes (MB) to be allocated for the in-memory state - # cache. Note that CacheSize needs to be a multiple of 32 MB. If it is not a multiple - # of 32 MB, the peer would round the size to the next multiple of 32 MB. - # To disable the cache, 0 MB needs to be assigned to the cacheSize. - cacheSize: 64 +exit 0` - history: - # enableHistoryDatabase - options are true or false - # Indicates if the history of key updates should be stored. - # All history 'index' will be stored in goleveldb, regardless if using - # CouchDB or alternate database for the state. - enableHistoryDatabase: true + if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "build"), []byte(buildScript), 0755); err != nil { + return fmt.Errorf("failed to write build script: %w", err) + } - pvtdataStore: - # the maximum db batch size for converting - # the ineligible missing data entries to eligible missing data entries - collElgProcMaxDbBatchSize: 5000 - # the minimum duration (in milliseconds) between writing - # two consecutive db batches for converting the ineligible missing data entries to eligible missing data entries - collElgProcDbBatchesInterval: 1000 + // Create detect script + detectScript := `#!/bin/bash -############################################################################### -# -# Operations section -# -############################################################################### -operations: - # host and port for the operations server - listenAddress: 127.0.0.1:9443 +METADIR=$2 +# check if the "type" field is set to "external" +# crude way without jq which is not in the default fabric peer image +TYPE=$(tr -d '\n' < "$METADIR/metadata.json" | awk -F':' '{ for (i = 1; i < NF; i++){ if ($i~/type/) { print $(i+1); break }}}'| cut -d\" -f2) - # TLS configuration for the operations endpoint - tls: - # TLS enabled - enabled: false +if [ "$TYPE" = "ccaas" ]; then + exit 0 +fi - # path to PEM encoded server certificate for the operations server - cert: - file: +exit 1` - # path to PEM encoded server key for the operations server - key: - file: + if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "detect"), []byte(detectScript), 0755); err != nil { + return fmt.Errorf("failed to write detect script: %w", err) + } - # most operations service endpoints require client authentication when TLS - # is enabled. clientAuthRequired requires client certificate authentication - # at the TLS layer to access all resources. - clientAuthRequired: false + // Create release script + releaseScript := `#!/bin/bash - # paths to PEM encoded ca certificates to trust for client authentication - clientRootCAs: - files: [] +BLD="$1" +RELEASE="$2" -############################################################################### -# -# Metrics section -# -############################################################################### -metrics: - # metrics provider is one of statsd, prometheus, or disabled - provider: disabled +if [ -d "$BLD/metadata" ]; then + cp -a "$BLD/metadata/"* "$RELEASE/" +fi - # statsd configuration - statsd: - # network type: tcp or udp - network: udp +#external chaincodes expect artifacts to be placed under "$RELEASE"/chaincode/server +if [ -f $BLD/connection.json ]; then + mkdir -p "$RELEASE"/chaincode/server + cp $BLD/connection.json "$RELEASE"/chaincode/server - # statsd server address - address: 127.0.0.1:8125 + #if tls_required is true, copy TLS files (using above example, the fully qualified path for these fils would be "$RELEASE"/chaincode/server/tls) - # the interval at which locally cached counters and gauges are pushed - # to statsd; timings are pushed immediately - writeInterval: 10s + exit 0 +fi - # prefix is prepended to all emitted statsd metrics - prefix: +exit 1` + + if err := os.WriteFile(filepath.Join(binExternalBuilderPath, "release"), []byte(releaseScript), 0755); err != nil { + return fmt.Errorf("failed to write release script: %w", err) + } + return nil +} + +const configYamlContent = `NodeOUs: + Enable: true + ClientOUIdentifier: + Certificate: cacerts/cacert.pem + OrganizationalUnitIdentifier: client + PeerOUIdentifier: + Certificate: cacerts/cacert.pem + OrganizationalUnitIdentifier: peer + AdminOUIdentifier: + Certificate: cacerts/cacert.pem + OrganizationalUnitIdentifier: admin + OrdererOUIdentifier: + Certificate: cacerts/cacert.pem + OrganizationalUnitIdentifier: orderer ` +// writeConfigFiles writes the config.yaml and core.yaml files +func (p *LocalPeer) writeConfigFiles(mspConfigPath, dataConfigPath string) error { + // Write config.yaml + if err := os.WriteFile(filepath.Join(mspConfigPath, "config.yaml"), []byte(configYamlContent), 0644); err != nil { + return fmt.Errorf("failed to write config.yaml: %w", err) + } + convertedOverrides, err := p.convertAddressOverrides(mspConfigPath, p.opts.AddressOverrides) + if err != nil { + return fmt.Errorf("failed to convert address overrides: %w", err) + } + + // Define template data + data := struct { + PeerID string + ListenAddress string + ChaincodeAddress string + ExternalEndpoint string + DataPath string + MSPID string + ExternalBuilderPath string + OperationsListenAddress string + AddressOverrides []AddressOverridePath + }{ + PeerID: p.opts.ID, + ListenAddress: p.opts.ListenAddress, + ChaincodeAddress: p.opts.ChaincodeAddress, + ExternalEndpoint: p.opts.ExternalEndpoint, + DataPath: dataConfigPath, + MSPID: p.mspID, + ExternalBuilderPath: filepath.Join(mspConfigPath, "ccaas"), + OperationsListenAddress: p.opts.OperationsListenAddress, + AddressOverrides: convertedOverrides, + } + // Create template tmpl, err := template.New("core.yaml").Parse(coreYamlTemplate) if err != nil { @@ -2745,3 +2780,91 @@ func (p *LocalPeer) GetBlockByTxID(ctx context.Context, channelID string, txID s return block, nil } + +// SynchronizeConfig synchronizes the peer's configuration files and service +func (p *LocalPeer) SynchronizeConfig(deployConfig *types.FabricPeerDeploymentConfig) error { + slugifiedID := strings.ReplaceAll(strings.ToLower(p.opts.ID), " ", "-") + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + dirPath := filepath.Join(homeDir, ".chainlaunch/peers", slugifiedID) + mspConfigPath := filepath.Join(dirPath, "config") + dataConfigPath := filepath.Join(dirPath, "data") + // Write config.yaml + if err := os.WriteFile(filepath.Join(mspConfigPath, "config.yaml"), []byte(configYamlContent), 0644); err != nil { + return fmt.Errorf("failed to write config.yaml: %w", err) + } + convertedOverrides, err := p.convertAddressOverrides(mspConfigPath, deployConfig.AddressOverrides) + if err != nil { + return fmt.Errorf("failed to convert address overrides: %w", err) + } + + // Define template data + data := struct { + PeerID string + ListenAddress string + ChaincodeAddress string + ExternalEndpoint string + DataPath string + MSPID string + ExternalBuilderPath string + OperationsListenAddress string + AddressOverrides []AddressOverridePath + }{ + PeerID: p.opts.ID, + ListenAddress: deployConfig.ListenAddress, + ChaincodeAddress: deployConfig.ChaincodeAddress, + ExternalEndpoint: deployConfig.ExternalEndpoint, + DataPath: dataConfigPath, + MSPID: deployConfig.MSPID, + ExternalBuilderPath: filepath.Join(mspConfigPath, "ccaas"), + OperationsListenAddress: deployConfig.OperationsListenAddress, + AddressOverrides: convertedOverrides, + } + // Create template + tmpl, err := template.New("core.yaml").Parse(coreYamlTemplate) + if err != nil { + return fmt.Errorf("failed to parse core.yaml template: %w", err) + } + + // Execute template + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return fmt.Errorf("failed to execute core.yaml template: %w", err) + } + + // Write core.yaml + if err := os.WriteFile(filepath.Join(mspConfigPath, "core.yaml"), buf.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write core.yaml: %w", err) + } + + return nil +} + +// Add this new function +func (p *LocalPeer) convertAddressOverrides(mspConfigPath string, overrides []types.AddressOverride) ([]AddressOverridePath, error) { + // Create temporary directory for override certificates + tmpDir := filepath.Join(mspConfigPath, "orderer-overrides") + if err := os.MkdirAll(tmpDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create orderer overrides directory: %w", err) + } + + var convertedOverrides []AddressOverridePath + for i, override := range overrides { + // Write TLS CA certificate to file + certPath := filepath.Join(tmpDir, fmt.Sprintf("tlsca-%d.pem", i)) + if err := os.WriteFile(certPath, []byte(override.TLSCACert), 0644); err != nil { + return nil, fmt.Errorf("failed to write orderer TLS CA certificate: %w", err) + } + + // Add converted override + convertedOverrides = append(convertedOverrides, AddressOverridePath{ + From: override.From, + To: override.To, + TLSCAPath: certPath, + }) + } + + return convertedOverrides, nil +} diff --git a/pkg/nodes/peer/types.go b/pkg/nodes/peer/types.go index c7a8c14..0c18d32 100644 --- a/pkg/nodes/peer/types.go +++ b/pkg/nodes/peer/types.go @@ -1,16 +1,19 @@ package peer +import "github.com/chainlaunch/chainlaunch/pkg/nodes/types" + // StartPeerOpts represents the options for starting a peer type StartPeerOpts struct { - ID string `json:"id"` - ListenAddress string `json:"listenAddress"` - ChaincodeAddress string `json:"chaincodeAddress"` - EventsAddress string `json:"eventsAddress"` - OperationsListenAddress string `json:"operationsListenAddress"` - ExternalEndpoint string `json:"externalEndpoint"` - DomainNames []string `json:"domainNames"` - Env map[string]string `json:"env"` - Version string `json:"version"` // Fabric version to use + ID string `json:"id"` + ListenAddress string `json:"listenAddress"` + ChaincodeAddress string `json:"chaincodeAddress"` + EventsAddress string `json:"eventsAddress"` + OperationsListenAddress string `json:"operationsListenAddress"` + ExternalEndpoint string `json:"externalEndpoint"` + DomainNames []string `json:"domainNames"` + Env map[string]string `json:"env"` + Version string `json:"version"` // Fabric version to use + AddressOverrides []types.AddressOverride `json:"addressOverrides,omitempty"` } // PeerConfig represents the configuration for a peer node diff --git a/pkg/nodes/service/config.go b/pkg/nodes/service/config.go index b8b8967..47e1295 100644 --- a/pkg/nodes/service/config.go +++ b/pkg/nodes/service/config.go @@ -31,7 +31,7 @@ type PaginatedNodes struct { HasNextPage bool } -// NodeResponse represents a node with type-specific properties +// NodeResponse represents the response for node configuration type NodeResponse struct { ID int64 `json:"id"` Name string `json:"name"` @@ -67,6 +67,9 @@ type FabricPeerProperties struct { TLSCert string `json:"tlsCert,omitempty"` SignCACert string `json:"signCaCert,omitempty"` TLSCACert string `json:"tlsCaCert,omitempty"` + + AddressOverrides []types.AddressOverride `json:"addressOverrides,omitempty"` + Version string `json:"version"` } // FabricOrdererProperties represents the properties specific to a Fabric orderer node diff --git a/pkg/nodes/service/service.go b/pkg/nodes/service/service.go index e6bfad4..fcccae9 100644 --- a/pkg/nodes/service/service.go +++ b/pkg/nodes/service/service.go @@ -14,6 +14,9 @@ import ( "strings" "time" + "crypto/x509" + "encoding/pem" + "github.com/chainlaunch/chainlaunch/pkg/db" "github.com/chainlaunch/chainlaunch/pkg/errors" fabricservice "github.com/chainlaunch/chainlaunch/pkg/fabric/service" @@ -128,45 +131,39 @@ func (s *NodeService) validateAddress(address string) error { // validateFabricPeerAddresses validates all addresses used by a Fabric peer func (s *NodeService) validateFabricPeerAddresses(config *types.FabricPeerConfig) error { - // Validate listen address - if err := s.validateAddress(config.ListenAddress); err != nil { - return fmt.Errorf("invalid listen address: %w", err) - } - - // Validate chaincode address - if err := s.validateAddress(config.ChaincodeAddress); err != nil { - return fmt.Errorf("invalid chaincode address: %w", err) - } - - // Validate events address - if err := s.validateAddress(config.EventsAddress); err != nil { - return fmt.Errorf("invalid events address: %w", err) - } - - // Validate operations listen address - if err := s.validateAddress(config.OperationsListenAddress); err != nil { - return fmt.Errorf("invalid operations listen address: %w", err) - } - - // Check for port conflicts between addresses - addresses := map[string]string{ + // Get current addresses to compare against + currentAddresses := map[string]string{ "listen": config.ListenAddress, "chaincode": config.ChaincodeAddress, "events": config.EventsAddress, "operations": config.OperationsListenAddress, } + // Check for port conflicts between addresses usedPorts := make(map[string]string) - for addrType, addr := range addresses { + for addrType, addr := range currentAddresses { _, port, err := net.SplitHostPort(addr) if err != nil { return fmt.Errorf("invalid %s address format: %w", addrType, err) } if existingType, exists := usedPorts[port]; exists { + // If the port is already used by the same address type, it's okay + if existingType == addrType { + continue + } return fmt.Errorf("port conflict: %s and %s addresses use the same port %s", existingType, addrType, port) } usedPorts[port] = addrType + + // Only validate port availability if it's not already in use by this peer + if err := s.validateAddress(addr); err != nil { + // Check if the error is due to the port being in use by this peer + if strings.Contains(err.Error(), "address already in use") { + continue + } + return fmt.Errorf("invalid %s address: %w", addrType, err) + } } return nil @@ -486,6 +483,7 @@ func (s *NodeService) getPeerFromConfig(dbNode db.Node, org *fabricservice.Organ DomainNames: config.DomainNames, Env: config.Env, Version: config.Version, + AddressOverrides: config.AddressOverrides, }, config.Mode, org, @@ -524,11 +522,12 @@ func (s *NodeService) getOrdererFromConfig(dbNode db.Node, org *fabricservice.Or ID: dbNode.Name, ListenAddress: config.ListenAddress, OperationsListenAddress: config.OperationsListenAddress, - AdminAddress: config.AdminAddress, + AdminListenAddress: config.AdminAddress, ExternalEndpoint: config.ExternalEndpoint, DomainNames: config.DomainNames, Env: config.Env, Version: config.Version, + AddressOverrides: config.AddressOverrides, }, config.Mode, org, @@ -828,6 +827,7 @@ func (s *NodeService) mapDBNodeToServiceNode(dbNode db.Node) (*Node, *NodeRespon peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) peerDeployConfig, ok := deploymentConfig.(*types.FabricPeerDeploymentConfig) if ok && peerConfig != nil { + nodeResponse.FabricPeer.AddressOverrides = peerDeployConfig.AddressOverrides // Get certificates from key service signKey, err := s.keymanagementService.GetKey(ctx, int(peerDeployConfig.SignKeyID)) if err == nil && signKey.Certificate != nil { @@ -2210,3 +2210,627 @@ func (s *NodeService) renewOrdererCertificates(ctx context.Context, dbNode db.No return nil } + +// UpdateNodeEnvironment updates the environment variables for a node +func (s *NodeService) UpdateNodeEnvironment(ctx context.Context, nodeID int64, req *types.UpdateNodeEnvRequest) (*types.UpdateNodeEnvResponse, error) { + // Get the node from the database + dbNode, err := s.db.GetNode(ctx, nodeID) + if err != nil { + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Get the node's current configuration + switch dbNode.NodeType.String { + case string(types.NodeTypeFabricPeer): + var peerConfig types.FabricPeerConfig + if err := json.Unmarshal([]byte(dbNode.Config.String), &peerConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal peer config: %w", err) + } + peerConfig.Env = req.Env + newConfig, err := json.Marshal(peerConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated peer config: %w", err) + } + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + case string(types.NodeTypeFabricOrderer): + var ordererConfig types.FabricOrdererConfig + if err := json.Unmarshal([]byte(dbNode.Config.String), &ordererConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal orderer config: %w", err) + } + ordererConfig.Env = req.Env + newConfig, err := json.Marshal(ordererConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated orderer config: %w", err) + } + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + default: + return nil, fmt.Errorf("unsupported node type: %s", dbNode.NodeType.String) + } + + // Return the updated environment variables and indicate that a restart is required + return &types.UpdateNodeEnvResponse{ + Env: req.Env, + RequiresRestart: true, + }, nil +} + +// GetNodeEnvironment retrieves the current environment variables for a node +func (s *NodeService) GetNodeEnvironment(ctx context.Context, nodeID int64) (map[string]string, error) { + // Get the node from the database + dbNode, err := s.db.GetNode(ctx, nodeID) + if err != nil { + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Get the node's current configuration + switch dbNode.NodeType.String { + case string(types.NodeTypeFabricPeer): + var peerConfig types.FabricPeerConfig + if err := json.Unmarshal([]byte(dbNode.Config.String), &peerConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal peer config: %w", err) + } + return peerConfig.Env, nil + + case string(types.NodeTypeFabricOrderer): + var ordererConfig types.FabricOrdererConfig + if err := json.Unmarshal([]byte(dbNode.Config.String), &ordererConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal orderer config: %w", err) + } + return ordererConfig.Env, nil + + default: + return nil, fmt.Errorf("unsupported node type: %s", dbNode.NodeType.String) + } +} + +// UpdateNodeConfig updates a node's configuration +func (s *NodeService) UpdateNodeConfig(ctx context.Context, nodeID int64, req *types.UpdateNodeConfigRequest) (*types.UpdateNodeConfigResponse, error) { + // Get the node from the database + dbNode, err := s.db.GetNode(ctx, nodeID) + if err != nil { + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Load current config + nodeConfig, err := utils.LoadNodeConfig([]byte(dbNode.Config.String)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal node config: %w", err) + } + + // Update configuration based on node type + switch dbNode.NodeType.String { + case string(types.NodeTypeFabricPeer): + peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) + if !ok { + return nil, fmt.Errorf("invalid peer config type") + } + + // Update peer-specific fields if provided + if req.ListenAddress != "" { + if err := s.validateAddress(req.ListenAddress); err != nil { + return nil, fmt.Errorf("invalid listen address: %w", err) + } + peerConfig.ListenAddress = req.ListenAddress + } + if req.ChaincodeAddress != "" { + if err := s.validateAddress(req.ChaincodeAddress); err != nil { + return nil, fmt.Errorf("invalid chaincode address: %w", err) + } + peerConfig.ChaincodeAddress = req.ChaincodeAddress + } + if req.EventsAddress != "" { + if err := s.validateAddress(req.EventsAddress); err != nil { + return nil, fmt.Errorf("invalid events address: %w", err) + } + peerConfig.EventsAddress = req.EventsAddress + } + if req.OperationsListenAddress != "" { + if err := s.validateAddress(req.OperationsListenAddress); err != nil { + return nil, fmt.Errorf("invalid operations listen address: %w", err) + } + peerConfig.OperationsListenAddress = req.OperationsListenAddress + } + if req.ExternalEndpoint != "" { + peerConfig.ExternalEndpoint = req.ExternalEndpoint + } + if req.DomainNames != nil { + peerConfig.DomainNames = req.DomainNames + } + if req.Mode != "" { + peerConfig.Mode = req.Mode + } + if req.Env != nil { + peerConfig.Env = req.Env + } + + // Validate all addresses together for port conflicts + if err := s.validateFabricPeerAddresses(peerConfig); err != nil { + return nil, err + } + + // Update the config in the database + newConfig, err := json.Marshal(peerConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated peer config: %w", err) + } + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + return &types.UpdateNodeConfigResponse{ + Config: peerConfig, + RequiresRestart: true, + }, nil + + case string(types.NodeTypeFabricOrderer): + ordererConfig, ok := nodeConfig.(*types.FabricOrdererConfig) + if !ok { + return nil, fmt.Errorf("invalid orderer config type") + } + + // Update orderer-specific fields if provided + if req.ListenAddress != "" { + if err := s.validateAddress(req.ListenAddress); err != nil { + return nil, fmt.Errorf("invalid listen address: %w", err) + } + ordererConfig.ListenAddress = req.ListenAddress + } + if req.AdminAddress != "" { + if err := s.validateAddress(req.AdminAddress); err != nil { + return nil, fmt.Errorf("invalid admin address: %w", err) + } + ordererConfig.AdminAddress = req.AdminAddress + } + if req.OperationsListenAddress != "" { + if err := s.validateAddress(req.OperationsListenAddress); err != nil { + return nil, fmt.Errorf("invalid operations listen address: %w", err) + } + ordererConfig.OperationsListenAddress = req.OperationsListenAddress + } + if req.ExternalEndpoint != "" { + ordererConfig.ExternalEndpoint = req.ExternalEndpoint + } + if req.DomainNames != nil { + ordererConfig.DomainNames = req.DomainNames + } + if req.Mode != "" { + ordererConfig.Mode = req.Mode + } + if req.Env != nil { + ordererConfig.Env = req.Env + } + + // Validate all addresses together for port conflicts + if err := s.validateFabricOrdererAddresses(ordererConfig); err != nil { + return nil, err + } + + // Update the config in the database + newConfig, err := json.Marshal(ordererConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated orderer config: %w", err) + } + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + return &types.UpdateNodeConfigResponse{ + Config: ordererConfig, + RequiresRestart: true, + }, nil + + case string(types.NodeTypeBesuFullnode): + besuConfig, ok := nodeConfig.(*types.BesuNodeConfig) + if !ok { + return nil, fmt.Errorf("invalid besu config type") + } + + // Update besu-specific fields if provided + if req.P2PPort != 0 { + if err := s.validatePort(besuConfig.P2PHost, int(req.P2PPort)); err != nil { + return nil, fmt.Errorf("invalid P2P port: %w", err) + } + besuConfig.P2PPort = req.P2PPort + } + if req.RPCPort != 0 { + if err := s.validatePort(besuConfig.RPCHost, int(req.RPCPort)); err != nil { + return nil, fmt.Errorf("invalid RPC port: %w", err) + } + besuConfig.RPCPort = req.RPCPort + } + if req.P2PHost != "" { + besuConfig.P2PHost = req.P2PHost + } + if req.RPCHost != "" { + besuConfig.RPCHost = req.RPCHost + } + if req.ExternalIP != "" { + besuConfig.ExternalIP = req.ExternalIP + } + if req.InternalIP != "" { + besuConfig.InternalIP = req.InternalIP + } + if req.Mode != "" { + besuConfig.Mode = req.Mode + } + if req.Env != nil { + besuConfig.Env = req.Env + } + + // Update the config in the database + newConfig, err := json.Marshal(besuConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated besu config: %w", err) + } + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + return &types.UpdateNodeConfigResponse{ + Config: besuConfig, + RequiresRestart: true, + }, nil + + default: + return nil, fmt.Errorf("unsupported node type: %s", dbNode.NodeType.String) + } +} + +// UpdatePeerOrdererOverrides updates the orderer address overrides for a Fabric peer +func (s *NodeService) UpdatePeerOrdererOverrides(ctx context.Context, nodeID int64, req *types.UpdatePeerOrdererOverridesRequest) (*types.UpdatePeerOrdererOverridesResponse, error) { + // Get the node from the database + dbNode, err := s.db.GetNode(ctx, nodeID) + if err != nil { + return nil, fmt.Errorf("failed to get node: %w", err) + } + + // Verify node type + if dbNode.NodeType.String != string(types.NodeTypeFabricPeer) { + return nil, fmt.Errorf("node is not a Fabric peer") + } + + // Load current config + nodeConfig, err := utils.LoadNodeConfig([]byte(dbNode.Config.String)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal node config: %w", err) + } + + peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) + if !ok { + return nil, fmt.Errorf("invalid peer config type") + } + + // Validate each override + for _, override := range req.Overrides { + // Validate 'from' address format + if _, _, err := net.SplitHostPort(override.From); err != nil { + return nil, fmt.Errorf("invalid 'from' address format in override: %s - %w", override.From, err) + } + + // Validate 'to' address format + if _, _, err := net.SplitHostPort(override.To); err != nil { + return nil, fmt.Errorf("invalid 'to' address format in override: %s - %w", override.To, err) + } + + // Validate TLS CA certificate + block, _ := pem.Decode([]byte(override.TLSCACert)) + if block == nil { + return nil, fmt.Errorf("failed to decode TLS CA certificate for override %s", override.From) + } + if _, err := x509.ParseCertificate(block.Bytes); err != nil { + return nil, fmt.Errorf("invalid TLS CA certificate for override %s: %w", override.From, err) + } + } + + // Update the orderer address overrides + peerConfig.OrdererAddressOverrides = req.Overrides + + // Update the config in the database + newConfig, err := json.Marshal(peerConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated peer config: %w", err) + } + + if _, err := s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: nodeID, + Config: sql.NullString{String: string(newConfig), Valid: true}, + }); err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + // Return the updated overrides and indicate that a restart is required + return &types.UpdatePeerOrdererOverridesResponse{ + Overrides: req.Overrides, + RequiresRestart: true, + }, nil +} + +// UpdateFabricPeer updates a Fabric peer node configuration +func (s *NodeService) UpdateFabricPeer(ctx context.Context, opts UpdateFabricPeerOpts) (*NodeResponse, error) { + // Get the node from database + node, err := s.db.GetNode(ctx, opts.NodeID) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.NewNotFoundError("peer node not found", nil) + } + return nil, fmt.Errorf("failed to get peer node: %w", err) + } + + // Verify node type + if types.NodeType(node.NodeType.String) != types.NodeTypeFabricPeer { + return nil, fmt.Errorf("node %d is not a Fabric peer", opts.NodeID) + } + + // Load current config + nodeConfig, err := utils.LoadNodeConfig([]byte(node.NodeConfig.String)) + if err != nil { + return nil, fmt.Errorf("failed to load peer config: %w", err) + } + + peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) + if !ok { + return nil, fmt.Errorf("invalid peer config type") + } + + deployConfig, err := utils.DeserializeDeploymentConfig(node.DeploymentConfig.String) + if err != nil { + return nil, fmt.Errorf("failed to deserialize deployment config: %w", err) + } + deployPeerConfig, ok := deployConfig.(*types.FabricPeerDeploymentConfig) + if !ok { + return nil, fmt.Errorf("invalid deployment config type") + } + + // Update configuration fields if provided + if opts.ExternalEndpoint != "" && opts.ExternalEndpoint != peerConfig.ExternalEndpoint { + peerConfig.ExternalEndpoint = opts.ExternalEndpoint + } + if opts.ListenAddress != "" && opts.ListenAddress != peerConfig.ListenAddress { + if err := s.validateAddress(opts.ListenAddress); err != nil { + return nil, fmt.Errorf("invalid listen address: %w", err) + } + peerConfig.ListenAddress = opts.ListenAddress + } + if opts.EventsAddress != "" && opts.EventsAddress != peerConfig.EventsAddress { + if err := s.validateAddress(opts.EventsAddress); err != nil { + return nil, fmt.Errorf("invalid events address: %w", err) + } + peerConfig.EventsAddress = opts.EventsAddress + } + if opts.OperationsListenAddress != "" && opts.OperationsListenAddress != peerConfig.OperationsListenAddress { + if err := s.validateAddress(opts.OperationsListenAddress); err != nil { + return nil, fmt.Errorf("invalid operations listen address: %w", err) + } + peerConfig.OperationsListenAddress = opts.OperationsListenAddress + } + if opts.ChaincodeAddress != "" && opts.ChaincodeAddress != peerConfig.ChaincodeAddress { + if err := s.validateAddress(opts.ChaincodeAddress); err != nil { + return nil, fmt.Errorf("invalid chaincode address: %w", err) + } + peerConfig.ChaincodeAddress = opts.ChaincodeAddress + } + if opts.DomainNames != nil { + peerConfig.DomainNames = opts.DomainNames + } + if opts.Env != nil { + peerConfig.Env = opts.Env + } + if opts.AddressOverrides != nil { + peerConfig.AddressOverrides = opts.AddressOverrides + deployPeerConfig.AddressOverrides = opts.AddressOverrides + } + + // Validate all addresses together for port conflicts + if err := s.validateFabricPeerAddresses(peerConfig); err != nil { + return nil, err + } + + // Update the config in the database + configBytes, err := json.Marshal(peerConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated peer config: %w", err) + } + + node, err = s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: opts.NodeID, + Config: sql.NullString{ + String: string(configBytes), + Valid: true, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + // Update the deployment config in the database + deploymentConfigBytes, err := json.Marshal(deployPeerConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated deployment config: %w", err) + } + + node, err = s.db.UpdateDeploymentConfig(ctx, db.UpdateDeploymentConfigParams{ + ID: opts.NodeID, + DeploymentConfig: sql.NullString{ + String: string(deploymentConfigBytes), + Valid: true, + }, + }) + + // Synchronize the peer config + if err := s.SynchronizePeerConfig(ctx, opts.NodeID); err != nil { + return nil, fmt.Errorf("failed to synchronize peer config: %w", err) + } + + // Return updated node response + _, nodeResponse := s.mapDBNodeToServiceNode(node) + return nodeResponse, nil +} + +// UpdateFabricOrderer updates a Fabric orderer node configuration +func (s *NodeService) UpdateFabricOrderer(ctx context.Context, opts UpdateFabricOrdererOpts) (*NodeResponse, error) { + // Get the node from database + node, err := s.db.GetNode(ctx, opts.NodeID) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.NewNotFoundError("orderer node not found", nil) + } + return nil, fmt.Errorf("failed to get orderer node: %w", err) + } + + // Verify node type + if types.NodeType(node.NodeType.String) != types.NodeTypeFabricOrderer { + return nil, fmt.Errorf("node %d is not a Fabric orderer", opts.NodeID) + } + + // Load current config + nodeConfig, err := utils.LoadNodeConfig([]byte(node.NodeConfig.String)) + if err != nil { + return nil, fmt.Errorf("failed to load orderer config: %w", err) + } + + ordererConfig, ok := nodeConfig.(*types.FabricOrdererConfig) + if !ok { + return nil, fmt.Errorf("invalid orderer config type") + } + + // Update configuration fields if provided + if opts.ExternalEndpoint != "" { + ordererConfig.ExternalEndpoint = opts.ExternalEndpoint + } + if opts.ListenAddress != "" { + if err := s.validateAddress(opts.ListenAddress); err != nil { + return nil, fmt.Errorf("invalid listen address: %w", err) + } + ordererConfig.ListenAddress = opts.ListenAddress + } + if opts.AdminAddress != "" { + if err := s.validateAddress(opts.AdminAddress); err != nil { + return nil, fmt.Errorf("invalid admin address: %w", err) + } + ordererConfig.AdminAddress = opts.AdminAddress + } + if opts.OperationsListenAddress != "" { + if err := s.validateAddress(opts.OperationsListenAddress); err != nil { + return nil, fmt.Errorf("invalid operations listen address: %w", err) + } + ordererConfig.OperationsListenAddress = opts.OperationsListenAddress + } + if opts.DomainNames != nil { + ordererConfig.DomainNames = opts.DomainNames + } + if opts.Env != nil { + ordererConfig.Env = opts.Env + } + + // Validate all addresses together for port conflicts + if err := s.validateFabricOrdererAddresses(ordererConfig); err != nil { + return nil, err + } + + // Update the config in the database + configBytes, err := json.Marshal(ordererConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal updated orderer config: %w", err) + } + + node, err = s.db.UpdateNodeConfig(ctx, db.UpdateNodeConfigParams{ + ID: opts.NodeID, + Config: sql.NullString{ + String: string(configBytes), + Valid: true, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to update node config: %w", err) + } + + // Return updated node response + _, nodeResponse := s.mapDBNodeToServiceNode(node) + return nodeResponse, nil +} + +// SynchronizePeerConfig synchronizes the peer's configuration files and service +func (s *NodeService) SynchronizePeerConfig(ctx context.Context, nodeID int64) error { + // Get the node from database + node, err := s.db.GetNode(ctx, nodeID) + if err != nil { + return fmt.Errorf("failed to get node: %w", err) + } + + // Verify node type + if types.NodeType(node.NodeType.String) != types.NodeTypeFabricPeer { + return fmt.Errorf("node %d is not a Fabric peer", nodeID) + } + + // Load node config + nodeConfig, err := utils.LoadNodeConfig([]byte(node.NodeConfig.String)) + if err != nil { + return fmt.Errorf("failed to load node config: %w", err) + } + + peerConfig, ok := nodeConfig.(*types.FabricPeerConfig) + if !ok { + return fmt.Errorf("invalid peer config type") + } + + // Get organization + org, err := s.orgService.GetOrganization(ctx, peerConfig.OrganizationID) + if err != nil { + return fmt.Errorf("failed to get organization: %w", err) + } + + // Get local peer instance + localPeer := s.getPeerFromConfig(node, org, peerConfig) + + // Get deployment config + deploymentConfig, err := utils.DeserializeDeploymentConfig(node.DeploymentConfig.String) + if err != nil { + return fmt.Errorf("failed to deserialize deployment config: %w", err) + } + + peerDeployConfig, ok := deploymentConfig.(*types.FabricPeerDeploymentConfig) + if !ok { + return fmt.Errorf("invalid peer deployment config type") + } + + // Synchronize configuration + if err := localPeer.SynchronizeConfig(peerDeployConfig); err != nil { + return fmt.Errorf("failed to synchronize peer config: %w", err) + } + + // Get home directory + // homeDir, err := os.UserHomeDir() + // if err != nil { + // return fmt.Errorf("failed to get home directory: %w", err) + // } + + // // Update systemd service if running on Linux + // if runtime.GOOS == "linux" { + // if err := localPeer.UpdateSystemdService(); err != nil { + // return fmt.Errorf("failed to update systemd service: %w", err) + // } + // } + + return nil +} diff --git a/pkg/nodes/service/types.go b/pkg/nodes/service/types.go new file mode 100644 index 0000000..80b6dc3 --- /dev/null +++ b/pkg/nodes/service/types.go @@ -0,0 +1,29 @@ +package service + +import ( + "github.com/chainlaunch/chainlaunch/pkg/nodes/types" +) + +// UpdateFabricPeerOpts represents the options for updating a Fabric peer node +type UpdateFabricPeerOpts struct { + NodeID int64 + ExternalEndpoint string + ListenAddress string + EventsAddress string + OperationsListenAddress string + ChaincodeAddress string + DomainNames []string + Env map[string]string + AddressOverrides []types.AddressOverride +} + +// UpdateFabricOrdererOpts represents the options for updating a Fabric orderer node +type UpdateFabricOrdererOpts struct { + NodeID int64 + ExternalEndpoint string + ListenAddress string + AdminAddress string + OperationsListenAddress string + DomainNames []string + Env map[string]string +} diff --git a/pkg/nodes/types/deployment.go b/pkg/nodes/types/deployment.go index 2547ed7..b0bd98e 100644 --- a/pkg/nodes/types/deployment.go +++ b/pkg/nodes/types/deployment.go @@ -79,6 +79,9 @@ type FabricPeerDeploymentConfig struct { ExternalEndpoint string `json:"externalEndpoint" example:"peer0.org1.example.com:7051"` // @Description Domain names for the peer DomainNames []string `json:"domainNames,omitempty"` + + // @Description Address overrides for the peer + AddressOverrides []AddressOverride `json:"addressOverrides,omitempty"` } func (c *FabricPeerDeploymentConfig) GetMode() string { return c.Mode } @@ -289,6 +292,10 @@ type FabricPeerConfig struct { Env map[string]string `json:"env,omitempty"` // @Description Fabric version to use Version string `json:"version" example:"2.2.0"` + // @Description Orderer address overrides for the peer + OrdererAddressOverrides []OrdererAddressOverride `json:"ordererAddressOverrides,omitempty"` + // @Description Address overrides for the peer + AddressOverrides []AddressOverride `json:"addressOverrides,omitempty"` } // FabricOrdererConfig represents the parameters needed to create a Fabric orderer node @@ -304,6 +311,8 @@ type FabricOrdererConfig struct { DomainNames []string `json:"domainNames,omitempty"` Env map[string]string `json:"env,omitempty"` Version string `json:"version"` // Fabric version to use + // @Description Address overrides for the orderer + AddressOverrides []AddressOverride `json:"addressOverrides,omitempty"` } // BesuNodeConfig represents the parameters needed to create a Besu node @@ -446,3 +455,104 @@ func MapToNodeConfig(deploymentConfig NodeDeploymentConfig) (NodeConfig, error) return nil, fmt.Errorf("unsupported node type: %s", deploymentConfig.GetType()) } } + +// UpdateNodeEnvRequest represents a request to update a node's environment variables +type UpdateNodeEnvRequest struct { + // @Description Environment variables to update + Env map[string]string `json:"env" validate:"required"` +} + +// UpdateNodeEnvResponse represents the response after updating a node's environment variables +type UpdateNodeEnvResponse struct { + // @Description Updated environment variables + Env map[string]string `json:"env"` + // @Description Whether the node needs to be restarted for changes to take effect + RequiresRestart bool `json:"requiresRestart"` +} + +// UpdateNodeConfigRequest represents a request to update a node's configuration +type UpdateNodeConfigRequest struct { + // Common fields + // @Description Environment variables to update + Env map[string]string `json:"env,omitempty"` + // @Description Domain names for the node + DomainNames []string `json:"domainNames,omitempty"` + // @Description The deployment mode (service or docker) + Mode string `json:"mode,omitempty" validate:"omitempty,oneof=service docker"` + + // Fabric peer specific fields + // @Description Listen address for the peer + ListenAddress string `json:"listenAddress,omitempty"` + // @Description Chaincode listen address + ChaincodeAddress string `json:"chaincodeAddress,omitempty"` + // @Description Events listen address + EventsAddress string `json:"eventsAddress,omitempty"` + // @Description Operations listen address + OperationsListenAddress string `json:"operationsListenAddress,omitempty"` + // @Description External endpoint for the peer + ExternalEndpoint string `json:"externalEndpoint,omitempty"` + + // Fabric orderer specific fields + // @Description Admin listen address for orderer + AdminAddress string `json:"adminAddress,omitempty"` + + // Besu specific fields + // @Description P2P port for Besu node + P2PPort uint `json:"p2pPort,omitempty"` + // @Description RPC port for Besu node + RPCPort uint `json:"rpcPort,omitempty"` + // @Description P2P host address + P2PHost string `json:"p2pHost,omitempty"` + // @Description RPC host address + RPCHost string `json:"rpcHost,omitempty"` + // @Description External IP address + ExternalIP string `json:"externalIp,omitempty"` + // @Description Internal IP address + InternalIP string `json:"internalIp,omitempty"` +} + +// UpdateNodeConfigResponse represents the response after updating a node's configuration +type UpdateNodeConfigResponse struct { + // @Description Updated node configuration + Config NodeConfig `json:"config"` + // @Description Whether the node needs to be restarted for changes to take effect + RequiresRestart bool `json:"requiresRestart"` +} + +// OrdererAddressOverride represents an orderer address override configuration +type OrdererAddressOverride struct { + // @Description Original orderer address + From string `json:"from" validate:"required"` + // @Description New orderer address to use + To string `json:"to" validate:"required"` + // @Description TLS CA certificate in PEM format + TLSCACert string `json:"tlsCACert" validate:"required"` +} + +// UpdatePeerOrdererOverridesRequest represents a request to update a peer's orderer address overrides +type UpdatePeerOrdererOverridesRequest struct { + // @Description List of orderer address overrides + Overrides []OrdererAddressOverride `json:"overrides" validate:"required,dive"` +} + +// UpdatePeerOrdererOverridesResponse represents the response after updating orderer address overrides +type UpdatePeerOrdererOverridesResponse struct { + // @Description Updated orderer address overrides + Overrides []OrdererAddressOverride `json:"overrides"` + // @Description Whether the node needs to be restarted for changes to take effect + RequiresRestart bool `json:"requiresRestart"` +} + +// UpdateNodeAddressOverridesRequest represents a request to update a node's address overrides +type UpdateNodeAddressOverridesRequest struct { + // @Description List of address overrides + Overrides []AddressOverride `json:"overrides" validate:"required,dive"` +} + +// UpdateNodeAddressOverridesResponse represents the response after updating address overrides +type UpdateNodeAddressOverridesResponse struct { + // @Description Updated address overrides + Overrides []AddressOverride `json:"overrides"` + // @Description Whether the node needs to be restarted for changes to take effect + RequiresRestart bool `json:"requiresRestart"` +} diff --git a/pkg/nodes/types/types.go b/pkg/nodes/types/types.go index 93c72f4..199db47 100644 --- a/pkg/nodes/types/types.go +++ b/pkg/nodes/types/types.go @@ -47,3 +47,9 @@ type StoredConfig struct { Type string `json:"type"` Config json.RawMessage `json:"config"` } + +type AddressOverride struct { + From string `json:"from"` + To string `json:"to"` + TLSCACert string `json:"tlsCACert"` +} diff --git a/web/src/App.tsx b/web/src/App.tsx index fddfe53..e558bd8 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -14,6 +14,7 @@ import './globals.css' import CreateBesuNodePage from '@/pages/nodes/besu/create' import CreateFabricNodePage from '@/pages/nodes/fabric/create' +import EditFabricNodePage from '@/pages/nodes/fabric/edit' import NodesLogsPage from '@/pages/nodes/logs' import { Toaster } from './components/ui/sonner' import CertificateTemplatesPage from './pages/identity/certificates' @@ -89,6 +90,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/web/src/api/client/@tanstack/react-query.gen.ts b/web/src/api/client/@tanstack/react-query.gen.ts index 54d77c7..38392d5 100644 --- a/web/src/api/client/@tanstack/react-query.gen.ts +++ b/web/src/api/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import type { Options } from '@hey-api/client-fetch'; import { queryOptions, type UseMutationOptions, type DefaultError, infiniteQueryOptions, type InfiniteData } from '@tanstack/react-query'; -import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNetworksFabricByIdTransactionsByTxIdData, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigError, PostNetworksFabricByIdUpdateConfigResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; -import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdBlocks, getNetworksFabricByIdBlocksByBlockNumTransactions, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNetworksFabricByIdTransactionsByTxId, postNetworksFabricByIdUpdateConfig, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; +import type { PostAuthLoginData, PostAuthLoginError, PostAuthLoginResponse, PostAuthLogoutData, PostAuthLogoutError, PostAuthLogoutResponse, GetAuthMeData, GetBackupsData, PostBackupsData, PostBackupsError, PostBackupsResponse, GetBackupsSchedulesData, PostBackupsSchedulesData, PostBackupsSchedulesError, PostBackupsSchedulesResponse, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableError, PutBackupsSchedulesByIdEnableResponse, GetBackupsTargetsData, PostBackupsTargetsData, PostBackupsTargetsError, PostBackupsTargetsResponse, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, PutBackupsTargetsByIdData, PutBackupsTargetsByIdError, PutBackupsTargetsByIdResponse, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, PostDummyData, PostDummyResponse, GetKeyProvidersData, PostKeyProvidersData, PostKeyProvidersError, PostKeyProvidersResponse, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeysData, GetKeysError, GetKeysResponse, PostKeysData, PostKeysError, PostKeysResponse, GetKeysAllData, GetKeysFilterData, GetKeysFilterError, GetKeysFilterResponse, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, PostKeysByKeyIdSignData, PostKeysByKeyIdSignError, PostKeysByKeyIdSignResponse, GetNetworksBesuData, GetNetworksBesuError, GetNetworksBesuResponse, PostNetworksBesuData, PostNetworksBesuError, PostNetworksBesuResponse, PostNetworksBesuImportData, PostNetworksBesuImportError, PostNetworksBesuImportResponse, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksFabricData, GetNetworksFabricError, GetNetworksFabricResponse, PostNetworksFabricData, PostNetworksFabricError, PostNetworksFabricResponse, GetNetworksFabricByNameByNameData, PostNetworksFabricImportData, PostNetworksFabricImportError, PostNetworksFabricImportResponse, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgError, PostNetworksFabricImportWithOrgResponse, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersError, PostNetworksFabricByIdAnchorPeersResponse, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesResponse, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdError, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdError, DeleteNetworksFabricByIdPeersByPeerIdResponse, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockError, PostNetworksFabricByIdReloadBlockResponse, GetNetworksFabricByIdTransactionsByTxIdData, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigError, PostNetworksFabricByIdUpdateConfigResponse, GetNodesData, GetNodesError, GetNodesResponse, PostNodesData, PostNodesError, PostNodesResponse, GetNodesDefaultsBesuNodeData, GetNodesDefaultsFabricData, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricPeerData, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformError, GetNodesPlatformByPlatformResponse, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, PutNodesByIdData, PutNodesByIdError, PutNodesByIdResponse, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewError, PostNodesByIdCertificatesRenewResponse, GetNodesByIdChannelsData, GetNodesByIdEventsData, GetNodesByIdEventsError, GetNodesByIdEventsResponse, GetNodesByIdLogsData, PostNodesByIdRestartData, PostNodesByIdRestartError, PostNodesByIdRestartResponse, PostNodesByIdStartData, PostNodesByIdStartError, PostNodesByIdStartResponse, PostNodesByIdStopData, PostNodesByIdStopError, PostNodesByIdStopResponse, GetNotificationsProvidersData, PostNotificationsProvidersData, PostNotificationsProvidersError, PostNotificationsProvidersResponse, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdError, PutNotificationsProvidersByIdResponse, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestError, PostNotificationsProvidersByIdTestResponse, GetOrganizationsData, PostOrganizationsData, PostOrganizationsError, PostOrganizationsResponse, GetOrganizationsByMspidByMspidData, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, PutOrganizationsByIdData, PutOrganizationsByIdError, PutOrganizationsByIdResponse } from '../types.gen'; +import { postAuthLogin, postAuthLogout, getAuthMe, getBackups, postBackups, getBackupsSchedules, postBackupsSchedules, deleteBackupsSchedulesById, getBackupsSchedulesById, putBackupsSchedulesById, putBackupsSchedulesByIdDisable, putBackupsSchedulesByIdEnable, getBackupsTargets, postBackupsTargets, deleteBackupsTargetsById, getBackupsTargetsById, putBackupsTargetsById, deleteBackupsById, getBackupsById, postDummy, getKeyProviders, postKeyProviders, deleteKeyProvidersById, getKeyProvidersById, getKeys, postKeys, getKeysAll, getKeysFilter, deleteKeysById, getKeysById, postKeysByKeyIdSign, getNetworksBesu, postNetworksBesu, postNetworksBesuImport, deleteNetworksBesuById, getNetworksBesuById, getNetworksFabric, postNetworksFabric, getNetworksFabricByNameByName, postNetworksFabricImport, postNetworksFabricImportWithOrg, deleteNetworksFabricById, getNetworksFabricById, postNetworksFabricByIdAnchorPeers, getNetworksFabricByIdBlocks, getNetworksFabricByIdBlocksByBlockNumTransactions, getNetworksFabricByIdChannelConfig, getNetworksFabricByIdCurrentChannelConfig, getNetworksFabricByIdNodes, postNetworksFabricByIdNodes, deleteNetworksFabricByIdOrderersByOrdererId, postNetworksFabricByIdOrderersByOrdererIdJoin, postNetworksFabricByIdOrderersByOrdererIdUnjoin, getNetworksFabricByIdOrganizationsByOrgIdConfig, deleteNetworksFabricByIdPeersByPeerId, postNetworksFabricByIdPeersByPeerIdJoin, postNetworksFabricByIdPeersByPeerIdUnjoin, postNetworksFabricByIdReloadBlock, getNetworksFabricByIdTransactionsByTxId, postNetworksFabricByIdUpdateConfig, getNodes, postNodes, getNodesDefaultsBesuNode, getNodesDefaultsFabric, getNodesDefaultsFabricOrderer, getNodesDefaultsFabricPeer, getNodesPlatformByPlatform, deleteNodesById, getNodesById, putNodesById, postNodesByIdCertificatesRenew, getNodesByIdChannels, getNodesByIdEvents, getNodesByIdLogs, postNodesByIdRestart, postNodesByIdStart, postNodesByIdStop, getNotificationsProviders, postNotificationsProviders, deleteNotificationsProvidersById, getNotificationsProvidersById, putNotificationsProvidersById, postNotificationsProvidersByIdTest, getOrganizations, postOrganizations, getOrganizationsByMspidByMspid, deleteOrganizationsById, getOrganizationsById, putOrganizationsById, client } from '../sdk.gen'; type QueryKey = [ Pick & { @@ -1820,6 +1820,20 @@ export const getNodesByIdOptions = (options: Options) => { }); }; +export const putNodesByIdMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await putNodesById({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const postNodesByIdCertificatesRenewQueryKey = (options: Options) => [ createQueryKey('postNodesByIdCertificatesRenew', options) ]; diff --git a/web/src/api/client/sdk.gen.ts b/web/src/api/client/sdk.gen.ts index abac460..0f40162 100644 --- a/web/src/api/client/sdk.gen.ts +++ b/web/src/api/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdBlocksByBlockNumTransactionsResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNetworksFabricByIdTransactionsByTxIdData, GetNetworksFabricByIdTransactionsByTxIdResponse, GetNetworksFabricByIdTransactionsByTxIdError, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigResponse, PostNetworksFabricByIdUpdateConfigError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; +import type { PostAuthLoginData, PostAuthLoginResponse, PostAuthLoginError, PostAuthLogoutData, PostAuthLogoutResponse, PostAuthLogoutError, GetAuthMeData, GetAuthMeResponse, GetAuthMeError, GetBackupsData, GetBackupsResponse, GetBackupsError, PostBackupsData, PostBackupsResponse, PostBackupsError, GetBackupsSchedulesData, GetBackupsSchedulesResponse, GetBackupsSchedulesError, PostBackupsSchedulesData, PostBackupsSchedulesResponse, PostBackupsSchedulesError, DeleteBackupsSchedulesByIdData, DeleteBackupsSchedulesByIdError, GetBackupsSchedulesByIdData, GetBackupsSchedulesByIdResponse, GetBackupsSchedulesByIdError, PutBackupsSchedulesByIdData, PutBackupsSchedulesByIdResponse, PutBackupsSchedulesByIdError, PutBackupsSchedulesByIdDisableData, PutBackupsSchedulesByIdDisableResponse, PutBackupsSchedulesByIdDisableError, PutBackupsSchedulesByIdEnableData, PutBackupsSchedulesByIdEnableResponse, PutBackupsSchedulesByIdEnableError, GetBackupsTargetsData, GetBackupsTargetsResponse, GetBackupsTargetsError, PostBackupsTargetsData, PostBackupsTargetsResponse, PostBackupsTargetsError, DeleteBackupsTargetsByIdData, DeleteBackupsTargetsByIdError, GetBackupsTargetsByIdData, GetBackupsTargetsByIdResponse, GetBackupsTargetsByIdError, PutBackupsTargetsByIdData, PutBackupsTargetsByIdResponse, PutBackupsTargetsByIdError, DeleteBackupsByIdData, DeleteBackupsByIdError, GetBackupsByIdData, GetBackupsByIdResponse, GetBackupsByIdError, PostDummyData, PostDummyResponse, GetKeyProvidersData, GetKeyProvidersResponse, GetKeyProvidersError, PostKeyProvidersData, PostKeyProvidersResponse, PostKeyProvidersError, DeleteKeyProvidersByIdData, DeleteKeyProvidersByIdError, GetKeyProvidersByIdData, GetKeyProvidersByIdResponse, GetKeyProvidersByIdError, GetKeysData, GetKeysResponse, GetKeysError, PostKeysData, PostKeysResponse, PostKeysError, GetKeysAllData, GetKeysAllResponse, GetKeysAllError, GetKeysFilterData, GetKeysFilterResponse, GetKeysFilterError, DeleteKeysByIdData, DeleteKeysByIdError, GetKeysByIdData, GetKeysByIdResponse, GetKeysByIdError, PostKeysByKeyIdSignData, PostKeysByKeyIdSignResponse, PostKeysByKeyIdSignError, GetNetworksBesuData, GetNetworksBesuResponse, GetNetworksBesuError, PostNetworksBesuData, PostNetworksBesuResponse, PostNetworksBesuError, PostNetworksBesuImportData, PostNetworksBesuImportResponse, PostNetworksBesuImportError, DeleteNetworksBesuByIdData, DeleteNetworksBesuByIdError, GetNetworksBesuByIdData, GetNetworksBesuByIdResponse, GetNetworksBesuByIdError, GetNetworksFabricData, GetNetworksFabricResponse, GetNetworksFabricError, PostNetworksFabricData, PostNetworksFabricResponse, PostNetworksFabricError, GetNetworksFabricByNameByNameData, GetNetworksFabricByNameByNameResponse, GetNetworksFabricByNameByNameError, PostNetworksFabricImportData, PostNetworksFabricImportResponse, PostNetworksFabricImportError, PostNetworksFabricImportWithOrgData, PostNetworksFabricImportWithOrgResponse, PostNetworksFabricImportWithOrgError, DeleteNetworksFabricByIdData, DeleteNetworksFabricByIdError, GetNetworksFabricByIdData, GetNetworksFabricByIdResponse, GetNetworksFabricByIdError, PostNetworksFabricByIdAnchorPeersData, PostNetworksFabricByIdAnchorPeersResponse, PostNetworksFabricByIdAnchorPeersError, GetNetworksFabricByIdBlocksData, GetNetworksFabricByIdBlocksResponse, GetNetworksFabricByIdBlocksError, GetNetworksFabricByIdBlocksByBlockNumTransactionsData, GetNetworksFabricByIdBlocksByBlockNumTransactionsResponse, GetNetworksFabricByIdBlocksByBlockNumTransactionsError, GetNetworksFabricByIdChannelConfigData, GetNetworksFabricByIdChannelConfigResponse, GetNetworksFabricByIdChannelConfigError, GetNetworksFabricByIdCurrentChannelConfigData, GetNetworksFabricByIdCurrentChannelConfigResponse, GetNetworksFabricByIdCurrentChannelConfigError, GetNetworksFabricByIdNodesData, GetNetworksFabricByIdNodesResponse, GetNetworksFabricByIdNodesError, PostNetworksFabricByIdNodesData, PostNetworksFabricByIdNodesResponse, PostNetworksFabricByIdNodesError, DeleteNetworksFabricByIdOrderersByOrdererIdData, DeleteNetworksFabricByIdOrderersByOrdererIdResponse, DeleteNetworksFabricByIdOrderersByOrdererIdError, PostNetworksFabricByIdOrderersByOrdererIdJoinData, PostNetworksFabricByIdOrderersByOrdererIdJoinResponse, PostNetworksFabricByIdOrderersByOrdererIdJoinError, PostNetworksFabricByIdOrderersByOrdererIdUnjoinData, PostNetworksFabricByIdOrderersByOrdererIdUnjoinResponse, PostNetworksFabricByIdOrderersByOrdererIdUnjoinError, GetNetworksFabricByIdOrganizationsByOrgIdConfigData, GetNetworksFabricByIdOrganizationsByOrgIdConfigResponse, GetNetworksFabricByIdOrganizationsByOrgIdConfigError, DeleteNetworksFabricByIdPeersByPeerIdData, DeleteNetworksFabricByIdPeersByPeerIdResponse, DeleteNetworksFabricByIdPeersByPeerIdError, PostNetworksFabricByIdPeersByPeerIdJoinData, PostNetworksFabricByIdPeersByPeerIdJoinResponse, PostNetworksFabricByIdPeersByPeerIdJoinError, PostNetworksFabricByIdPeersByPeerIdUnjoinData, PostNetworksFabricByIdPeersByPeerIdUnjoinResponse, PostNetworksFabricByIdPeersByPeerIdUnjoinError, PostNetworksFabricByIdReloadBlockData, PostNetworksFabricByIdReloadBlockResponse, PostNetworksFabricByIdReloadBlockError, GetNetworksFabricByIdTransactionsByTxIdData, GetNetworksFabricByIdTransactionsByTxIdResponse, GetNetworksFabricByIdTransactionsByTxIdError, PostNetworksFabricByIdUpdateConfigData, PostNetworksFabricByIdUpdateConfigResponse, PostNetworksFabricByIdUpdateConfigError, GetNodesData, GetNodesResponse, GetNodesError, PostNodesData, PostNodesResponse, PostNodesError, GetNodesDefaultsBesuNodeData, GetNodesDefaultsBesuNodeResponse, GetNodesDefaultsBesuNodeError, GetNodesDefaultsFabricData, GetNodesDefaultsFabricResponse, GetNodesDefaultsFabricError, GetNodesDefaultsFabricOrdererData, GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricOrdererError, GetNodesDefaultsFabricPeerData, GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricPeerError, GetNodesPlatformByPlatformData, GetNodesPlatformByPlatformResponse, GetNodesPlatformByPlatformError, DeleteNodesByIdData, DeleteNodesByIdError, GetNodesByIdData, GetNodesByIdResponse, GetNodesByIdError, PutNodesByIdData, PutNodesByIdResponse, PutNodesByIdError, PostNodesByIdCertificatesRenewData, PostNodesByIdCertificatesRenewResponse, PostNodesByIdCertificatesRenewError, GetNodesByIdChannelsData, GetNodesByIdChannelsResponse, GetNodesByIdChannelsError, GetNodesByIdEventsData, GetNodesByIdEventsResponse, GetNodesByIdEventsError, GetNodesByIdLogsData, GetNodesByIdLogsResponse, GetNodesByIdLogsError, PostNodesByIdRestartData, PostNodesByIdRestartResponse, PostNodesByIdRestartError, PostNodesByIdStartData, PostNodesByIdStartResponse, PostNodesByIdStartError, PostNodesByIdStopData, PostNodesByIdStopResponse, PostNodesByIdStopError, GetNotificationsProvidersData, GetNotificationsProvidersResponse, GetNotificationsProvidersError, PostNotificationsProvidersData, PostNotificationsProvidersResponse, PostNotificationsProvidersError, DeleteNotificationsProvidersByIdData, DeleteNotificationsProvidersByIdError, GetNotificationsProvidersByIdData, GetNotificationsProvidersByIdResponse, GetNotificationsProvidersByIdError, PutNotificationsProvidersByIdData, PutNotificationsProvidersByIdResponse, PutNotificationsProvidersByIdError, PostNotificationsProvidersByIdTestData, PostNotificationsProvidersByIdTestResponse, PostNotificationsProvidersByIdTestError, GetOrganizationsData, GetOrganizationsResponse, GetOrganizationsError, PostOrganizationsData, PostOrganizationsResponse, PostOrganizationsError, GetOrganizationsByMspidByMspidData, GetOrganizationsByMspidByMspidResponse, GetOrganizationsByMspidByMspidError, DeleteOrganizationsByIdData, DeleteOrganizationsByIdError, GetOrganizationsByIdData, GetOrganizationsByIdResponse, GetOrganizationsByIdError, PutOrganizationsByIdData, PutOrganizationsByIdResponse, PutOrganizationsByIdError } from './types.gen'; export const client = createClient(createConfig()); @@ -853,6 +853,21 @@ export const getNodesById = (options: Opti }); }; +/** + * Update a node + * Updates an existing node's configuration based on its type + */ +export const putNodesById = (options: Options) => { + return (options?.client ?? client).put({ + url: '/nodes/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * Renew node certificates * Renews the TLS and signing certificates for a Fabric node diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts index 8b71849..470dd8c 100644 --- a/web/src/api/client/types.gen.ts +++ b/web/src/api/client/types.gen.ts @@ -654,6 +654,18 @@ export type HttpUpdateBatchTimeoutPayload = { timeout: string; }; +export type HttpUpdateBesuNodeRequest = { + env?: { + [key: string]: string; + }; + networkConfig?: { + [key: string]: string; + }; + p2pPort?: number; + rpcPort?: number; + wsPort?: number; +}; + export type HttpUpdateConsenterPayload = { client_tls_cert: string; host: string; @@ -675,6 +687,44 @@ export type HttpUpdateFabricNetworkRequest = { operations: Array; }; +export type HttpUpdateFabricOrdererRequest = { + adminAddress?: string; + domainNames?: Array; + env?: { + [key: string]: string; + }; + externalEndpoint?: string; + listenAddress?: string; + operationsListenAddress?: string; +}; + +export type HttpUpdateFabricPeerRequest = { + addressOverrides?: Array; + chaincodeAddress?: string; + domainNames?: Array; + env?: { + [key: string]: string; + }; + eventsAddress?: string; + externalEndpoint?: string; + listenAddress?: string; + operationsListenAddress?: string; +}; + +export type HttpUpdateNodeRequest = { + besuNode?: HttpUpdateBesuNodeRequest; + blockchainPlatform?: TypesBlockchainPlatform; + fabricOrderer?: HttpUpdateFabricOrdererRequest; + /** + * Platform-specific configurations + */ + fabricPeer?: HttpUpdateFabricPeerRequest; + /** + * Common fields + */ + name?: string; +}; + export type HttpUpdateOrgMspPayload = { msp_id: string; root_certs: Array; @@ -883,6 +933,7 @@ export type ServiceFabricOrdererProperties = { }; export type ServiceFabricPeerProperties = { + addressOverrides?: Array; chaincodeAddress?: string; domainNames?: Array; eventsAddress?: string; @@ -904,6 +955,7 @@ export type ServiceFabricPeerProperties = { tlsCaCert?: string; tlsCert?: string; tlsKeyId?: number; + version?: string; }; export type ServiceMode = 'service' | 'docker'; @@ -969,6 +1021,12 @@ export type ServiceTransaction = { type?: string; }; +export type TypesAddressOverride = { + from?: string; + tlsCACert?: string; + to?: string; +}; + export type TypesBesuNodeConfig = { bootNodes?: Array; env?: { @@ -995,6 +1053,10 @@ export type TypesBesuNodeConfig = { export type TypesBlockchainPlatform = 'FABRIC' | 'BESU'; export type TypesFabricOrdererConfig = { + /** + * @Description Address overrides for the orderer + */ + addressOverrides?: Array; adminAddress?: string; domainNames?: Array; env?: { @@ -1024,6 +1086,10 @@ export type TypesFabricOrdererConfig = { * Configuration for creating a new Fabric peer node */ export type TypesFabricPeerConfig = { + /** + * @Description Address overrides for the peer + */ + addressOverrides?: Array; /** * @Description Chaincode listen address */ @@ -1066,6 +1132,10 @@ export type TypesFabricPeerConfig = { * @Description Operations listen address */ operationsListenAddress?: string; + /** + * @Description Orderer address overrides for the peer + */ + ordererAddressOverrides?: Array; /** * @Description Organization ID that owns this peer */ @@ -1084,6 +1154,21 @@ export type TypesNodeStatus = 'PENDING' | 'RUNNING' | 'STOPPED' | 'STOPPING' | ' export type TypesNodeType = 'FABRIC_PEER' | 'FABRIC_ORDERER' | 'BESU_FULLNODE'; +export type TypesOrdererAddressOverride = { + /** + * @Description Original orderer address + */ + from: string; + /** + * @Description TLS CA certificate in PEM format + */ + tlsCACert: string; + /** + * @Description New orderer address to use + */ + to: string; +}; + export type UrlUrl = { /** * append a query ('?') even if RawQuery is empty @@ -3667,6 +3752,47 @@ export type GetNodesByIdResponses = { export type GetNodesByIdResponse = GetNodesByIdResponses[keyof GetNodesByIdResponses]; +export type PutNodesByIdData = { + /** + * Update node request + */ + body: HttpUpdateNodeRequest; + path: { + /** + * Node ID + */ + id: number; + }; + query?: never; + url: '/nodes/{id}'; +}; + +export type PutNodesByIdErrors = { + /** + * Validation error + */ + 400: ResponseErrorResponse; + /** + * Node not found + */ + 404: ResponseErrorResponse; + /** + * Internal server error + */ + 500: ResponseErrorResponse; +}; + +export type PutNodesByIdError = PutNodesByIdErrors[keyof PutNodesByIdErrors]; + +export type PutNodesByIdResponses = { + /** + * OK + */ + 200: HttpNodeResponse; +}; + +export type PutNodesByIdResponse = PutNodesByIdResponses[keyof PutNodesByIdResponses]; + export type PostNodesByIdCertificatesRenewData = { body?: never; path: { diff --git a/web/src/components/nodes/FabricPeerConfig.tsx b/web/src/components/nodes/FabricPeerConfig.tsx index b4a65ac..f9bb4b8 100644 --- a/web/src/components/nodes/FabricPeerConfig.tsx +++ b/web/src/components/nodes/FabricPeerConfig.tsx @@ -1,14 +1,65 @@ import { ServiceFabricPeerProperties } from '@/api/client' import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Separator } from '@/components/ui/separator' +import { CertificateViewer } from '@/components/ui/certificate-viewer' import { Link } from 'react-router-dom' +import { useState } from 'react' +import { Eye } from 'lucide-react' interface FabricPeerConfigProps { config: ServiceFabricPeerProperties } +interface AddressOverrideModalProps { + open: boolean + onOpenChange: (open: boolean) => void + override: { + from: string + to: string + tlsCACert: string + } +} + +function AddressOverrideModal({ open, onOpenChange, override }: AddressOverrideModalProps) { + return ( + + + + Address Override Details + Certificate and routing information + +
+
+
+

From Address

+

{override.from}

+
+
+

To Address

+

{override.to}

+
+
+ +
+

TLS CA Certificate

+ +
+
+
+
+ ) +} + export function FabricPeerConfig({ config }: FabricPeerConfigProps) { + const [selectedOverride, setSelectedOverride] = useState<{ + from: string + to: string + tlsCACert: string + } | null>(null) + return ( @@ -88,7 +139,40 @@ export function FabricPeerConfig({ config }: FabricPeerConfigProps) {
)} + + {config.addressOverrides && config.addressOverrides.length > 0 && ( + <> + +
+

Address Overrides

+
+ {config.addressOverrides.map((override, index) => ( +
+
+

{override.from} → {override.to}

+
+ +
+ ))} +
+
+ + )} + + {selectedOverride && ( + !open && setSelectedOverride(null)} + override={selectedOverride} + /> + )} ) } diff --git a/web/src/components/nodes/fabric-node-form.tsx b/web/src/components/nodes/fabric-node-form.tsx index c593adc..fc7d08c 100644 --- a/web/src/components/nodes/fabric-node-form.tsx +++ b/web/src/components/nodes/fabric-node-form.tsx @@ -1,36 +1,39 @@ -import { GetNodesDefaultsFabricPeerResponse, GetNodesDefaultsFabricOrdererResponse } from '@/api/client' +import { GetNodesDefaultsFabricOrdererResponse, GetNodesDefaultsFabricPeerResponse } from '@/api/client' import { Button } from '@/components/ui/button' -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from '@/components/ui/form' +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' import { zodResolver } from '@hookform/resolvers/zod' -import { useEffect, useMemo } from 'react' +import { Trash2 } from 'lucide-react' +import { useEffect } from 'react' import { useForm, useWatch } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' const fabricNodeFormSchema = z.object({ - name: z.string().min(2, 'Name must be at least 2 characters'), + name: z.string().min(1, 'Name is required'), fabricProperties: z.object({ nodeType: z.enum(['FABRIC_PEER', 'FABRIC_ORDERER']), - mode: z.enum(['docker', 'service'], { - required_error: 'Mode is required', - invalid_type_error: 'Mode must be either docker or service', - }), - version: z.enum(['2.5.12', '3.0.0'], { - required_error: 'Version is required', - invalid_type_error: 'Invalid version selected', - }), - organizationId: z.number().min(1, 'Organization is required'), - listenAddress: z.string().min(1, 'Listen address is required'), - operationsListenAddress: z.string().min(1, 'Operations listen address is required'), - externalEndpoint: z.string().optional(), + mode: z.enum(['docker', 'service']), + version: z.string(), + organizationId: z.number().optional(), + listenAddress: z.string(), + operationsListenAddress: z.string(), + externalEndpoint: z.string(), domains: z.array(z.string()).optional(), - // Peer-specific fields chaincodeAddress: z.string().optional(), eventsAddress: z.string().optional(), - // Orderer-specific fields adminAddress: z.string().optional(), + addressOverrides: z + .array( + z.object({ + from: z.string(), + to: z.string(), + tlsCACert: z.string(), + }) + ) + .optional(), }), }) @@ -63,13 +66,14 @@ export function FabricNodeForm({ onSubmit, isSubmitting, organizations, defaults operationsListenAddress: defaults?.operationsListenAddress || '', externalEndpoint: defaults?.externalEndpoint || '', domains: [], + addressOverrides: [], }, }, }) const values = useWatch({ control: form.control }) useEffect(() => { - onChange?.(values) + onChange?.(values as FabricNodeFormValues) }, [values]) const nodeType = form.watch('fabricProperties.nodeType') @@ -305,6 +309,89 @@ export function FabricNodeForm({ onSubmit, isSubmitting, organizations, defaults
+
+

Address Overrides

+ ( + + Address Overrides + Configure address overrides for the node +
+ {field.value?.map((override, index) => ( +
+
+ + { + const newOverrides = [...(field.value || [])] + newOverrides[index] = { ...override, from: e.target.value } + field.onChange(newOverrides) + }} + /> + +
+
+ + { + const newOverrides = [...(field.value || [])] + newOverrides[index] = { ...override, to: e.target.value } + field.onChange(newOverrides) + }} + /> + +
+
+ +