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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions api/types/orderer_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package types

import (
"encoding/json"
"errors"
"fmt"
"net"
"slices"
"strconv"
"strings"

"gopkg.in/yaml.v3"
)

type (
// OrdererEndpoint defines an orderer party's endpoint.
OrdererEndpoint struct {
Host string `mapstructure:"host" json:"host,omitempty" yaml:"host,omitempty"`
Port int `mapstructure:"port" json:"port,omitempty" yaml:"port,omitempty"`
// ID is the party ID.
ID uint32 `mapstructure:"id" json:"id,omitempty" yaml:"id,omitempty"`
MspID string `mapstructure:"msp-id" json:"msp-id,omitempty" yaml:"msp-id,omitempty"`
// API should be broadcast and/or deliver. Empty value means all API is supported.
API []string `mapstructure:"api" json:"api,omitempty" yaml:"api,omitempty"`
}
)

const (
// Broadcast support by endpoint.
Broadcast = "broadcast"
// Deliver support by endpoint.
Deliver = "deliver"
// NoID indicates that a party ID is not specified (default).
// This allows backward compatibility with orderers that doesn't support this syntax.
NoID = uint32(0x100000)
)

// ErrInvalidEndpoint orderer endpoints error.
var ErrInvalidEndpoint = errors.New("invalid endpoint")

// Address returns a string representation of the endpoint's address.
func (e *OrdererEndpoint) Address() string {
return net.JoinHostPort(e.Host, strconv.Itoa(e.Port))
}

// String returns a deterministic representation of the endpoint.
func (e *OrdererEndpoint) String() string {
var output strings.Builder
isFirst := true
if e.ID < NoID {
output.WriteString("id=")
output.WriteString(strconv.FormatUint(uint64(e.ID), 10))
isFirst = false
}
if len(e.MspID) > 0 {
if !isFirst {
output.WriteRune(',')
}
output.WriteString("msp-id=")
output.WriteString(e.MspID)
isFirst = false
}
for _, api := range e.API {
if !isFirst {
output.WriteRune(',')
}
output.WriteString(api)
isFirst = false
}
if len(e.Host) > 0 || e.Port > 0 {
if !isFirst {
output.WriteRune(',')
}
output.WriteString(e.Address())
}
return output.String()
}

// SupportsAPI returns true if this endpoint supports API.
// It also returns true if no APIs are specified, as we cannot know.
func (e *OrdererEndpoint) SupportsAPI(api string) bool {
return len(e.API) == 0 || slices.Contains(e.API, api)
}

// ParseOrdererEndpoint parses a string according to the following schema order (the first that succeeds).
// Schema 1: YAML.
// Schema 2: JSON.
// Schema 3: [id=ID,][msp-id=MspID,][broadcast,][deliver,][host=Host,][port=Port,][Host:Port].
Comment on lines +92 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about supporting multiple schema formats, as too many choices are not always good. It would be better to support only the YAML schema within the YAML config file. Further, it would reduce the number of lines of code that we need to manage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to support at least two schemas. Scehma 1 (YAML, to the request of @pasquale95), and schema 3 to support regular endpoint for legacy.

func ParseOrdererEndpoint(valueRaw string) (*OrdererEndpoint, error) {
ret := &OrdererEndpoint{ID: NoID}
if len(valueRaw) == 0 {
return ret, nil
}
if err := yaml.Unmarshal([]byte(valueRaw), ret); err == nil {
return ret, nil
}
if err := json.Unmarshal([]byte(valueRaw), ret); err == nil {
return ret, nil
}
err := unmarshalOrdererEndpoint(valueRaw, ret)
return ret, err
}

func unmarshalOrdererEndpoint(valueRaw string, out *OrdererEndpoint) error {
metaParts := strings.Split(valueRaw, ",")
for _, item := range metaParts {
item = strings.TrimSpace(item)
equalIdx := strings.Index(item, "=")
colonIdx := strings.Index(item, ":")
var err error
switch {
case item == Broadcast || item == Deliver:
out.API = append(out.API, item)
case equalIdx >= 0:
key, value := strings.TrimSpace(item[:equalIdx]), strings.TrimSpace(item[equalIdx+1:])
switch key {
case "msp-id":
out.MspID = value
case "host":
out.Host = value
case "id":
err = out.setID(value)
case "port":
err = out.setPort(value)
default:
return fmt.Errorf("invalid key '%s' for item '%s': %w", key, item, ErrInvalidEndpoint)
}
case colonIdx >= 0:
var port string
out.Host, port, err = net.SplitHostPort(strings.TrimSpace(item))
if err != nil {
return fmt.Errorf("invalid host/port '%s': %w", item, err)
}
err = out.setPort(strings.TrimSpace(port))
default:
return fmt.Errorf("invalid item '%s': %w", item, ErrInvalidEndpoint)
}
if err != nil {
return err
}
}
return nil
}

func (e *OrdererEndpoint) setPort(portStr string) error {
port, err := strconv.ParseInt(portStr, 10, 32)
if err != nil {
return fmt.Errorf("failed to parse port: %w", err)
}
e.Port = int(port)
return nil
}

func (e *OrdererEndpoint) setID(idStr string) error {
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
return fmt.Errorf("invalid id value: %w", err)
}
e.ID = uint32(id)
return nil
}
70 changes: 70 additions & 0 deletions api/types/orderer_endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package types

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func TestReadWrite(t *testing.T) {
t.Parallel()
valSchema := "id=5,msp-id=org,broadcast,deliver,localhost:5050"
valJSON := `{"id":5,"msp-id":"org","api":["broadcast","deliver"],"host":"localhost","port":5050}`
valYAML := `
id: 5
msp-id: org
api:
- broadcast
- deliver
host: localhost
port: 5050
`
expected := &OrdererEndpoint{
ID: 5,
MspID: "org",
API: []string{"broadcast", "deliver"},
Host: "localhost",
Port: 5050,
}
require.Equal(t, "localhost:5050", expected.Address())
require.Equal(t, valSchema, expected.String())

valJSONRaw, err := json.Marshal(expected)
require.NoError(t, err)
require.JSONEq(t, valJSON, string(valJSONRaw))

valYamlRaw, err := yaml.Marshal(expected)
require.NoError(t, err)
require.YAMLEq(t, valYAML, string(valYamlRaw))

e, err := ParseOrdererEndpoint(valSchema)
require.NoError(t, err)
require.Equal(t, expected, e)

e, err = ParseOrdererEndpoint(valJSON)
require.NoError(t, err)
require.Equal(t, expected, e)

e, err = ParseOrdererEndpoint(valYAML)
require.NoError(t, err)
require.Equal(t, expected, e)

valJSONNoID := `{"msp-id":"org","api":["broadcast","deliver"],"host":"localhost","port":5050}`
e, err = ParseOrdererEndpoint(valJSONNoID)
require.NoError(t, err)
require.Equal(t, &OrdererEndpoint{
ID: NoID,
MspID: "org",
API: []string{"broadcast", "deliver"},
Host: "localhost",
Port: 5050,
}, e)
}
2 changes: 1 addition & 1 deletion cmd/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"os"

"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"

"github.com/hyperledger/fabric-x-common/cmd/common/comm"
"github.com/hyperledger/fabric-x-common/cmd/common/signer"
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptogen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/hyperledger/fabric-x-common/internaltools/cryptogen/msp"

"github.com/alecthomas/kingpin/v2"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion cmd/cryptogen/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"testing"

"github.com/stretchr/testify/require"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

func TestDefaultConfigParsing(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion common/channelconfig/realconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hyperledger/fabric-protos-go-apiv2/common"
"github.com/stretchr/testify/require"

"github.com/hyperledger/fabric-x-common/api/types"
"github.com/hyperledger/fabric-x-common/common/channelconfig"
"github.com/hyperledger/fabric-x-common/core/config/configtest"
"github.com/hyperledger/fabric-x-common/internaltools/configtxgen/encoder"
Expand Down Expand Up @@ -57,7 +58,7 @@ func TestOrgSpecificOrdererEndpoints(t *testing.T) {
require.Nil(t, cg)
require.EqualError(t, err, "could not create orderer group: failed to create orderer org: orderer endpoints for organization SampleOrg are missing and must be configured when capability V3_0 is enabled")

conf.Orderer.Organizations[0].OrdererEndpoints = []string{"127.0.0.1:7050"}
conf.Orderer.Organizations[0].OrdererEndpoints = []*types.OrdererEndpoint{{Host: "127.0.0.1", Port: 7050}}
cg, err = encoder.NewChannelGroup(conf)
require.NoError(t, err)

Expand Down
6 changes: 5 additions & 1 deletion common/configtx/test/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ func MakeGenesisBlockFromMSPs(channelID string, appMSPConf, ordererMSPConf *mspp
ModPolicy: channelconfig.AdminsPolicyKey,
}

endpoints := profile.Orderer.Organizations[0].OrdererEndpoints
ordererOrgProtos := &cb.OrdererAddresses{
Addresses: profile.Orderer.Organizations[0].OrdererEndpoints,
Addresses: make([]string, len(endpoints)),
}
for i, e := range endpoints {
ordererOrgProtos.Addresses[i] = e.String()
}
ordererOrg.Values[channelconfig.EndpointsKey] = &cb.ConfigValue{
Value: protoutil.MarshalOrPanic(ordererOrgProtos),
Expand Down
Loading