diff --git a/cmd/config/app_config_test.go b/cmd/config/app_config_test.go index ce4128c9..28435b76 100644 --- a/cmd/config/app_config_test.go +++ b/cmd/config/app_config_test.go @@ -379,20 +379,20 @@ func TestReadConfigLoadGen(t *testing.T) { Block: workload.BlockProfile{Size: 500}, Transaction: workload.TransactionProfile{ ReadWriteCount: workload.NewConstantDistribution(2), - Policy: &workload.PolicyProfile{ - ChannelID: "mychannel", - NamespacePolicies: map[string]*workload.Policy{ - workload.DefaultGeneratedNamespaceID: { - Scheme: signature.Ecdsa, Seed: 10, - }, - committerpb.MetaNamespaceID: { - Scheme: signature.Ecdsa, Seed: 11, - }, + }, + Policy: workload.PolicyProfile{ + ChannelID: "mychannel", + NamespacePolicies: map[string]*workload.Policy{ + workload.DefaultGeneratedNamespaceID: { + Scheme: signature.Ecdsa, Seed: 10, }, - OrdererEndpoints: []*commontypes.OrdererEndpoint{ - newOrdererEndpoint("org", "orderer"), + committerpb.MetaNamespaceID: { + Scheme: signature.Ecdsa, Seed: 11, }, }, + OrdererEndpoints: []*commontypes.OrdererEndpoint{ + newOrdererEndpoint("org", "orderer"), + }, }, Conflicts: workload.ConflictProfile{ InvalidSignatures: 0.1, diff --git a/cmd/config/samples/loadgen.yaml b/cmd/config/samples/loadgen.yaml index 0dc34a38..1d04f760 100644 --- a/cmd/config/samples/loadgen.yaml +++ b/cmd/config/samples/loadgen.yaml @@ -48,17 +48,20 @@ load-profile: transaction: read-write-count: const: 2 - policy: - channel-id: mychannel - namespace-policies: - 0: - scheme: ECDSA - seed: 10 - _meta: - scheme: ECDSA - seed: 11 - orderer-endpoints: - - id=0,msp-id=org,broadcast,deliver,orderer:7050 + policy: + channel-id: mychannel + namespace-policies: + 0: + # Supported schemes: MSP, ECDSA, EDDSA, and BLS + # If MSP is used, the default policy is requiring + # endorsement by all peer organizations. + scheme: ECDSA + seed: 10 + _meta: + scheme: ECDSA + seed: 11 + orderer-endpoints: + - id=0,msp-id=org,broadcast,deliver,orderer:7050 conflicts: invalid-signatures: 0.1 seed: 12345 diff --git a/cmd/config/templates/loadgen_common.yaml b/cmd/config/templates/loadgen_common.yaml index a9a6ae44..45078f34 100644 --- a/cmd/config/templates/loadgen_common.yaml +++ b/cmd/config/templates/loadgen_common.yaml @@ -38,15 +38,15 @@ load-profile: read-only-count: read-write-count: const: 2 - policy: - channel-id: {{ .Policy.ChannelID }} - namespace-policies: - {{ range $nsID, $element := .Policy.NamespacePolicies }} - {{ $nsID }}: - scheme: {{ $element.Scheme }} - seed: {{ $element.Seed }} - {{ end }} - config-block-path: {{ .ConfigBlockPath }} + policy: + channel-id: {{ .Policy.ChannelID }} + namespace-policies: + {{ range $nsID, $element := .Policy.NamespacePolicies }} + {{ $nsID }}: + scheme: {{ $element.Scheme }} + seed: {{ $element.Seed }} + {{ end }} + config-block-path: {{ .ConfigBlockPath }} query: query-size: min-invalid-keys-portion: diff --git a/cmd/loadgen/main.go b/cmd/loadgen/main.go index 8c22501d..30d6e2fb 100644 --- a/cmd/loadgen/main.go +++ b/cmd/loadgen/main.go @@ -100,7 +100,7 @@ func loadGenGenesisBlock() *cobra.Command { } cmd.SilenceUsage = true - block, err := workload.CreateConfigBlock(conf.LoadProfile.Transaction.Policy) + block, err := workload.CreateConfigBlock(&conf.LoadProfile.Policy) if err != nil { return err } diff --git a/docker/images/test_node/Dockerfile b/docker/images/test_node/Dockerfile index 4c77179b..50edfc36 100644 --- a/docker/images/test_node/Dockerfile +++ b/docker/images/test_node/Dockerfile @@ -20,7 +20,7 @@ ENV SC_SIDECAR_COMMITTER_ENDPOINT=localhost:9001 ENV SC_QUERY_DATABASE_ENDPOINTS=localhost:5433 ENV SC_VC_DATABASE_ENDPOINTS=localhost:5433 ENV SC_LOADGEN_ORDERER_CLIENT_ORDERER_CONNECTION_ENDPOINTS=localhost:7050 -ENV SC_LOADGEN_LOAD_PROFILE_TRANSACTION_POLICY_ORDERER_ENDPOINTS="id=0,msp-id=org,broadcast,deliver,localhost:7050" +ENV SC_LOADGEN_LOAD_PROFILE_POLICY_ORDERER_ENDPOINTS="id=0,msp-id=org,broadcast,deliver,localhost:7050" ENV SC_LOADGEN_ORDERER_CLIENT_SIDECAR_CLIENT_ENDPOINT=localhost:4001 # Disable TLS usage for db. diff --git a/docker/test/container_release_image_test.go b/docker/test/container_release_image_test.go index 7b006b09..90c7f5c7 100644 --- a/docker/test/container_release_image_test.go +++ b/docker/test/container_release_image_test.go @@ -66,7 +66,8 @@ func TestCommitterReleaseImagesWithTLS(t *testing.T) { v := config.NewViperWithLoadGenDefaults() c, err := config.ReadLoadGenYamlAndSetupLogging(v, filepath.Join(localConfigPath, "loadgen.yaml")) require.NoError(t, err) - configBlock, err := workload.CreateConfigBlock(c.LoadProfile.Transaction.Policy) + c.LoadProfile.Policy.CryptoMaterialPath = t.TempDir() + configBlock, err := workload.CreateConfigBlock(&c.LoadProfile.Policy) require.NoError(t, err) require.NoError(t, configtxgen.WriteOutputBlock(configBlock, configBlockPath)) diff --git a/docker/test/container_test.go b/docker/test/container_test.go index d55ab241..bc1429ce 100644 --- a/docker/test/container_test.go +++ b/docker/test/container_test.go @@ -76,7 +76,7 @@ func TestStartTestNodeWithTLSModesAndRemoteConnection(t *testing.T) { c, err := config.ReadLoadGenYamlAndSetupLogging(v, filepath.Join(localConfigPath, "loadgen.yaml")) require.NoError(t, err) ordererEp := mustGetEndpoint(ctx, t, containerName, mockOrdererPort) - c.LoadProfile.Transaction.Policy.OrdererEndpoints = []*commontypes.OrdererEndpoint{ + c.LoadProfile.Policy.OrdererEndpoints = []*commontypes.OrdererEndpoint{ { Host: ordererEp.Host, Port: ordererEp.Port, ID: 0, MspID: "org", API: []string{commontypes.Broadcast, commontypes.Deliver}, @@ -98,7 +98,7 @@ func TestStartTestNodeWithTLSModesAndRemoteConnection(t *testing.T) { Server: mustGetEndpoint(ctx, t, containerName, coordinatorServicePort), }, }, - Policy: c.LoadProfile.Transaction.Policy, + Policy: &c.LoadProfile.Policy, }, DBEnv: vc.NewDatabaseTestEnvFromConnection( t, diff --git a/integration/runner/runtime.go b/integration/runner/runtime.go index d1b93d61..e1840402 100644 --- a/integration/runner/runtime.go +++ b/integration/runner/runtime.go @@ -135,8 +135,9 @@ func NewRuntime(t *testing.T, conf *Config) *CommitterRuntime { LoadGenBlockLimit: conf.LoadgenBlockLimit, LoadGenWorkers: 1, Policy: &workload.PolicyProfile{ - ChannelID: TestChannelName, - NamespacePolicies: make(map[string]*workload.Policy), + ChannelID: TestChannelName, + NamespacePolicies: make(map[string]*workload.Policy), + CryptoMaterialPath: t.TempDir(), }, Logging: &logging.DefaultConfig, }, diff --git a/integration/test/config_update_test.go b/integration/test/config_update_test.go index fae269db..dc65e4d0 100644 --- a/integration/test/config_update_test.go +++ b/integration/test/config_update_test.go @@ -86,7 +86,8 @@ func TestConfigUpdate(t *testing.T) { lgMetaTx := c.TxBuilder.MakeTx(metaTx) c.AddOrUpdateNamespaces(t, committerpb.MetaNamespaceID) - metaPolicy := c.TxBuilder.TxEndorser.Policy(committerpb.MetaNamespaceID).VerificationPolicy() + verPolicies := c.TxBuilder.TxEndorser.VerificationPolicies() + metaPolicy := verPolicies[committerpb.MetaNamespaceID] submitConfigBlock := func(endpoints []*commontypes.OrdererEndpoint) { ordererEnv.SubmitConfigBlock(t, &workload.ConfigBlock{ ChannelID: c.SystemConfig.Policy.ChannelID, diff --git a/integration/test/namespace_lifecycle_test.go b/integration/test/namespace_lifecycle_test.go index 1b31f2ec..59347c87 100644 --- a/integration/test/namespace_lifecycle_test.go +++ b/integration/test/namespace_lifecycle_test.go @@ -29,9 +29,10 @@ func TestCreateUpdateNamespace(t *testing.T) { }) c.Start(t, runner.FullTxPath) - policyBytesNs1, err := proto.Marshal(c.TxBuilder.TxEndorser.Policy("1").VerificationPolicy()) + verPolicies := c.TxBuilder.TxEndorser.VerificationPolicies() + policyBytesNs1, err := proto.Marshal(verPolicies["1"]) require.NoError(t, err) - policyBytesNs2, err := proto.Marshal(c.TxBuilder.TxEndorser.Policy("2").VerificationPolicy()) + policyBytesNs2, err := proto.Marshal(verPolicies["2"]) require.NoError(t, err) tests := []struct { diff --git a/loadgen/adapters/common.go b/loadgen/adapters/common.go index 7252c28f..b9bd3114 100644 --- a/loadgen/adapters/common.go +++ b/loadgen/adapters/common.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "github.com/cockroachdb/errors" + "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-x-committer/api/committerpb" "github.com/hyperledger/fabric-x-committer/api/servicepb" @@ -24,10 +25,11 @@ import ( type ( // ClientResources holds client's pre-generated resources to be used by the adapters. ClientResources struct { - Metrics *metrics.PerfMetrics - Profile *workload.Profile - Stream *workload.StreamOptions - Limit *GenerateLimit + Metrics *metrics.PerfMetrics + Profile *workload.Profile + Stream *workload.StreamOptions + ConfigBlock *common.Block + Limit *GenerateLimit } // Phases specify the generation phases to enable. diff --git a/loadgen/adapters/sidecar.go b/loadgen/adapters/sidecar.go index 2eb70347..30eea4d4 100644 --- a/loadgen/adapters/sidecar.go +++ b/loadgen/adapters/sidecar.go @@ -28,11 +28,20 @@ type ( ) // NewSidecarAdapter instantiate SidecarAdapter. -func NewSidecarAdapter(config *SidecarClientConfig, res *ClientResources) *SidecarAdapter { +func NewSidecarAdapter(config *SidecarClientConfig, res *ClientResources) (*SidecarAdapter, error) { + // The sidecar adapter overwrite the orderer endpoints with its own. + // We first pre-allocate the ports. + for _, s := range config.OrdererServers { + _, err := s.PreAllocateListener() + if err != nil { + return nil, err + } + } + res.Profile.Policy.OrdererEndpoints = ordererconn.NewEndpoints(0, "msp", config.OrdererServers...) return &SidecarAdapter{ commonAdapter: commonAdapter{res: res}, config: config, - } + }, nil } // RunWorkload applies load on the sidecar. @@ -56,13 +65,7 @@ func (c *SidecarAdapter) RunWorkload(ctx context.Context, txStream *workload.Str }) // The sidecar adapter submits a config block manually. - policy := *c.res.Profile.Transaction.Policy - policy.OrdererEndpoints = ordererconn.NewEndpoints(0, "msp", c.config.OrdererServers...) - configBlock, err := workload.CreateConfigBlock(&policy) - if err != nil { - return err - } - if err = orderer.SubmitBlock(ctx, configBlock); err != nil { + if err = orderer.SubmitBlock(ctx, c.res.ConfigBlock); err != nil { return err } c.nextBlockNum.Add(1) diff --git a/loadgen/adapters/sidecar_receiver.go b/loadgen/adapters/sidecar_receiver.go index ce626e1f..226e5eae 100644 --- a/loadgen/adapters/sidecar_receiver.go +++ b/loadgen/adapters/sidecar_receiver.go @@ -38,7 +38,7 @@ const ( // runSidecarReceiver start receiving blocks from the sidecar. func runSidecarReceiver(ctx context.Context, params *sidecarReceiverParameters) error { ledgerReceiver, err := sidecarclient.New(&sidecarclient.Parameters{ - ChannelID: params.Res.Profile.Transaction.Policy.ChannelID, + ChannelID: params.Res.Profile.Policy.ChannelID, Client: params.ClientConfig, }) if err != nil { diff --git a/loadgen/adapters/sigverifier.go b/loadgen/adapters/sigverifier.go index a4a24791..6d1d5be3 100644 --- a/loadgen/adapters/sigverifier.go +++ b/loadgen/adapters/sigverifier.go @@ -38,7 +38,7 @@ func NewSVAdapter(config *connection.MultiClientConfig, res *ClientResources) *S // RunWorkload applies load on the SV. func (c *SvAdapter) RunWorkload(ctx context.Context, txStream *workload.StreamWithSetup) error { - updateMsg, err := createUpdate(c.res.Profile.Transaction.Policy) + updateMsg, err := createUpdate(c.res) if err != nil { return err } @@ -80,34 +80,30 @@ func (c *SvAdapter) RunWorkload(ctx context.Context, txStream *workload.StreamWi return errors.Wrap(g.Wait(), "workload done") } -func createUpdate(policy *workload.PolicyProfile) (*servicepb.VerifierUpdates, error) { - txEndorser := workload.NewTxEndorserVerifier(policy) - - envelopeBytes, err := workload.CreateConfigEnvelope(policy) - if err != nil { - return nil, err - } +func createUpdate(res *ClientResources) (*servicepb.VerifierUpdates, error) { + txEndorser := workload.NewTxEndorser(&res.Profile.Policy) + verPolicies := txEndorser.VerificationPolicies() updateMsg := &servicepb.VerifierUpdates{ Config: &applicationpb.ConfigTransaction{ - Envelope: envelopeBytes, + Envelope: res.ConfigBlock.Data.Data[0], }, NamespacePolicies: &applicationpb.NamespacePolicies{ - Policies: make([]*applicationpb.PolicyItem, 0, len(txEndorser.AllNamespaces())), + Policies: make([]*applicationpb.PolicyItem, 0, len(verPolicies)), }, } - for _, ns := range txEndorser.AllNamespaces() { - if ns == committerpb.MetaNamespaceID { + for nsID, nsPolicy := range verPolicies { + if nsID == committerpb.MetaNamespaceID { continue } - policyBytes, err := proto.Marshal(txEndorser.Policy(ns).VerificationPolicy()) + policyBytes, err := proto.Marshal(nsPolicy) if err != nil { return nil, errors.Wrap(err, "failed to serialize policy") } updateMsg.NamespacePolicies.Policies = append( updateMsg.NamespacePolicies.Policies, &applicationpb.PolicyItem{ - Namespace: ns, + Namespace: nsID, Policy: policyBytes, }, ) diff --git a/loadgen/client.go b/loadgen/client.go index 6f2b6a32..e1d50ead 100644 --- a/loadgen/client.go +++ b/loadgen/client.go @@ -56,24 +56,34 @@ var logger = logging.New("load-gen-client") // NewLoadGenClient creates a new client instance. func NewLoadGenClient(conf *ClientConfig) (*Client, error) { - logger.Infof("Config passed: %s", &utils.LazyJSON{O: conf}) + logger.Debugf("Config passed: %s", &utils.LazyJSON{O: conf}) c := &Client{ - conf: conf, - txStream: workload.NewTxStream(conf.LoadProfile, conf.Stream), + conf: conf, resources: adapters.ClientResources{ Profile: conf.LoadProfile, Stream: conf.Stream, Limit: conf.Limit, + Metrics: metrics.NewLoadgenServiceMetrics(&conf.Monitoring), }, healthcheck: connection.DefaultHealthCheckService(), } - c.resources.Metrics = metrics.NewLoadgenServiceMetrics(&conf.Monitoring) adapter, err := getAdapter(&conf.Adapter, &c.resources) if err != nil { return nil, err } + + // We generate the crypto material and block after we create the adapter since the sidecar adapter + // modifies the orderer endpoints. + c.resources.ConfigBlock, err = workload.CreateConfigBlock(&conf.LoadProfile.Policy) + if err != nil { + return nil, err + } + + // After creating the material, we can create the stream. + c.txStream = workload.NewTxStream(conf.LoadProfile, conf.Stream) + c.adapter = adapter conf.Generate = adapters.PhasesIntersect(conf.Generate, adapter.Supports()) return c, nil @@ -88,7 +98,7 @@ func getAdapter(conf *adapters.AdapterConfig, res *adapters.ClientResources) (Se case conf.OrdererClient != nil: return adapters.NewOrdererAdapter(conf.OrdererClient, res), nil case conf.SidecarClient != nil: - return adapters.NewSidecarAdapter(conf.SidecarClient, res), nil + return adapters.NewSidecarAdapter(conf.SidecarClient, res) case conf.VerifierClient != nil: return adapters.NewSVAdapter(conf.VerifierClient, res), nil case conf.LoadGenClient != nil: @@ -213,7 +223,7 @@ func (c *Client) runLimiterServer(ctx context.Context) error { func (c *Client) submitWorkloadSetupTXs(ctx context.Context, txs chan *servicepb.LoadGenTx) error { defer close(txs) - workloadSetupTXs, err := makeWorkloadSetupTXs(c.conf) + workloadSetupTXs, err := makeWorkloadSetupTXs(c.conf, &c.resources) if err != nil { return err } @@ -238,17 +248,17 @@ func (c *Client) submitWorkloadSetupTXs(ctx context.Context, txs chan *servicepb return nil } -func makeWorkloadSetupTXs(config *ClientConfig) ([]*servicepb.LoadGenTx, error) { +func makeWorkloadSetupTXs(config *ClientConfig, res *adapters.ClientResources) ([]*servicepb.LoadGenTx, error) { workloadSetupTXs := make([]*servicepb.LoadGenTx, 0, 2) if config.Generate.Config { - configTX, err := workload.CreateConfigTx(config.LoadProfile.Transaction.Policy) + configTX, err := workload.CreateConfigTxFromConfigBlock(res.ConfigBlock) if err != nil { return nil, err } workloadSetupTXs = append(workloadSetupTXs, configTX) } if config.Generate.Namespaces { - metaNsTX, err := workload.CreateLoadGenNamespacesTX(config.LoadProfile.Transaction.Policy) + metaNsTX, err := workload.CreateLoadGenNamespacesTX(&config.LoadProfile.Policy) if err != nil { return nil, err } diff --git a/loadgen/client_test.go b/loadgen/client_test.go index 8886491a..667f872f 100644 --- a/loadgen/client_test.go +++ b/loadgen/client_test.go @@ -51,7 +51,7 @@ func TestLoadGenForLoadGen(t *testing.T) { for _, limit := range defaultLimits { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Server.TLS = serverTLSConfig clientConf.Limit = limit // Ensure the client doesn't generate load, but only receives it from the sub client. @@ -61,7 +61,12 @@ func TestLoadGenForLoadGen(t *testing.T) { _, err := clientConf.Server.PreAllocateListener() require.NoError(t, err) - subClientConf := DefaultClientConf() + subClientConf := DefaultClientConf(t) + // We ensure the sub client uses the same crypto material as + // the main load generator and the entire system. + //nolint:revive // can't break line. + subClientConf.LoadProfile.Policy.CryptoMaterialPath = clientConf.LoadProfile.Policy.CryptoMaterialPath + subClientConf.Adapter.LoadGenClient = test.NewTLSClientConfig( clientTLSConfig, &clientConf.Server.Endpoint, ) @@ -86,7 +91,7 @@ func TestLoadGenForVCService(t *testing.T) { for _, limit := range defaultLimits { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit env := vc.NewValidatorAndCommitServiceTestEnvWithTLS(t, 2, serverTLSConfig) clientConf.Adapter.VCClient = test.NewTLSMultiClientConfig(clientTLSConfig, env.Endpoints...) @@ -106,7 +111,7 @@ func TestLoadGenForSigVerifier(t *testing.T) { for _, limit := range defaultLimits { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit clientConf.Adapter.VerifierClient = startVerifiers(t, serverTLSConfig, clientTLSConfig) // Start client @@ -153,7 +158,7 @@ func TestLoadGenForCoordinator(t *testing.T) { ) { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit _, sigVerServer := mock.StartMockSVService(t, 1) _, vcServer := mock.StartMockVCService(t, 1) @@ -197,7 +202,7 @@ func TestLoadGenForSidecar(t *testing.T) { ) { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit _, coordinatorServer := mock.StartMockCoordinatorService(t) @@ -219,8 +224,8 @@ func TestLoadGenForSidecar(t *testing.T) { Endpoints: ordererconn.NewEndpoints(0, "org", ordererServers...), TLS: clientTLSConfig, }, - ChannelID: clientConf.LoadProfile.Transaction.Policy.ChannelID, - Identity: clientConf.LoadProfile.Transaction.Policy.Identity, + ChannelID: clientConf.LoadProfile.Policy.ChannelID, + Identity: clientConf.LoadProfile.Policy.Identity, ConsensusType: ordererconn.Bft, }, LastCommittedBlockSetInterval: 100 * time.Millisecond, @@ -258,7 +263,7 @@ func TestLoadGenForOrderer(t *testing.T) { for _, limit := range defaultLimits { t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit numService := 3 sc := make([]*connection.ServerConfig, numService) @@ -279,8 +284,8 @@ func TestLoadGenForOrderer(t *testing.T) { Endpoints: endpoints, TLS: clientTLSConfig, }, - ChannelID: clientConf.LoadProfile.Transaction.Policy.ChannelID, - Identity: clientConf.LoadProfile.Transaction.Policy.Identity, + ChannelID: clientConf.LoadProfile.Policy.ChannelID, + Identity: clientConf.LoadProfile.Policy.Identity, ConsensusType: ordererconn.Bft, }, LastCommittedBlockSetInterval: 100 * time.Millisecond, @@ -302,8 +307,8 @@ func TestLoadGenForOrderer(t *testing.T) { // Submit default config block. require.NotNil(t, clientConf.LoadProfile) - clientConf.LoadProfile.Transaction.Policy.OrdererEndpoints = endpoints - configBlock, err := workload.CreateConfigBlock(clientConf.LoadProfile.Transaction.Policy) + clientConf.LoadProfile.Policy.OrdererEndpoints = endpoints + configBlock, err := workload.CreateConfigBlock(&clientConf.LoadProfile.Policy) require.NoError(t, err) err = orderer.SubmitBlock(t.Context(), configBlock) require.NoError(t, err) @@ -328,7 +333,7 @@ func TestLoadGenForOnlyOrderer(t *testing.T) { t.Parallel() serverTLSConfig, clientTLSConfig := test.CreateServerAndClientTLSConfig(t, mode) for _, limit := range defaultLimits { - clientConf := DefaultClientConf() + clientConf := DefaultClientConf(t) clientConf.Limit = limit t.Run(limitToString(limit), func(t *testing.T) { t.Parallel() @@ -352,8 +357,8 @@ func TestLoadGenForOnlyOrderer(t *testing.T) { // This is ignored when sidecar isn't used. // We validate the test doesn't break when config block is delivered. require.NotNil(t, clientConf.LoadProfile) - clientConf.LoadProfile.Transaction.Policy.OrdererEndpoints = endpoints - configBlock, err := workload.CreateConfigBlock(clientConf.LoadProfile.Transaction.Policy) + clientConf.LoadProfile.Policy.OrdererEndpoints = endpoints + configBlock, err := workload.CreateConfigBlock(&clientConf.LoadProfile.Policy) require.NoError(t, err) err = orderer.SubmitBlock(t.Context(), configBlock) require.NoError(t, err) @@ -365,8 +370,8 @@ func TestLoadGenForOnlyOrderer(t *testing.T) { Endpoints: endpoints, TLS: clientTLSConfig, }, - ChannelID: clientConf.LoadProfile.Transaction.Policy.ChannelID, - Identity: clientConf.LoadProfile.Transaction.Policy.Identity, + ChannelID: clientConf.LoadProfile.Policy.ChannelID, + Identity: clientConf.LoadProfile.Policy.Identity, ConsensusType: ordererconn.Bft, }, BroadcastParallelism: 5, diff --git a/loadgen/client_test_utils.go b/loadgen/client_test_utils.go index 3935decd..b082717a 100644 --- a/loadgen/client_test_utils.go +++ b/loadgen/client_test_utils.go @@ -38,7 +38,8 @@ func eventuallyMetrics( } // DefaultClientConf returns default config values for client testing. -func DefaultClientConf() *ClientConfig { +func DefaultClientConf(t *testing.T) *ClientConfig { + t.Helper() return &ClientConfig{ Server: connection.NewLocalHostServerWithTLS(test.InsecureTLSConfig), Monitoring: metrics.Config{ @@ -49,19 +50,20 @@ func DefaultClientConf() *ClientConfig { Block: workload.BlockProfile{Size: defaultBlockSize}, Transaction: workload.TransactionProfile{ ReadWriteCount: workload.NewConstantDistribution(2), - Policy: &workload.PolicyProfile{ - NamespacePolicies: map[string]*workload.Policy{ - workload.DefaultGeneratedNamespaceID: { - Scheme: signature.Ecdsa, - Seed: 10, - }, - committerpb.MetaNamespaceID: { - Scheme: signature.Ecdsa, - Seed: 11, - }, + }, + Policy: workload.PolicyProfile{ + NamespacePolicies: map[string]*workload.Policy{ + workload.DefaultGeneratedNamespaceID: { + Scheme: "MSP", + }, + committerpb.MetaNamespaceID: { + Scheme: signature.Ecdsa, + Seed: 11, }, - ChannelID: "channel", }, + ChannelID: "channel", + CryptoMaterialPath: t.TempDir(), + PeerOrganizationCount: 3, }, Seed: 12345, // We use small number of workers to reduce the CPU load during tests. diff --git a/loadgen/workload/config.go b/loadgen/workload/config.go index 87c63e03..cbf86566 100644 --- a/loadgen/workload/config.go +++ b/loadgen/workload/config.go @@ -28,6 +28,7 @@ type Profile struct { Key KeyProfile `mapstructure:"key" yaml:"key"` Transaction TransactionProfile `mapstructure:"transaction" yaml:"transaction"` Query QueryProfile `mapstructure:"query" yaml:"query"` + Policy PolicyProfile `mapstructure:"policy" yaml:"policy"` Conflicts ConflictProfile `mapstructure:"conflicts" yaml:"conflicts"` // The seed to generate the seeds for each worker @@ -62,8 +63,7 @@ type TransactionProfile struct { // The number of keys to generate (read ver=nil/write) ReadWriteCount *Distribution `mapstructure:"read-write-count" yaml:"read-write-count"` // The number of keys to generate (write) - BlindWriteCount *Distribution `mapstructure:"write-count" yaml:"write-count"` - Policy *PolicyProfile `mapstructure:"policy" yaml:"policy"` + BlindWriteCount *Distribution `mapstructure:"write-count" yaml:"write-count"` } // QueryProfile describes generate query characteristics. @@ -109,21 +109,33 @@ type PolicyProfile struct { // If ConfigBlockPath is specified, this value is ignored. OrdererEndpoints []*commontypes.OrdererEndpoint `mapstructure:"orderer-endpoints" yaml:"orderer-endpoints"` - // ConfigBlockPath may specify the config block to use. - // If this field is empty, a default config block will be generated. - ConfigBlockPath string `mapstructure:"config-block-path" yaml:"config-block-path"` + // CryptoMaterialPath may specify the path to the material generated by CreateDefaultConfigBlockWithCrypto(). + // If this field is empty, the material will be generated into a temporary folder. + // If this path does not exist, or it is empty, the material will be generated into it. + // If ConfigBlockPath is empty, the config block will be fetched from CryptoMaterialPath. + // Otherwise, it will be fetched from ConfigBlockPath regardless of CryptoMaterialPath. + CryptoMaterialPath string `mapstructure:"crypto-material-path" yaml:"crypto-material-path"` + ConfigBlockPath string `mapstructure:"config-block-path" yaml:"config-block-path"` // ChannelID and Identity are used to create the TX envelop. ChannelID string `mapstructure:"channel-id"` Identity *ordererconn.IdentityConfig `mapstructure:"identity"` + + // PeerOrganizationCount may specify the number of peer organizations to generate if the CryptoMaterialPath + // is not provided. + PeerOrganizationCount uint32 `mapstructure:"peer-organization-count"` } // Policy describes how to sign/verify a TX. +// It supports a signing with a raw signing key, or via a local MSP. +// Scheme can be a valid signature schemes (NONE, ECDSA, BLS, or EDDSA) or MSP to indicate using a local MSP. +// When Scheme is not MSP, we generate a key using the given Seed, or loading one if KeyPath is given. +// When Scheme is MSP, we load the signing identities from the CryptoMaterialPath, ignoring Seed and KeyPath. +// In such case, we use the default rule, which state that all peer organization should sign. type Policy struct { - Scheme signature.Scheme `mapstructure:"scheme" yaml:"scheme"` - Seed int64 `mapstructure:"seed" yaml:"seed"` - // KeyPath describes how to find/generate the signature keys. - KeyPath *KeyPath `mapstructure:"key-path" yaml:"key-path"` + Scheme signature.Scheme `mapstructure:"scheme" yaml:"scheme"` + Seed int64 `mapstructure:"seed" yaml:"seed"` + KeyPath *KeyPath `mapstructure:"key-path" yaml:"key-path"` } // KeyPath describes how to find/generate the signature keys. diff --git a/loadgen/workload/config_tx.go b/loadgen/workload/config_tx.go index fd2b5ec3..fc52e20b 100644 --- a/loadgen/workload/config_tx.go +++ b/loadgen/workload/config_tx.go @@ -10,6 +10,7 @@ import ( "fmt" "maps" "os" + "path" "slices" "github.com/cockroachdb/errors" @@ -35,12 +36,9 @@ type ConfigBlock struct { MetaNamespaceVerificationKey []byte } -// CreateConfigTx creating a config TX. -func CreateConfigTx(policy *PolicyProfile) (*servicepb.LoadGenTx, error) { - envelopeBytes, err := CreateConfigEnvelope(policy) - if err != nil { - return nil, err - } +// CreateConfigTxFromConfigBlock creates a config TX. +func CreateConfigTxFromConfigBlock(block *common.Block) (*servicepb.LoadGenTx, error) { + envelopeBytes := block.Data.Data[0] envelope, err := protoutil.GetEnvelopeFromBlock(envelopeBytes) if err != nil { return nil, errors.Wrap(err, "error getting envelope") @@ -66,39 +64,59 @@ func CreateConfigTx(policy *PolicyProfile) (*servicepb.LoadGenTx, error) { }, nil } -// CreateConfigEnvelope creating a meta policy. -func CreateConfigEnvelope(policy *PolicyProfile) ([]byte, error) { - block, err := CreateConfigBlock(policy) +// CreateConfigBlock creating a config block. +func CreateConfigBlock(policy *PolicyProfile) (*common.Block, error) { + err := prepareCryptoMaterial(policy) if err != nil { return nil, err } - return block.Data.Data[0], nil + + configBlockPath := policy.ConfigBlockPath + if configBlockPath == "" { + configBlockPath = path.Join(policy.CryptoMaterialPath, "config-block.pb.bin") + } + + block, err := configtxgen.ReadBlock(configBlockPath) + if err != nil { + return nil, errors.Wrapf(err, "failed reading config block from %s", policy.ConfigBlockPath) + } + return block, nil } -// CreateConfigBlock creating a config block. -func CreateConfigBlock(policy *PolicyProfile) (*common.Block, error) { - if policy.ConfigBlockPath != "" { - block, err := configtxgen.ReadBlock(policy.ConfigBlockPath) +// prepareCryptoMaterial generates the crypto material for a policy if it wasn't generated before. +func prepareCryptoMaterial(policy *PolicyProfile) error { + if policy.CryptoMaterialPath == "" { + tempDir, err := makeTemporaryDir() if err != nil { - return nil, errors.Wrapf(err, "failed reading config block from %s", policy.ConfigBlockPath) + return err } - return block, nil + policy.CryptoMaterialPath = tempDir + } + err := os.MkdirAll(policy.CryptoMaterialPath, 0o750) + if err != nil { + return errors.Wrap(err, "error creating crypto material folder") + } + + configBlockPath := path.Join(policy.CryptoMaterialPath, "config-block.pb.bin") + if _, fErr := os.Stat(configBlockPath); fErr == nil { + return nil } - txEndorser := NewTxEndorserVerifier(policy) - metaPolicy := txEndorser.Policy(committerpb.MetaNamespaceID) - return CreateDefaultConfigBlock(&ConfigBlock{ - MetaNamespaceVerificationKey: metaPolicy.VerificationPolicy().GetThresholdRule().GetPublicKey(), + _, metaPolicy := newPolicyEndorser(policy.CryptoMaterialPath, policy.NamespacePolicies[committerpb.MetaNamespaceID]) + _, err = CreateDefaultConfigBlockWithCrypto(policy.CryptoMaterialPath, &ConfigBlock{ + MetaNamespaceVerificationKey: metaPolicy.GetThresholdRule().GetPublicKey(), OrdererEndpoints: policy.OrdererEndpoints, ChannelID: policy.ChannelID, + PeerOrganizationCount: policy.PeerOrganizationCount, }, configtxgen.TwoOrgsSampleFabricX) + return err } // CreateDefaultConfigBlock creates a config block with default values. func CreateDefaultConfigBlock(conf *ConfigBlock, profileName string) (*common.Block, error) { - target, err := os.MkdirTemp("", "cryptogen-temp-*") + target, err := makeTemporaryDir() if err != nil { - return nil, errors.Wrap(err, "failed creating temp dir for config block generation") + return nil, err } defer func() { _ = os.RemoveAll(target) @@ -106,6 +124,14 @@ func CreateDefaultConfigBlock(conf *ConfigBlock, profileName string) (*common.Bl return CreateDefaultConfigBlockWithCrypto(target, conf, profileName) } +func makeTemporaryDir() (string, error) { + tempDir, err := os.MkdirTemp("", "sc-loadgen-crypto-*") + if err != nil { + return "", errors.Wrap(err, "error creating temp dir for crypto-material") + } + return tempDir, nil +} + // CreateDefaultConfigBlockWithCrypto creates a config block with crypto material. func CreateDefaultConfigBlockWithCrypto( targetPath string, conf *ConfigBlock, profileName string, diff --git a/loadgen/workload/namespace.go b/loadgen/workload/namespace.go index 40011b82..896f7e7b 100644 --- a/loadgen/workload/namespace.go +++ b/loadgen/workload/namespace.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package workload import ( + "slices" + "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" @@ -32,29 +34,25 @@ func CreateLoadGenNamespacesTX(policy *PolicyProfile) (*servicepb.LoadGenTx, err func CreateNamespacesTX( policy *PolicyProfile, metaNamespaceVersion uint64, includeNS ...string, ) (*applicationpb.Tx, error) { - endorser := NewTxEndorserVerifier(policy) + endorser := NewTxEndorser(policy) return CreateNamespacesTxFromEndorser(endorser, metaNamespaceVersion, includeNS...) } // CreateNamespacesTxFromEndorser creating the transaction containing the requested namespaces into the MetaNamespace. func CreateNamespacesTxFromEndorser( - endorser *TxEndorserVerifier, metaNamespaceVersion uint64, includeNS ...string, + endorser *TxEndorser, metaNamespaceVersion uint64, includeNS ...string, ) (*applicationpb.Tx, error) { - if len(includeNS) == 0 { - includeNS = endorser.AllNamespaces() - } - readWrites := make([]*applicationpb.ReadWrite, 0, len(includeNS)) - for _, ns := range includeNS { - if ns == committerpb.MetaNamespaceID { + for nsID, nsPolicy := range endorser.VerificationPolicies() { + if (len(includeNS) > 0 && !slices.Contains(includeNS, nsID)) || nsID == committerpb.MetaNamespaceID { continue } - policyBytes, err := proto.Marshal(endorser.Policy(ns).VerificationPolicy()) + policyBytes, err := proto.Marshal(nsPolicy) if err != nil { return nil, errors.Wrap(err, "failed to serialize namespace policy") } readWrites = append(readWrites, &applicationpb.ReadWrite{ - Key: []byte(ns), + Key: []byte(nsID), Value: policyBytes, }) } diff --git a/loadgen/workload/namespace_test.go b/loadgen/workload/namespace_test.go index 7ae634f0..afa1c50f 100644 --- a/loadgen/workload/namespace_test.go +++ b/loadgen/workload/namespace_test.go @@ -57,7 +57,9 @@ func TestNamespaceGeneratorKeyCreation(t *testing.T) { func TestCreateConfigTx(t *testing.T) { t.Parallel() policyProfile := makePolicyProfile(signature.Ecdsa) - tx, err := CreateConfigTx(policyProfile) + block, err := CreateConfigBlock(policyProfile) + require.NoError(t, err) + tx, err := CreateConfigTxFromConfigBlock(block) require.NoError(t, err) require.NotNil(t, tx) require.NotEmpty(t, tx.Id) diff --git a/loadgen/workload/sign.go b/loadgen/workload/sign.go index 85b39f78..82a10dc4 100644 --- a/loadgen/workload/sign.go +++ b/loadgen/workload/sign.go @@ -9,9 +9,16 @@ package workload import ( "maps" "os" - "slices" + "path" + "path/filepath" + "strings" "github.com/cockroachdb/errors" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-x-common/common/policydsl" + "github.com/hyperledger/fabric-x-common/msp" + "github.com/hyperledger/fabric-x-common/protoutil" + "github.com/hyperledger/fabric-x-common/tools/cryptogen" "github.com/hyperledger/fabric-x-committer/api/applicationpb" "github.com/hyperledger/fabric-x-committer/api/committerpb" @@ -19,139 +26,151 @@ import ( "github.com/hyperledger/fabric-x-committer/utils/logging" "github.com/hyperledger/fabric-x-committer/utils/signature" "github.com/hyperledger/fabric-x-committer/utils/signature/sigtest" + "github.com/hyperledger/fabric-x-committer/utils/test" ) var logger = logging.New("load-gen-sign") -type ( - // TxEndorserVerifier supports endorsing and verifying a TX, given an endorser. - TxEndorserVerifier struct { - policies map[string]*NsPolicyEndorserVerifier - } - - // NsPolicyEndorserVerifier supports endorsing and verifying a TX namespace. - NsPolicyEndorserVerifier struct { - endorser *sigtest.NsEndorser - verifier *signature.NsVerifier - verificationPolicy *applicationpb.NamespacePolicy - } -) +// TxEndorser supports endorsing a TX. +type TxEndorser struct { + endorsers map[string]*sigtest.NsEndorser + policies map[string]*applicationpb.NamespacePolicy +} var defaultPolicy = Policy{ Scheme: signature.Ecdsa, } -// NewTxEndorserVerifier creates a new TxEndorserVerifier given a workload profile. -func NewTxEndorserVerifier(policy *PolicyProfile) *TxEndorserVerifier { - endorsers := make(map[string]*NsPolicyEndorserVerifier, len(policy.NamespacePolicies)+2) - for nsID, p := range policy.NamespacePolicies { - endorsers[nsID] = NewPolicyEndorserVerifier(p) - } - +// NewTxEndorser creates a new TxEndorser given a workload profile. +func NewTxEndorser(policy *PolicyProfile) *TxEndorser { // We set default policy to ensure smooth operation even if the user did not specify anything. + nsPolicies := policy.NamespacePolicies for _, nsID := range []string{DefaultGeneratedNamespaceID, committerpb.MetaNamespaceID} { - if _, ok := endorsers[nsID]; !ok { - endorsers[nsID] = NewPolicyEndorserVerifier(&defaultPolicy) + if _, ok := nsPolicies[nsID]; !ok { + nsPolicies[nsID] = &defaultPolicy } } - return &TxEndorserVerifier{policies: endorsers} -} + endorsers := make(map[string]*sigtest.NsEndorser, len(nsPolicies)) + policies := make(map[string]*applicationpb.NamespacePolicy, len(nsPolicies)) + for nsID, p := range nsPolicies { + endorsers[nsID], policies[nsID] = newPolicyEndorser(policy.CryptoMaterialPath, p) + } -// AllNamespaces returns all the endorser's supported namespaces. -func (e *TxEndorserVerifier) AllNamespaces() []string { - return slices.Collect(maps.Keys(e.policies)) + return &TxEndorser{ + policies: policies, + endorsers: endorsers, + } } -// Policy returns a namespace policy. It creates one if it does not exist. -func (e *TxEndorserVerifier) Policy(nsID string) *NsPolicyEndorserVerifier { - policyEndorser, ok := e.policies[nsID] - if !ok { - policyEndorser = NewPolicyEndorserVerifier(&defaultPolicy) - e.policies[nsID] = policyEndorser - } - return policyEndorser +// VerificationPolicies returns the verification policies. +func (e *TxEndorser) VerificationPolicies() map[string]*applicationpb.NamespacePolicy { + return maps.Clone(e.policies) } // Endorse a TX. -func (e *TxEndorserVerifier) Endorse(txID string, tx *applicationpb.Tx) { +func (e *TxEndorser) Endorse(txID string, tx *applicationpb.Tx) { tx.Endorsements = make([]*applicationpb.Endorsements, len(tx.Namespaces)) for nsIndex, ns := range tx.Namespaces { - signer, ok := e.policies[ns.NsId] + signer, ok := e.endorsers[ns.NsId] if !ok { continue } - tx.Endorsements[nsIndex] = signer.Endorse(txID, tx, nsIndex) + endorsement, err := signer.EndorseTxNs(txID, tx, nsIndex) + Must(err) + tx.Endorsements[nsIndex] = endorsement } } -// Verify an endorsement on the transaction. -func (e *TxEndorserVerifier) Verify(txID string, tx *applicationpb.Tx) bool { - if len(tx.Endorsements) < len(tx.Namespaces) { - return false +// newPolicyEndorser creates a new [sigtest.NsEndorser] and its [applicationpb.NamespacePolicy] +// given a workload profile and a seed. +func newPolicyEndorser(cryptoPath string, profile *Policy) (*sigtest.NsEndorser, *applicationpb.NamespacePolicy) { + if profile == nil { + profile = &defaultPolicy + } + switch strings.ToUpper(profile.Scheme) { + case "MSP": + return newPolicyEndorserFromMsp(cryptoPath) + default: + signingKey, verificationKey := getKeyPair(profile) + return newPolicyEndorserFromKey(profile.Scheme, signingKey, verificationKey) } +} - for nsIndex, ns := range tx.Namespaces { - signer, ok := e.policies[ns.NsId] - if !ok || !signer.Verify(txID, tx, nsIndex) { - return false +// newPolicyEndorserFromKey creates a new [sigtest.NsEndorser] and its [applicationpb.NamespacePolicy] +// given a scheme and a key pair. +func newPolicyEndorserFromKey( + scheme string, signingKey, verificationKey []byte, +) (*sigtest.NsEndorser, *applicationpb.NamespacePolicy) { + endorser, err := sigtest.NewNsEndorserFromKey(scheme, signingKey) + utils.Must(err) + nsPolicy := &applicationpb.NamespacePolicy{ + Rule: &applicationpb.NamespacePolicy_ThresholdRule{ + ThresholdRule: &applicationpb.ThresholdRule{ + Scheme: scheme, PublicKey: verificationKey, + }, + }, + } + return endorser, nsPolicy +} + +func newPolicyEndorserFromMsp(cryptoPath string) (*sigtest.NsEndorser, *applicationpb.NamespacePolicy) { + peerOrgs := path.Join(cryptoPath, cryptogen.PeerOrganizationsDir) + dir, err := os.ReadDir(peerOrgs) + utils.Must(err) + mspDirs := make([]msp.DirLoadParameters, 0, len(dir)) + for _, dirEntry := range dir { + if !dirEntry.IsDir() { + continue } + orgName := dirEntry.Name() + clientName := "client@" + orgName + ".com" + mspDirs = append(mspDirs, msp.DirLoadParameters{ + MspName: orgName, + MspDir: filepath.Join(peerOrgs, orgName, "users", clientName, "msp"), + }) } - return true -} + signingIdentities, err := sigtest.GetSigningIdentities(mspDirs...) + utils.Must(err) + + endorser, err := sigtest.NewNsEndorserFromMsp(test.CreatorID, mspDirs...) + utils.Must(err) -// NewPolicyEndorserVerifier creates a new NsPolicyEndorserVerifier given a workload profile and a seed. -func NewPolicyEndorserVerifier(profile *Policy) *NsPolicyEndorserVerifier { - logger.Debugf("sig profile: %v", profile) + serializedSigningIdentities := make([][]byte, len(signingIdentities)) + sigPolicies := make([]*common.SignaturePolicy, len(signingIdentities)) + for i, si := range signingIdentities { + siBytes, serErr := si.Serialize() + utils.Must(serErr) + serializedSigningIdentities[i] = siBytes + sigPolicies[i] = policydsl.SignedBy(int32(i)) //nolint:gosec // safe int -> int32. + } - var signingKey signature.PrivateKey - var verificationKey signature.PublicKey + nsPolicy := &applicationpb.NamespacePolicy{ + Rule: &applicationpb.NamespacePolicy_MspRule{ + MspRule: protoutil.MarshalOrPanic( + policydsl.Envelope( + //nolint:gosec // safe int -> int32. + policydsl.NOutOf(int32(len(serializedSigningIdentities)), sigPolicies), + serializedSigningIdentities, + ), + ), + }, + } + return endorser, nsPolicy +} + +func getKeyPair(profile *Policy) (signingKey signature.PrivateKey, verificationKey signature.PublicKey) { + var err error if profile.KeyPath != nil { logger.Infof("Attempting to load keys") - var err error signingKey, verificationKey, err = loadKeys(*profile.KeyPath) utils.Must(err) } else { logger.Debugf("Generating new keys") signingKey, verificationKey = sigtest.NewKeyPairWithSeed(profile.Scheme, profile.Seed) } - v, err := signature.NewNsVerifierFromKey(profile.Scheme, verificationKey) - utils.Must(err) - endorser, err := sigtest.NewNsEndorserFromKey(profile.Scheme, signingKey) - utils.Must(err) - - return &NsPolicyEndorserVerifier{ - endorser: endorser, - verifier: v, - verificationPolicy: &applicationpb.NamespacePolicy{ - Rule: &applicationpb.NamespacePolicy_ThresholdRule{ - ThresholdRule: &applicationpb.ThresholdRule{ - Scheme: profile.Scheme, PublicKey: verificationKey, - }, - }, - }, - } -} - -// Endorse a TX. -func (e *NsPolicyEndorserVerifier) Endorse(txID string, tx *applicationpb.Tx, nsIndex int) *applicationpb.Endorsements { - sign, err := e.endorser.EndorseTxNs(txID, tx, nsIndex) - Must(err) - return sign -} - -// Verify a TX endorsement. -func (e *NsPolicyEndorserVerifier) Verify(txID string, tx *applicationpb.Tx, nsIndex int) bool { - if err := e.verifier.VerifyNs(txID, tx, nsIndex); err != nil { - return false - } - return true -} - -// VerificationPolicy returns the verification policy. -func (e *NsPolicyEndorserVerifier) VerificationPolicy() *applicationpb.NamespacePolicy { - return e.verificationPolicy + return signingKey, verificationKey } func loadKeys(keyPath KeyPath) (signingKey signature.PrivateKey, verificationKey signature.PublicKey, err error) { diff --git a/loadgen/workload/stream.go b/loadgen/workload/stream.go index ab23c2f7..88149e62 100644 --- a/loadgen/workload/stream.go +++ b/loadgen/workload/stream.go @@ -66,7 +66,7 @@ func NewTxStream( modifiers = append(modifiers, newSignTxModifier(NewRandFromSeedGenerator(w.seed), profile)) txGenSeed := NewRandFromSeedGenerator(w.seed) - txGen := newIndependentTxGenerator(txGenSeed, w.keyGen, &profile.Transaction, modifiers...) + txGen := newIndependentTxGenerator(txGenSeed, w.keyGen, &profile.Transaction, &profile.Policy, modifiers...) txStream.gens = append(txStream.gens, txGen) } return txStream diff --git a/loadgen/workload/stream_test.go b/loadgen/workload/stream_test.go index bff13671..fb0708ec 100644 --- a/loadgen/workload/stream_test.go +++ b/loadgen/workload/stream_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/hyperledger/fabric-x-common/msp" "github.com/stretchr/testify/require" "golang.org/x/time/rate" @@ -64,9 +65,9 @@ func benchTxProfiles() (profiles []*Profile) { for _, sign := range []bool{true, false} { for _, p := range benchWorkersProfiles() { if !sign { - p.Transaction.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.NoScheme + p.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.NoScheme } else { - p.Transaction.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.Ecdsa + p.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.Ecdsa } profiles = append(profiles, p) } @@ -78,7 +79,7 @@ func genericBench(b *testing.B, benchFunc func(b *testing.B, p *Profile)) { b.Helper() for _, p := range benchTxProfiles() { name := fmt.Sprintf("workers-%d-sign-%s", - p.Workers, p.Transaction.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme) + p.Workers, p.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme) b.Run(name, func(b *testing.B) { benchFunc(b, p) }) @@ -136,7 +137,7 @@ func requireValidKey(t *testing.T, key []byte, profile *Profile) { require.Positive(t, SumInt(key)) } -func requireValidTx(t *testing.T, tx *servicepb.LoadGenTx, profile *Profile, endorser *TxEndorserVerifier) { +func requireValidTx(t *testing.T, tx *servicepb.LoadGenTx, profile *Profile, endorser *TxEndorser) { t.Helper() require.NotEmpty(t, tx.Id) require.NotNil(t, tx.Tx) @@ -173,7 +174,7 @@ func requireValidTx(t *testing.T, tx *servicepb.LoadGenTx, profile *Profile, end requireValidKey(t, v.Key, profile) } - require.True(t, endorser.Verify(tx.Id, tx.Tx)) + require.True(t, verify(t, endorser.VerificationPolicies(), tx.Id, tx.Tx, nil)) } func testWorkersProfiles() (profiles []*Profile) { @@ -228,7 +229,7 @@ func TestGenValidTx(t *testing.T) { t.Parallel() c := startTxGeneratorUnderTest(t, p, defaultStreamOptions()) g := c.MakeGenerator() - endorser := NewTxEndorserVerifier(p.Transaction.Policy) + endorser := NewTxEndorser(&p.Policy) for range 100 { requireValidTx(t, g.Next(t.Context()), p, endorser) @@ -248,7 +249,7 @@ func TestGenValidBlock(t *testing.T) { t.Parallel() c := startTxGeneratorUnderTest(t, p, defaultStreamOptions()) g := c.MakeGenerator() - endorser := NewTxEndorserVerifier(p.Transaction.Policy) + endorser := NewTxEndorser(&p.Policy) for range 5 { txs := g.NextN(t.Context(), int(p.Block.Size)) //nolint:gosec // uint64 -> int. @@ -263,15 +264,15 @@ func TestGenValidBlock(t *testing.T) { func TestGenInvalidSigTx(t *testing.T) { t.Parallel() p := DefaultProfile(1) - p.Transaction.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.Ecdsa + p.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.Ecdsa p.Conflicts.InvalidSignatures = 0.2 c := startTxGeneratorUnderTest(t, p, defaultStreamOptions()) g := c.MakeGenerator() txs := g.NextN(t.Context(), 1e4) - endorser := NewTxEndorserVerifier(p.Transaction.Policy) + endorser := NewTxEndorser(&p.Policy) valid := Map(txs, func(_ int, tx *servicepb.LoadGenTx) float64 { - if !endorser.Verify(tx.Id, tx.Tx) { + if !verify(t, endorser.VerificationPolicies(), tx.Id, tx.Tx, nil) { return 1 } return 0 @@ -282,7 +283,7 @@ func TestGenInvalidSigTx(t *testing.T) { func TestGenDependentTx(t *testing.T) { t.Parallel() p := DefaultProfile(1) - p.Transaction.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.NoScheme + p.Policy.NamespacePolicies[DefaultGeneratedNamespaceID].Scheme = signature.NoScheme p.Conflicts.Dependencies = []DependencyDescription{ { Gap: NewConstantDistribution(1), @@ -566,3 +567,26 @@ func TestAsnMarshal(t *testing.T) { // We test against the generated load to enforce a coupling between different parts of the system. signature.CommonTestAsnMarshal(t, txs) } + +//nolint:revive // 5 arguments. +func verify( + t *testing.T, + policies map[string]*applicationpb.NamespacePolicy, + txID string, tx *applicationpb.Tx, + idDeserializer msp.IdentityDeserializer, +) bool { + t.Helper() + if len(tx.Endorsements) < len(tx.Namespaces) { + return false + } + for nsIndex, ns := range tx.Namespaces { + policy, ok := policies[ns.NsId] + require.Truef(t, ok, "No policy nsID=%s", ns.NsId) + verifier, err := signature.NewNsVerifier(policy, idDeserializer) + require.NoError(t, err, "Failed to create verifier for nsID=%s", ns.NsId) + if verErr := verifier.VerifyNs(txID, tx, nsIndex); verErr != nil { + return false + } + } + return true +} diff --git a/loadgen/workload/test_export.go b/loadgen/workload/test_export.go index 8955a194..ae6060a6 100644 --- a/loadgen/workload/test_export.go +++ b/loadgen/workload/test_export.go @@ -38,18 +38,18 @@ func DefaultProfile(workers uint32) *Profile { Transaction: TransactionProfile{ ReadWriteValueSize: 32, ReadWriteCount: NewConstantDistribution(2), - Policy: &PolicyProfile{ - NamespacePolicies: map[string]*Policy{ - DefaultGeneratedNamespaceID: {Scheme: signature.NoScheme}, - committerpb.MetaNamespaceID: {Scheme: signature.Ecdsa}, - }, - }, }, Query: QueryProfile{ QuerySize: NewConstantDistribution(100), MinInvalidKeysPortion: NewConstantDistribution(0), Shuffle: false, }, + Policy: PolicyProfile{ + NamespacePolicies: map[string]*Policy{ + DefaultGeneratedNamespaceID: {Scheme: signature.NoScheme}, + committerpb.MetaNamespaceID: {Scheme: signature.Ecdsa}, + }, + }, Conflicts: ConflictProfile{ InvalidSignatures: Never, }, diff --git a/loadgen/workload/tx.go b/loadgen/workload/tx.go index 294296c7..209222d2 100644 --- a/loadgen/workload/tx.go +++ b/loadgen/workload/tx.go @@ -40,17 +40,21 @@ const DefaultGeneratedNamespaceID = "0" // newIndependentTxGenerator creates a new valid TX generator given a transaction profile. func newIndependentTxGenerator( - rnd *rand.Rand, keys *ByteArrayGenerator, profile *TransactionProfile, modifiers ...Modifier, + rnd *rand.Rand, + keys *ByteArrayGenerator, + txProfile *TransactionProfile, + policyProfile *PolicyProfile, + modifiers ...Modifier, ) *IndependentTxGenerator { - txb, err := NewTxBuilderFromPolicy(profile.Policy, rnd) + txb, err := NewTxBuilderFromPolicy(policyProfile, rnd) utils.Must(err) return &IndependentTxGenerator{ TxBuilder: txb, - ReadOnlyKeyGenerator: multiKeyGenerator(rnd, keys, profile.ReadOnlyCount), - ReadWriteKeyGenerator: multiKeyGenerator(rnd, keys, profile.ReadWriteCount), - BlindWriteKeyGenerator: multiKeyGenerator(rnd, keys, profile.BlindWriteCount), - ReadWriteValueGenerator: valueGenerator(rnd, profile.ReadWriteValueSize), - BlindWriteValueGenerator: valueGenerator(rnd, profile.BlindWriteValueSize), + ReadOnlyKeyGenerator: multiKeyGenerator(rnd, keys, txProfile.ReadOnlyCount), + ReadWriteKeyGenerator: multiKeyGenerator(rnd, keys, txProfile.ReadWriteCount), + BlindWriteKeyGenerator: multiKeyGenerator(rnd, keys, txProfile.BlindWriteCount), + ReadWriteValueGenerator: valueGenerator(rnd, txProfile.ReadWriteValueSize), + BlindWriteValueGenerator: valueGenerator(rnd, txProfile.BlindWriteValueSize), Modifiers: modifiers, } } diff --git a/loadgen/workload/tx_builder.go b/loadgen/workload/tx_builder.go index bedb9e66..050a2025 100644 --- a/loadgen/workload/tx_builder.go +++ b/loadgen/workload/tx_builder.go @@ -25,7 +25,7 @@ import ( type TxBuilder struct { ChannelID string EnvSigner protoutil.Signer - TxEndorser *TxEndorserVerifier + TxEndorser *TxEndorser EnvCreator []byte NonceSource io.Reader } @@ -45,7 +45,7 @@ func NewTxBuilderFromPolicy(policy *PolicyProfile, nonceSource io.Reader) (*TxBu } return &TxBuilder{ ChannelID: policy.ChannelID, - TxEndorser: NewTxEndorserVerifier(policy), + TxEndorser: NewTxEndorser(policy), EnvSigner: envSigner, EnvCreator: envCreator, NonceSource: nonceSource, diff --git a/service/query/query_service_test.go b/service/query/query_service_test.go index bdf0b71b..06f2c619 100644 --- a/service/query/query_service_test.go +++ b/service/query/query_service_test.go @@ -361,9 +361,9 @@ func generateNamespacesUnderTest(t *testing.T, namespaces []string) *vc.Database env := vc.NewValidatorAndCommitServiceTestEnvWithTLS(t, 1, test.InsecureTLSConfig) env.SetupSystemTablesAndNamespaces(t.Context(), t) - clientConf := loadgen.DefaultClientConf() + clientConf := loadgen.DefaultClientConf(t) clientConf.Adapter.VCClient = test.NewTLSMultiClientConfig(test.InsecureTLSConfig, env.Endpoints...) - policies := &workload.PolicyProfile{ + policies := workload.PolicyProfile{ NamespacePolicies: make(map[string]*workload.Policy, len(namespaces)), } for i, ns := range append(namespaces, committerpb.MetaNamespaceID) { @@ -372,7 +372,7 @@ func generateNamespacesUnderTest(t *testing.T, namespaces []string) *vc.Database Seed: int64(i), } } - clientConf.LoadProfile.Transaction.Policy = policies + clientConf.LoadProfile.Policy = policies clientConf.Generate = adapters.Phases{Config: true, Namespaces: true} client, err := loadgen.NewLoadGenClient(clientConf) require.NoError(t, err) diff --git a/service/verifier/verifier_server_test.go b/service/verifier/verifier_server_test.go index 300e0ddb..bdd11d21 100644 --- a/service/verifier/verifier_server_test.go +++ b/service/verifier/verifier_server_test.go @@ -17,6 +17,7 @@ import ( commonmsp "github.com/hyperledger/fabric-x-common/msp" "github.com/hyperledger/fabric-x-common/protoutil" "github.com/hyperledger/fabric-x-common/tools/configtxgen" + "github.com/hyperledger/fabric-x-common/tools/cryptogen" "github.com/stretchr/testify/require" "github.com/hyperledger/fabric-x-committer/api/applicationpb" @@ -163,10 +164,11 @@ func TestSignatureRule(t *testing.T) { require.NoError(t, err) mspDirs := make([]commonmsp.DirLoadParameters, 2) + peerOrgPath := filepath.Join(cp.cryptoPath, cryptogen.PeerOrganizationsDir) for i, org := range []string{"peer-org-0", "peer-org-1"} { mspDirs[i].MspName = org clientName := "client@" + org + ".com" - mspDirs[i].MspDir = filepath.Join(cp.cryptoPath, "peerOrganizations", org, "users", clientName, "msp") + mspDirs[i].MspDir = filepath.Join(peerOrgPath, org, "users", clientName, "msp") } signingIdentities, err := sigtest.GetSigningIdentities(mspDirs...) diff --git a/utils/logging/logging.go b/utils/logging/logging.go index 164298b4..00cc8f13 100644 --- a/utils/logging/logging.go +++ b/utils/logging/logging.go @@ -12,6 +12,7 @@ import ( "strings" "sync" + "github.com/hyperledger/fabric-lib-go/common/flogging" "go.uber.org/zap" "go.uber.org/zap/zapcore" "google.golang.org/grpc/grpclog" @@ -28,6 +29,11 @@ var loggerInstance Logger // SetupWithConfig updates the logger with the given config. func SetupWithConfig(config *Config) { grpclog.SetLoggerV2(grpclog.NewLoggerV2(io.Discard, io.Discard, os.Stderr)) + logSpec := strings.ToUpper(config.Level) + if !config.Enabled { + logSpec = "FATAL" + } + flogging.Init(flogging.Config{LogSpec: logSpec}) loggerInstance.updateConfig(config) } diff --git a/utils/ordererconn/identity.go b/utils/ordererconn/identity.go index b2a3a0f6..9680236b 100644 --- a/utils/ordererconn/identity.go +++ b/utils/ordererconn/identity.go @@ -8,8 +8,6 @@ package ordererconn import ( "github.com/cockroachdb/errors" - "github.com/hyperledger/fabric-lib-go/bccsp" - "github.com/hyperledger/fabric-lib-go/bccsp/factory" "github.com/hyperledger/fabric-x-common/msp" "github.com/hyperledger/fabric-x-committer/utils" @@ -26,35 +24,18 @@ func NewIdentitySigner(config *IdentityConfig) (msp.SigningIdentity, error) { // return nil, nil } - mspConfig, err := msp.GetLocalMspConfig(config.MSPDir, config.BCCSP, config.MspID) - if err != nil { - return nil, errors.Wrap(err, "failed to load MSP config") - } - - bccspObj, err := getBCCSP(config.BCCSP) - if err != nil { - return nil, err - } - - localMsp, err := msp.New(msp.Options[msp.ProviderTypeToString(msp.FABRIC)], bccspObj) + localMsp, err := msp.LoadLocalMspDir(msp.DirLoadParameters{ + MspName: config.MspID, + MspDir: config.MSPDir, + CspConf: config.BCCSP, + }) if err != nil { return nil, errors.Wrap(err, "failed to create local MSP") } - err = localMsp.Setup(mspConfig) - if err != nil { - return nil, errors.Wrap(err, "failed to initialize local MSP") - } + signer, err := localMsp.GetDefaultSigningIdentity() if err != nil { return nil, errors.Wrap(err, "failed to load local signing identity") } return signer, nil } - -func getBCCSP(opts *factory.FactoryOpts) (bccsp.BCCSP, error) { //nolint:ireturn // Inherited from Fabric. - if opts == nil { - return factory.GetDefault(), nil - } - bccspObj, err := factory.GetBCCSPFromOpts(opts) - return bccspObj, errors.Wrap(err, "error creating BCCSP") -} diff --git a/utils/signature/sigtest/bench_test.go b/utils/signature/sigtest/bench_test.go index 00a93393..e48bcf12 100644 --- a/utils/signature/sigtest/bench_test.go +++ b/utils/signature/sigtest/bench_test.go @@ -9,15 +9,19 @@ package sigtest_test import ( "testing" + "github.com/hyperledger/fabric-lib-go/bccsp/factory" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-x-common/common/channelconfig" + "github.com/hyperledger/fabric-x-common/protoutil" "github.com/stretchr/testify/require" - "github.com/hyperledger/fabric-x-committer/api/applicationpb" "github.com/hyperledger/fabric-x-committer/loadgen/workload" "github.com/hyperledger/fabric-x-committer/utils/logging" "github.com/hyperledger/fabric-x-committer/utils/signature" - "github.com/hyperledger/fabric-x-committer/utils/signature/sigtest" ) +var testedSchemes = append(signature.AllRealSchemes, "MSP") + func BenchmarkDigest(b *testing.B) { logging.SetupWithConfig(&logging.Config{Enabled: false}) txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) @@ -38,25 +42,22 @@ func BenchmarkDigest(b *testing.B) { func BenchmarkSign(b *testing.B) { logging.SetupWithConfig(&logging.Config{Enabled: false}) - for _, scheme := range signature.AllRealSchemes { - sk, _ := sigtest.NewKeyPair(scheme) - endorser, err := sigtest.NewNsEndorserFromKey(scheme, sk) - require.NoError(b, err) - + for _, scheme := range testedSchemes { b.Run(scheme, func(b *testing.B) { - txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) + policy, _ := makePolicy(b, scheme) + endorser := workload.NewTxEndorser(policy) - resBench := make([]*applicationpb.Endorsements, b.N) - errBench := make([]error, b.N) + // We generate the TXs with a generic policy and add the endorsements as part of the benchmark. + txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) b.ResetTimer() - for i, tx := range txs { - resBench[i], errBench[i] = endorser.EndorseTxNs(tx.Id, tx.Tx, 0) + for _, tx := range txs { + endorser.Endorse(tx.Id, tx.Tx) } b.StopTimer() - for i := range b.N { - require.NoError(b, errBench[i], "error at index %d", i) - require.NotNil(b, resBench[i], "no result at index %d", i) + + for i, tx := range txs { + require.NotEmpty(b, tx.Tx.Endorsements, "endorsement is empty at index %d", i) } }) } @@ -64,20 +65,22 @@ func BenchmarkSign(b *testing.B) { func BenchmarkVerify(b *testing.B) { logging.SetupWithConfig(&logging.Config{Enabled: false}) - for _, scheme := range signature.AllRealSchemes { - sk, pk := sigtest.NewKeyPair(scheme) - endorser, err := sigtest.NewNsEndorserFromKey(scheme, sk) - require.NoError(b, err) - v, err := signature.NewNsVerifierFromKey(scheme, pk) - require.NoError(b, err) - + for _, scheme := range testedSchemes { b.Run(scheme, func(b *testing.B) { - txs := workload.GenerateTransactions(b, workload.DefaultProfile(8), b.N) - for _, tx := range txs { - endorsement, endorsementErr := endorser.EndorseTxNs(tx.Id, tx.Tx, 0) - require.NoError(b, endorsementErr) - tx.Tx.Endorsements = []*applicationpb.Endorsements{endorsement} - } + policy, block := makePolicy(b, scheme) + envelope, err := protoutil.ExtractEnvelope(block, 0) + require.NoError(b, err) + bundle, err := channelconfig.NewBundleFromEnvelope(envelope, factory.GetDefault()) + require.NoError(b, err) + + // We generate the TXs with the given policy endorsements. + profile := workload.DefaultProfile(8) + profile.Policy = *policy + txs := workload.GenerateTransactions(b, profile, b.N) + + allPolicies := workload.NewTxEndorser(policy).VerificationPolicies() + v, err := signature.NewNsVerifier(allPolicies[workload.DefaultGeneratedNamespaceID], bundle.MSPManager()) + require.NoError(b, err) errBench := make([]error, b.N) @@ -86,9 +89,27 @@ func BenchmarkVerify(b *testing.B) { errBench[i] = v.VerifyNs(tx.Id, tx.Tx, 0) } b.StopTimer() - for i := range b.N { - require.NoError(b, errBench[i], "error at index %d", i) + + for i, curErr := range errBench { + require.NoError(b, curErr, "error at index %d", i) } }) } } + +func makePolicy(b *testing.B, scheme string) (*workload.PolicyProfile, *common.Block) { + b.Helper() + policy := &workload.PolicyProfile{ + NamespacePolicies: map[string]*workload.Policy{ + workload.DefaultGeneratedNamespaceID: { + Scheme: scheme, + Seed: 10, + }, + }, + CryptoMaterialPath: b.TempDir(), + PeerOrganizationCount: 3, + } + block, err := workload.CreateConfigBlock(policy) + require.NoError(b, err) + return policy, block +} diff --git a/utils/signature/sigtest/factory.go b/utils/signature/sigtest/factory.go index c29cdd39..9466df25 100644 --- a/utils/signature/sigtest/factory.go +++ b/utils/signature/sigtest/factory.go @@ -15,6 +15,7 @@ import ( "io" "math/big" pseudorand "math/rand" + "strings" "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fr" @@ -27,7 +28,7 @@ import ( // NewKeyPair generate private and public keys. // This should only be used for evaluation and testing as it might use non-secure crypto methods. func NewKeyPair(scheme signature.Scheme) (signature.PrivateKey, signature.PublicKey) { - switch scheme { + switch strings.ToUpper(scheme) { case signature.Ecdsa: return ecdsaNewKeyPair() case signature.Bls: @@ -42,7 +43,7 @@ func NewKeyPair(scheme signature.Scheme) (signature.PrivateKey, signature.Public // NewKeyPairWithSeed generate deterministic private and public keys. // This should only be used for evaluation and testing as it might use non-secure crypto methods. func NewKeyPairWithSeed(scheme signature.Scheme, seed int64) (signature.PrivateKey, signature.PublicKey) { - switch scheme { + switch strings.ToUpper(scheme) { case signature.Ecdsa: return ecdsaNewKeyPairWithSeed(seed) case signature.Bls: