Skip to content

Commit 176bf35

Browse files
authored
[cryptogen] Support config block re-generation (#81)
#### Type of change - Improvement (improvement to code, performance, etc) #### Description Enables incremental updates to blockchain configuration blocks without regenerating existing crypto material. Key Changes: - Replace Generate() with Extend() for crypto material to preserve existing certificates - Standardize on *types.OrdererEndpoint instead of custom endpoint type - Add robust duplicate party ID validation with specific error messages - Improve bounds checking in consenter creation with proper error handling - Rename Node.Party to Node.PartyName with clearer semantics Benefits: - Supports adding/removing organizations without invalidating existing credentials - Maintains backward compatibility while improving error reporting Testing: - Comprehensive test coverage for organization addition/removal scenarios - Validates policy signatures work correctly across configuration changes - Ensures existing crypto material remains valid after updates This change is essential for testing where configuration updates must preserve existing organizational credentials. --------- Signed-off-by: Liran Funaro <liran.funaro@gmail.com>
1 parent d03614c commit 176bf35

File tree

3 files changed

+294
-195
lines changed

3 files changed

+294
-195
lines changed

tools/cryptogen/config_block.go

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ SPDX-License-Identifier: Apache-2.0
77
package cryptogen
88

99
import (
10-
"net"
10+
"fmt"
11+
"maps"
1112
"os"
1213
"path"
13-
"strconv"
14+
"slices"
1415
"strings"
1516

1617
"github.com/cockroachdb/errors"
@@ -36,7 +37,7 @@ type ConfigBlockParameters struct {
3637
type OrganizationParameters struct {
3738
Name string
3839
Domain string
39-
OrdererEndpoints []OrdererEndpoint
40+
OrdererEndpoints []*types.OrdererEndpoint
4041
ConsenterNodes []Node
4142
OrdererNodes []Node
4243
PeerNodes []Node
@@ -46,14 +47,19 @@ type OrganizationParameters struct {
4647
type Node struct {
4748
CommonName string
4849
Hostname string
49-
Party string
5050
SANS []string
51-
}
52-
53-
// OrdererEndpoint address should be in the format of <host>:<port>, not the full [types.OrdererEndpoint] format.
54-
type OrdererEndpoint struct {
55-
Address string
56-
API []string
51+
// Fabric-X supports multiple parties per organizations.
52+
// Thus, in such case, we can create multiple Orderer's nodes
53+
// for each organization.
54+
// We organize them such that each party's nodes will be under
55+
// a dedicated party folder.
56+
// This folder name is inffered from PartyName, if given.
57+
// Otherwise, a default name will be used.
58+
// If it is not set, and we have only one party for the organization,
59+
// the folder structure will collapse one step down.
60+
// If it is not set, and we have multiple parties for the organization,
61+
// The party assigned named will be party-<party-ID>.
62+
PartyName string
5763
}
5864

5965
// file names.
@@ -84,19 +90,14 @@ func LoadSampleConfig(profile string) (*configtxgen.Profile, error) {
8490
return result, nil
8591
}
8692

87-
// CreateDefaultConfigBlockWithCrypto creates a config block with default values and a crypto material.
93+
// CreateOrExtendConfigBlockWithCrypto creates a config block with default values and a crypto material.
8894
// It uses the first orderer organization as a template and creates the given organizations.
8995
// It uses the same organizations for the orderer and the application.
90-
func CreateDefaultConfigBlockWithCrypto(conf ConfigBlockParameters) (*common.Block, error) {
91-
if conf.BaseProfile == "" {
92-
conf.BaseProfile = configtxgen.SampleFabricX
93-
}
94-
if conf.ChannelID == "" {
95-
conf.ChannelID = "chan"
96-
}
97-
profile, err := LoadSampleConfig(conf.BaseProfile)
98-
if err != nil {
99-
return nil, err
96+
func CreateOrExtendConfigBlockWithCrypto(conf ConfigBlockParameters) (*common.Block, error) {
97+
initConfigDefault(&conf)
98+
profile, loadErr := LoadSampleConfig(conf.BaseProfile)
99+
if loadErr != nil {
100+
return nil, loadErr
100101
}
101102

102103
if len(profile.Orderer.Organizations) < 1 {
@@ -110,17 +111,23 @@ func CreateDefaultConfigBlockWithCrypto(conf ConfigBlockParameters) (*common.Blo
110111
profile.Orderer.Organizations = make([]*configtxgen.Organization, 0, len(conf.Organizations))
111112
profile.Application.Organizations = make([]*configtxgen.Organization, 0, len(conf.Organizations))
112113
cryptoConf := &Config{}
113-
for i, o := range conf.Organizations {
114-
spec := createOrgSpec(&o)
115114

116-
id := uint32(i) //nolint:gosec // int -> uint32.
117-
org, orgErr := createOrg(id, sourceOrg, &o)
118-
if orgErr != nil {
119-
return nil, orgErr
115+
allOrdererIDs := make(map[uint32]any)
116+
for _, o := range conf.Organizations {
117+
org, orgOrdererIDs := createOrg(sourceOrg, &o)
118+
for _, id := range orgOrdererIDs {
119+
if _, ok := allOrdererIDs[id]; ok {
120+
return nil, errors.Errorf("duplicate party id [%d] found in org %s", id, o.Name)
121+
}
122+
allOrdererIDs[id] = nil
120123
}
124+
allConsenters, err := createConsenter(&o, orgOrdererIDs)
125+
if err != nil {
126+
return nil, err
127+
}
128+
profile.Orderer.ConsenterMapping = append(profile.Orderer.ConsenterMapping, allConsenters...)
121129

122-
profile.Orderer.ConsenterMapping = append(profile.Orderer.ConsenterMapping, createConsenter(id, &o)...)
123-
130+
spec := createOrgSpec(&o)
124131
switch orgOU(&o) {
125132
case PeerOU:
126133
profile.Application.Organizations = append(profile.Application.Organizations, org)
@@ -135,13 +142,13 @@ func CreateDefaultConfigBlockWithCrypto(conf ConfigBlockParameters) (*common.Blo
135142
}
136143
}
137144

138-
err = os.WriteFile(path.Join(conf.TargetPath, armaDataFile), conf.ArmaMetaBytes, 0o644)
145+
err := os.WriteFile(path.Join(conf.TargetPath, armaDataFile), conf.ArmaMetaBytes, 0o644)
139146
if err != nil {
140147
return nil, errors.Wrap(err, "failed to write ARMA data file")
141148
}
142149
profile.Orderer.Arma.Path = armaDataFile
143150

144-
err = Generate(conf.TargetPath, cryptoConf)
151+
err = Extend(conf.TargetPath, cryptoConf)
145152
if err != nil {
146153
return nil, err
147154
}
@@ -156,6 +163,15 @@ func CreateDefaultConfigBlockWithCrypto(conf ConfigBlockParameters) (*common.Blo
156163
return block, errors.Wrap(err, "failed to write block")
157164
}
158165

166+
func initConfigDefault(conf *ConfigBlockParameters) {
167+
if conf.BaseProfile == "" {
168+
conf.BaseProfile = configtxgen.SampleFabricX
169+
}
170+
if conf.ChannelID == "" {
171+
conf.ChannelID = "chan"
172+
}
173+
}
174+
159175
func orgOU(o *OrganizationParameters) string {
160176
ordererNodeCount := len(o.ConsenterNodes) + len(o.OrdererNodes)
161177
peerNodeCount := len(o.PeerNodes)
@@ -178,7 +194,7 @@ func createOrgSpec(o *OrganizationParameters) OrgSpec {
178194
CommonName: n.CommonName,
179195
Hostname: n.Hostname,
180196
SANS: n.SANS,
181-
Party: n.Party,
197+
Party: n.PartyName,
182198
OrganizationalUnit: OrdererOU,
183199
})
184200
}
@@ -187,7 +203,7 @@ func createOrgSpec(o *OrganizationParameters) OrgSpec {
187203
CommonName: n.CommonName,
188204
Hostname: n.Hostname,
189205
SANS: n.SANS,
190-
Party: n.Party,
206+
Party: n.PartyName,
191207
OrganizationalUnit: OrdererOU,
192208
})
193209
}
@@ -196,7 +212,7 @@ func createOrgSpec(o *OrganizationParameters) OrgSpec {
196212
CommonName: n.CommonName,
197213
Hostname: n.Hostname,
198214
SANS: n.SANS,
199-
Party: n.Party,
215+
Party: n.PartyName,
200216
OrganizationalUnit: PeerOU,
201217
})
202218
}
@@ -218,19 +234,17 @@ func createOrgSpec(o *OrganizationParameters) OrgSpec {
218234
}
219235

220236
func createOrg(
221-
id uint32, sourceOrg configtxgen.Organization, o *OrganizationParameters,
222-
) (*configtxgen.Organization, error) {
237+
sourceOrg configtxgen.Organization, o *OrganizationParameters,
238+
) (*configtxgen.Organization, []uint32) {
223239
org := sourceOrg
224240
org.ID = o.Name
225241
org.Name = o.Name
226242
org.MSPDir = path.Join(getOrgPath(o), MSPDir)
227-
org.OrdererEndpoints = make([]*types.OrdererEndpoint, len(o.OrdererEndpoints))
228-
for epIdx, ep := range o.OrdererEndpoints {
229-
var err error
230-
org.OrdererEndpoints[epIdx], err = newOrdererEndpoint(id, ep.Address, o.Name, ep.API)
231-
if err != nil {
232-
return nil, errors.Wrapf(err, "invalid %+v", ep)
233-
}
243+
org.OrdererEndpoints = o.OrdererEndpoints
244+
allOrdererIDsMap := make(map[uint32]any)
245+
for _, ep := range org.OrdererEndpoints {
246+
ep.MspID = o.Name
247+
allOrdererIDsMap[ep.ID] = nil
234248
}
235249
org.Policies = make(map[string]*configtxgen.Policy)
236250
for k, p := range sourceOrg.Policies {
@@ -239,14 +253,23 @@ func createOrg(
239253
Rule: strings.ReplaceAll(p.Rule, sourceOrg.Name, o.Name),
240254
}
241255
}
242-
return &org, nil
256+
allOrdererIDs := slices.Collect(maps.Keys(allOrdererIDsMap))
257+
// We sort the IDs for deterministic output.
258+
slices.Sort(allOrdererIDs)
259+
return &org, allOrdererIDs
243260
}
244261

245-
func createConsenter(id uint32, o *OrganizationParameters) []*configtxgen.Consenter {
262+
func createConsenter(o *OrganizationParameters, ids []uint32) ([]*configtxgen.Consenter, error) {
263+
if len(ids) != len(o.ConsenterNodes) {
264+
return nil, errors.Errorf("number of consenters doesn't match number of parties in org: %s", o.Name)
265+
}
246266
consenter := make([]*configtxgen.Consenter, len(o.ConsenterNodes))
247267
for i, n := range o.ConsenterNodes {
248-
// We use the org's admin certificate as the consenter nodes.
249-
identity := path.Join(getOrgPath(o), OrdererNodesDir, n.Party, n.CommonName, MSPDir,
268+
id := ids[i]
269+
if len(n.PartyName) == 0 && len(ids) > 1 {
270+
n.PartyName = fmt.Sprintf("party-%d", id)
271+
}
272+
identity := path.Join(getOrgPath(o), OrdererNodesDir, n.PartyName, n.CommonName, MSPDir,
250273
SignCertsDir, n.CommonName+CertSuffix)
251274
consenter[i] = &configtxgen.Consenter{
252275
ID: id,
@@ -258,7 +281,7 @@ func createConsenter(id uint32, o *OrganizationParameters) []*configtxgen.Consen
258281
ServerTLSCert: identity,
259282
}
260283
}
261-
return consenter
284+
return consenter, nil
262285
}
263286

264287
func getOrgPath(o *OrganizationParameters) string {
@@ -271,21 +294,3 @@ func getOrgPath(o *OrganizationParameters) string {
271294
return path.Join(GenericOrganizationsDir, o.Name)
272295
}
273296
}
274-
275-
func newOrdererEndpoint(id uint32, address, name string, api []string) (*types.OrdererEndpoint, error) {
276-
host, portStr, err := net.SplitHostPort(address)
277-
if err != nil {
278-
return nil, errors.Wrapf(err, "invalid address: %s", address)
279-
}
280-
port, err := strconv.Atoi(portStr)
281-
if err != nil {
282-
return nil, errors.Wrapf(err, "invalid port: %s", portStr)
283-
}
284-
return &types.OrdererEndpoint{
285-
Host: host,
286-
Port: port,
287-
MspID: name,
288-
ID: id,
289-
API: api,
290-
}, nil
291-
}

0 commit comments

Comments
 (0)