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
34 changes: 20 additions & 14 deletions config/generate/config_block_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"

"github.com/hyperledger/fabric-protos-go-apiv2/common"
"github.com/hyperledger/fabric-x-common/api/types"
"github.com/hyperledger/fabric-x-common/tools/configtxgen"
"github.com/hyperledger/fabric-x-orderer/config"
"github.com/pkg/errors"
Expand Down Expand Up @@ -60,7 +61,7 @@ func CreateProfile(dir string, sharedConfigYaml *config.SharedConfigYaml, shared
Host: party.ConsenterConfig.Host,
Port: party.ConsenterConfig.Port,
MSPID: "",
Identity: party.ConsenterConfig.TLSCert,
Identity: party.ConsenterConfig.SignCert,
ClientTLSCert: party.ConsenterConfig.TLSCert,
ServerTLSCert: party.ConsenterConfig.TLSCert,
}
Expand All @@ -76,17 +77,16 @@ func CreateProfile(dir string, sharedConfigYaml *config.SharedConfigYaml, shared
templateAppOrg := profile.Application.Organizations[0]
profile.Application.Organizations = make([]*configtxgen.Organization, len(sharedConfigYaml.PartiesConfig))

for i := 0; i < len(sharedConfigYaml.PartiesConfig); i++ {
for i := range sharedConfigYaml.PartiesConfig {
org := &configtxgen.Organization{
Name: fmt.Sprintf("org%d", i+1),
ID: fmt.Sprintf("org%d", i+1),
MSPDir: filepath.Join(dir, "crypto", "ordererOrganizations", fmt.Sprintf("org%d", i+1), "msp"),
MSPType: templateAppOrg.MSPType,
Policies: make(map[string]*configtxgen.Policy),
AnchorPeers: templateAppOrg.AnchorPeers,
OrdererEndpoints: templateAppOrg.OrdererEndpoints, // TODO: org.OrdererEndpoints in the new format
AdminPrincipal: templateAppOrg.AdminPrincipal,
SkipAsForeign: templateAppOrg.SkipAsForeign,
Name: fmt.Sprintf("org%d", i+1),
ID: fmt.Sprintf("org%d", i+1),
MSPDir: filepath.Join(dir, "crypto", "ordererOrganizations", fmt.Sprintf("org%d", i+1), "msp"),
MSPType: templateAppOrg.MSPType,
Policies: make(map[string]*configtxgen.Policy),
AnchorPeers: templateAppOrg.AnchorPeers,
AdminPrincipal: templateAppOrg.AdminPrincipal,
SkipAsForeign: templateAppOrg.SkipAsForeign,
}

// Update policy rules to use correct org names
Expand All @@ -104,15 +104,14 @@ func CreateProfile(dir string, sharedConfigYaml *config.SharedConfigYaml, shared
templateOrdererOrg := profile.Orderer.Organizations[0]
profile.Orderer.Organizations = make([]*configtxgen.Organization, len(sharedConfigYaml.PartiesConfig))

for i := 0; i < len(sharedConfigYaml.PartiesConfig); i++ {
for i, p := range sharedConfigYaml.PartiesConfig {
org := &configtxgen.Organization{
Name: fmt.Sprintf("org%d", i+1),
ID: fmt.Sprintf("org%d", i+1),
MSPDir: filepath.Join(dir, "crypto", "ordererOrganizations", fmt.Sprintf("org%d", i+1), "msp"),
MSPType: templateOrdererOrg.MSPType,
Policies: make(map[string]*configtxgen.Policy),
AnchorPeers: templateOrdererOrg.AnchorPeers,
OrdererEndpoints: templateOrdererOrg.OrdererEndpoints, // TODO: org.OrdererEndpoints in the new format
OrdererEndpoints: buildOrdererEndpoints(uint32(p.PartyID), p.RouterConfig.Host, int(p.RouterConfig.Port), p.AssemblerConfig.Host, int(p.AssemblerConfig.Port)),
AdminPrincipal: templateOrdererOrg.AdminPrincipal,
SkipAsForeign: templateOrdererOrg.SkipAsForeign,
}
Expand Down Expand Up @@ -163,3 +162,10 @@ func generatePublicKey(path string) ([]byte, error) {

return publicKeyPEM, nil
}

func buildOrdererEndpoints(id uint32, routerHost string, routerPort int, assemblerHost string, assemblerPort int) []*types.OrdererEndpoint {
return []*types.OrdererEndpoint{
{Host: routerHost, Port: routerPort, ID: id, API: []string{types.Broadcast}},
{Host: assemblerHost, Port: assemblerPort, ID: id, API: []string{types.Deliver}},
}
}
104 changes: 99 additions & 5 deletions config/verify/orderer_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ SPDX-License-Identifier: Apache-2.0
package verify

import (
"slices"
"time"

smartbft_types "github.com/hyperledger-labs/SmartBFT/pkg/types"
"github.com/hyperledger/fabric-lib-go/bccsp"
"github.com/hyperledger/fabric-protos-go-apiv2/common"
"github.com/hyperledger/fabric-x-common/api/types"
"github.com/hyperledger/fabric-x-common/common/channelconfig"
"github.com/hyperledger/fabric-x-orderer/common/types"
arma_types "github.com/hyperledger/fabric-x-orderer/common/types"
config_protos "github.com/hyperledger/fabric-x-orderer/config/protos"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
)

//go:generate counterfeiter -o mocks/orderer_rules.go . OrdererRules
type OrdererRules interface {
ValidateNewConfig(envelope *common.Envelope, bccsp bccsp.BCCSP, partyID types.PartyID) error
ValidateNewConfig(envelope *common.Envelope, bccsp bccsp.BCCSP, partyID arma_types.PartyID) error
ValidateTransition(current channelconfig.Resources, next *common.Envelope, bccsp bccsp.BCCSP) error
}

Expand All @@ -36,13 +38,14 @@ type DefaultOrdererRules struct{}
// 3. SmartBFTConfig.RequestMaxBytes must be positive and >= SharedConfig.BatchingConfig.RequestMaxBytes.
// This ensures config requests accepted by the router are not rejected by SmartBFT.
// 4. SmartBFTConfig must pass SmartBFT validation.
// 5. OrdererEndpoints for each organization must be defined, non-empty,
// and include both "broadcast" and "deliver" roles.
// 6. ConsenterMapping must be consistent with the consenters defined in the shared config.
//
// TODO: Validate OrdererEndpoints in the organization definitions.
// TODO: Validate that ca certificates in the sharedConfig are the same as in the ordererOrganization ca certificates.
// TODO: Validate new certificates - chain of trust, expiration, etc.
// TODO: Validate ConsenterMapping.
// TODO: Validate BlockValidationPolicy.
func (or *DefaultOrdererRules) ValidateNewConfig(envelope *common.Envelope, bccsp bccsp.BCCSP, partyID types.PartyID) error {
func (or *DefaultOrdererRules) ValidateNewConfig(envelope *common.Envelope, bccsp bccsp.BCCSP, partyID arma_types.PartyID) error {
bundle, err := channelconfig.NewBundleFromEnvelope(envelope, bccsp)
if err != nil {
return errors.Wrap(err, "failed to create bundle from new envelope config")
Expand Down Expand Up @@ -92,6 +95,18 @@ func (or *DefaultOrdererRules) ValidateNewConfig(envelope *common.Envelope, bccs
return errors.Wrap(err, "smartbft config validation failed")
}

// 5.
for _, org := range ordererConfig.Organizations() {
if err := validateOrdererOrgEndpoints(org.Endpoints()); err != nil {
return errors.Wrapf(err, "invalid endpoints for orderer organization %s", org.Name())
}
}

// 6.
if err := validateConsenterConsistency(ordererConfig.Consenters(), sharedConfig.PartiesConfig); err != nil {
return errors.Wrap(err, "consenter mapping is inconsistent with shared config parties")
}

return nil
}

Expand Down Expand Up @@ -289,3 +304,82 @@ func validateSmartBFTConfig(id uint64, cfg *config_protos.SmartBFTConfig) error

return nil
}

func validateOrdererOrgEndpoints(endpoints []string) error {
if len(endpoints) == 0 {
return errors.New("endpoints are empty")
}

hasBroadcast := false
hasDeliver := false
for _, raw := range endpoints {
ep, err := types.ParseOrdererEndpoint(raw)
if err != nil {
return err
}
if slices.Contains(ep.API, types.Broadcast) {
hasBroadcast = true
}
if slices.Contains(ep.API, types.Deliver) {
hasDeliver = true
}
}

if !hasBroadcast {
return errors.New("missing broadcast endpoint")
}
if !hasDeliver {
return errors.New("missing deliver endpoint")
}

return nil
}

func validateConsenterConsistency(consenters []*common.Consenter, parties []*config_protos.PartyConfig) error {
if len(consenters) != len(parties) {
return errors.Errorf("number of parties in Orderer consenters mapping (%d) does not match number of parties in Shared config (%d)", len(consenters), len(parties))
}

partiesMap := make(map[uint32]*config_protos.PartyConfig)
for _, p := range parties {
if p == nil {
return errors.New("party config is nil in shared config")
}
partiesMap[p.PartyID] = p
}

for _, consenter := range consenters {
if consenter == nil {
return errors.New("consenter config is nil in shared config")
}
party, exists := partiesMap[consenter.Id]
if !exists {
return errors.Errorf("party ID %d missing from shared config", consenter.Id)
}
if party.ConsenterConfig == nil {
return errors.Errorf("consenter config missing in shared config for party %d", consenter.Id)
}
nodeCfg := party.ConsenterConfig
if consenter.Host != nodeCfg.Host {
return errors.Errorf("host mismatch for party %d: %s != %s", consenter.Id, consenter.Host, nodeCfg.Host)
}

if consenter.Port != nodeCfg.Port {
return errors.Errorf("port mismatch for party %d: %d != %d", consenter.Id, consenter.Port, nodeCfg.Port)
}

if !slices.Equal(consenter.Identity, nodeCfg.SignCert) {
return errors.Errorf("identity/sign_cert mismatch for party %d", consenter.Id)
}

if len(consenter.ServerTlsCert) > 0 && !slices.Equal(consenter.ServerTlsCert, nodeCfg.TlsCert) {
return errors.Errorf("server TLS certificate mismatch for party %d", consenter.Id)
}

if len(consenter.ClientTlsCert) > 0 && !slices.Equal(consenter.ClientTlsCert, nodeCfg.TlsCert) {
return errors.Errorf("client TLS certificate mismatch for party %d", consenter.Id)
}
}

return nil
}
91 changes: 91 additions & 0 deletions config/verify/orderer_rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package verify_test
import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/hyperledger/fabric-lib-go/bccsp/factory"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/hyperledger/fabric-x-orderer/testutil/configutil"
"github.com/hyperledger/fabric/protoutil"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)

func TestValidateNewConfig(t *testing.T) {
Expand Down Expand Up @@ -127,6 +129,95 @@ func TestValidateNewConfig_InvalidRequestMaxBytes(t *testing.T) {
require.Contains(t, err.Error(), "smartbft RequestMaxBytes must be equal or greater than BatchingConfig RequestMaxBytes")
}

func TestValidateNewConfig_InvalidOrdererEndpoint(t *testing.T) {
_, env, _, _, _, _, _, cleanup := setupOrdererRulesTest(t, 1)
defer cleanup()

payload, err := protoutil.UnmarshalPayload(env.Payload)
require.NoError(t, err)

cfgEnv := &common.ConfigEnvelope{}
require.NoError(t, proto.Unmarshal(payload.Data, cfgEnv))

endpointsVal := cfgEnv.Config.ChannelGroup.Groups["Orderer"].Groups["org1"].Values["Endpoints"]

oa := &common.OrdererAddresses{}
require.NoError(t, proto.Unmarshal(endpointsVal.Value, oa))

// remove broadcast
var addresses []string
for _, a := range oa.Addresses {
if !strings.Contains(a, ",broadcast,") {
addresses = append(addresses, a)
}
}
oa.Addresses = addresses
endpointsVal.Value, err = proto.Marshal(oa)
require.NoError(t, err)

payload.Data, err = proto.Marshal(cfgEnv)
require.NoError(t, err)

env.Payload, err = proto.Marshal(payload)
require.NoError(t, err)

or := verify.DefaultOrdererRules{}
err = or.ValidateNewConfig(env, factory.GetDefault(), types.PartyID(1))
require.Error(t, err)
require.Contains(t, err.Error(), "missing broadcast endpoint")
}

func TestValidateNewConfig_ConsenterConsistency(t *testing.T) {
dir, _, currBundle, builder, proposer, signer, verifier, cleanup := setupOrdererRulesTest(t, 1)
defer cleanup()

// create a valid config update first
cert := []byte("fake1-tls-cert")
updatePb := builder.UpdateConsensusTLSCert(t, types.PartyID(1), cert)

updateEnv := configutil.CreateConfigTX(t, dir, []types.PartyID{1}, 1, updatePb)
req := &comm.Request{Payload: updateEnv.Payload, Signature: updateEnv.Signature}

nextCfgEnv, err := proposer.ProposeConfigUpdate(req, currBundle, signer, verifier)
require.NoError(t, err)

env := &common.Envelope{
Payload: nextCfgEnv.Payload,
Signature: nextCfgEnv.Signature,
}

// change TLSCert in consenter_mapping to a different value
payload := &common.Payload{}
err = proto.Unmarshal(env.Payload, payload)
require.NoError(t, err)

cfgEnv := &common.ConfigEnvelope{}
require.NoError(t, proto.Unmarshal(payload.Data, cfgEnv))

orderersVal := cfgEnv.Config.ChannelGroup.Groups["Orderer"].Values["Orderers"]
orderers := &common.Orderers{}
require.NoError(t, proto.Unmarshal(orderersVal.Value, orderers))

// change the TLS cert in consenter_mapping to create mismatch
orderers.ConsenterMapping[0].ServerTlsCert = []byte("fake2-tls-cert")
orderers.ConsenterMapping[0].ClientTlsCert = []byte("fake2-tls-cert")

orderersVal.Value, err = proto.Marshal(orderers)
require.NoError(t, err)

payload.Data, err = proto.Marshal(cfgEnv)
require.NoError(t, err)

env.Payload, err = proto.Marshal(payload)
require.NoError(t, err)

or := verify.DefaultOrdererRules{}
err = or.ValidateNewConfig(env, factory.GetDefault(), types.PartyID(1))

require.Error(t, err)
require.Contains(t, err.Error(), "TLS certificate mismatch for party 1")
}

func TestValidateTransition_RemoveAndAddSameParty(t *testing.T) {
or := verify.DefaultOrdererRules{}
bccsp := factory.GetDefault()
Expand Down
Loading
Loading