diff --git a/common/channelconfig/load.go b/common/channelconfig/load.go new file mode 100644 index 000000000..6f3f3dea8 --- /dev/null +++ b/common/channelconfig/load.go @@ -0,0 +1,156 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channelconfig + +import ( + "github.com/cockroachdb/errors" + "github.com/hyperledger/fabric-lib-go/bccsp/factory" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + + commontypes "github.com/hyperledger/fabric-x-common/api/types" + "github.com/hyperledger/fabric-x-common/common/configtx" + "github.com/hyperledger/fabric-x-common/protoutil" +) + +type ( + // ConfigBlockMaterial contains the channel-ID, the config block, its bundle, and the organization's material. + ConfigBlockMaterial struct { + ChannelID string + ConfigBlock *common.Block + Bundle *Bundle + OrdererOrganizations []*OrdererOrganizationMaterial + ApplicationOrganizations []*OrganizationMaterial + } + + // OrganizationMaterial contains the MspID (Organization ID), and its root CAs in bytes. + OrganizationMaterial struct { + MspID string + CACerts [][]byte + } + + // OrdererOrganizationMaterial contains the MspID (Organization ID), orderer endpoints, and their root CAs in bytes. + OrdererOrganizationMaterial struct { + OrganizationMaterial + Endpoints []*commontypes.OrdererEndpoint + } +) + +// ErrNotConfigBlock is returned when the block is not a config block. +var ErrNotConfigBlock = errors.New("the block is not a config block") + +// LoadConfigBlockMaterialFromFile loads a config block from a file. +// If the block is not a config block, ErrNotConfigBlock will be returned. +func LoadConfigBlockMaterialFromFile(blockPath string) (*ConfigBlockMaterial, error) { + if blockPath == "" { + return nil, errors.New("config block path is empty") + } + configBlock, err := protoutil.ReadBlockFromFile(blockPath) + if err != nil { + return nil, err + } + return LoadConfigBlockMaterial(configBlock) +} + +// LoadConfigBlockMaterial attempts to read a config block from the given block. +// If the block is not a config block, ErrNotConfigBlock will be returned. +func LoadConfigBlockMaterial(block *common.Block) (*ConfigBlockMaterial, error) { + // We expect config blocks to have exactly one transaction, with a valid payload. + if block == nil || block.Data == nil || len(block.Data.Data) != 1 { + return nil, ErrNotConfigBlock + } + configTx, err := protoutil.GetEnvelopeFromBlock(block.Data.Data[0]) + if err != nil { + return nil, errors.Join(ErrNotConfigBlock, err) + } + + payload, err := protoutil.UnmarshalPayload(configTx.Payload) + if err != nil { + return nil, errors.Join(ErrNotConfigBlock, err) + } + if payload.Header == nil { + return nil, ErrNotConfigBlock + } + chHead, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) + if err != nil || chHead.Type != int32(common.HeaderType_CONFIG) { + return nil, errors.Join(ErrNotConfigBlock, err) + } + + // This is a config block. Let's parse it. + configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data) + if err != nil { + return nil, errors.Wrap(err, "error unmarshalling config envelope from payload data") + } + + bundle, err := NewBundle(chHead.ChannelId, configEnvelope.Config, factory.GetDefault()) + if err != nil { + return nil, errors.Wrap(err, "error creating channel config bundle") + } + ordererOrgs, err := newOrdererOrganizationsMaterialsFromBundle(bundle) + if err != nil { + return nil, err + } + applicationOrgs, err := newApplicationOrganizationsMaterialsFromBundle(bundle) + if err != nil { + return nil, err + } + return &ConfigBlockMaterial{ + ChannelID: chHead.ChannelId, + ConfigBlock: block, + Bundle: bundle, + OrdererOrganizations: ordererOrgs, + ApplicationOrganizations: applicationOrgs, + }, nil +} + +// newOrdererOrganizationsMaterialsFromBundle reads the organizations' materials from a config block bundle. +func newOrdererOrganizationsMaterialsFromBundle(bundle *Bundle) ([]*OrdererOrganizationMaterial, error) { + ordererCfg, ok := bundle.OrdererConfig() + if !ok { + return nil, errors.New("could not find orderer config") + } + orgs := ordererCfg.Organizations() + orgsMaterial := newOrganizationsMaterials(orgs) + + ordererOrgMaterial := make([]*OrdererOrganizationMaterial, len(orgsMaterial)) + for i, org := range orgsMaterial { + var endpoints []*commontypes.OrdererEndpoint + endpointsStr := orgs[org.MspID].Endpoints() + for _, eStr := range endpointsStr { + e, err := commontypes.ParseOrdererEndpoint(eStr) + if err != nil { + return nil, err + } + e.MspID = org.MspID + endpoints = append(endpoints, e) + } + ordererOrgMaterial[i] = &OrdererOrganizationMaterial{ + OrganizationMaterial: *org, + Endpoints: endpoints, + } + } + return ordererOrgMaterial, nil +} + +// newApplicationOrganizationsMaterialsFromBundle reads the organizations' materials from a config block bundle. +func newApplicationOrganizationsMaterialsFromBundle(bundle *Bundle) ([]*OrganizationMaterial, error) { + applicationCfg, ok := bundle.ApplicationConfig() + if !ok { + return nil, errors.New("could not find application config") + } + return newOrganizationsMaterials(applicationCfg.Organizations()), nil +} + +func newOrganizationsMaterials[T Org](orgs map[string]T) []*OrganizationMaterial { + organizationMaterials := make([]*OrganizationMaterial, 0, len(orgs)) + for orgID, org := range orgs { + organizationMaterials = append(organizationMaterials, &OrganizationMaterial{ + MspID: orgID, + CACerts: org.MSP().GetTLSRootCerts(), + }) + } + return organizationMaterials +} diff --git a/common/channelconfig/load_test.go b/common/channelconfig/load_test.go new file mode 100644 index 000000000..155fc21d7 --- /dev/null +++ b/common/channelconfig/load_test.go @@ -0,0 +1,281 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channelconfig_test + +import ( + "fmt" + "os" + "path" + "path/filepath" + "testing" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/stretchr/testify/require" + + commontypes "github.com/hyperledger/fabric-x-common/api/types" + "github.com/hyperledger/fabric-x-common/common/channelconfig" + "github.com/hyperledger/fabric-x-common/protoutil" + "github.com/hyperledger/fabric-x-common/tools/configtxgen" + "github.com/hyperledger/fabric-x-common/tools/cryptogen" +) + +func TestLoadConfigBlockFromFile(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + path string + expectedChannelID string + expectedOrdererOrgs int + expectedApplicationOrgs int + }{ + { + name: "valid config block file", + path: createConfigBlockPath(t, "channel-0", 2, 1), + expectedChannelID: "channel-0", + expectedOrdererOrgs: 1, + expectedApplicationOrgs: 2, + }, + { + name: "valid config block with single peer org", + path: createConfigBlockPath(t, "channel-1", 1, 1), + expectedChannelID: "channel-1", + expectedOrdererOrgs: 1, + expectedApplicationOrgs: 1, + }, + { + name: "valid config block with multiple peer orgs and orderers", + path: createConfigBlockPath(t, "channel-2", 3, 2), + expectedChannelID: "channel-2", + expectedOrdererOrgs: 2, + expectedApplicationOrgs: 3, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + retMaterial, retErr := channelconfig.LoadConfigBlockMaterialFromFile(tc.path) + + require.NoError(t, retErr) + require.NotNil(t, retMaterial) + require.Equal(t, tc.expectedChannelID, retMaterial.ChannelID) + require.NotNil(t, retMaterial.ConfigBlock) + require.NotNil(t, retMaterial.Bundle) + require.Len(t, retMaterial.OrdererOrganizations, tc.expectedOrdererOrgs) + require.Len(t, retMaterial.ApplicationOrganizations, tc.expectedApplicationOrgs) + for _, org := range retMaterial.OrdererOrganizations { + require.NotEmpty(t, org.Endpoints) + } + }) + } +} + +func TestLoadConfigBlockFromFileEdgeCases(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + blockPath string + expectedError string + }{ + { + name: "empty path", + blockPath: "", + expectedError: "config block path is empty", + }, + { + name: "non-existent file", + blockPath: path.Join(t.TempDir(), "file.block"), + expectedError: "could not read block", + }, + { + name: "nil data", + blockPath: createBlockFile(t, &common.Block{}), + expectedError: "the block is not a config block", + }, + { + name: "data block", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{Data: [][]byte{[]byte("transaction data")}}, + }), + expectedError: "the block is not a config block", + }, + { + name: "empty data", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{Data: [][]byte{}}, + }), + expectedError: "the block is not a config block", + }, + { + name: "multiple transactions", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{ + Data: [][]byte{[]byte("transaction 1"), []byte("transaction 2")}, + }, + }), + expectedError: "the block is not a config block", + }, + { + name: "invalid payload", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(&common.Envelope{ + Payload: []byte("invalid payload"), + }), + }, + }, + }), + expectedError: "the block is not a config block", + }, + { + name: "nil header", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(&common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{Header: nil}), + }), + }, + }, + }), + expectedError: "the block is not a config block", + }, + { + name: "wrong header type", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(&common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Header: &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ + Type: int32(common.HeaderType_ENDORSER_TRANSACTION), + ChannelId: "test-channel", + }), + }, + }), + }), + }, + }, + }), + expectedError: "the block is not a config block", + }, + { + name: "invalid config envelope", + blockPath: createBlockFile(t, &common.Block{ + Data: &common.BlockData{ + Data: [][]byte{ + protoutil.MarshalOrPanic(&common.Envelope{ + Payload: protoutil.MarshalOrPanic(&common.Payload{ + Header: &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ + Type: int32(common.HeaderType_CONFIG), + ChannelId: "test-channel", + }), + }, + Data: []byte("invalid config envelope"), + }), + }), + }, + }, + }), + expectedError: "error unmarshalling config envelope", + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + retMaterial, retErr := channelconfig.LoadConfigBlockMaterialFromFile(tc.blockPath) + require.ErrorContains(t, retErr, tc.expectedError) + require.Nil(t, retMaterial) + }) + } +} + +func TestOrganizationMaterialExtraction(t *testing.T) { + t.Parallel() + + material := createConfigBlockMaterial(t, 3, 2) + + require.Len(t, material.OrdererOrganizations, 2) + for _, ordererOrg := range material.OrdererOrganizations { + require.NotEmpty(t, ordererOrg.MspID) + require.NotEmpty(t, ordererOrg.CACerts) + require.Len(t, ordererOrg.Endpoints, 1) + for _, ep := range ordererOrg.Endpoints { + require.NotEmpty(t, ep.Host) + require.NotZero(t, ep.Port) + require.Equal(t, ordererOrg.MspID, ep.MspID) + } + } + + require.Len(t, material.ApplicationOrganizations, 3) + for _, peerOrg := range material.ApplicationOrganizations { + require.NotEmpty(t, peerOrg.MspID) + require.NotEmpty(t, peerOrg.CACerts) + } +} + +// Helper methods + +func createConfigBlockPath( + t *testing.T, + channelID string, + peerOrgCount uint32, + ordererOrgCount uint32, +) string { + t.Helper() + cryptoDir := t.TempDir() + orgs := make([]cryptogen.OrganizationParameters, 0, int(peerOrgCount)+int(ordererOrgCount)) + for i := range peerOrgCount { + orgs = append(orgs, cryptogen.OrganizationParameters{ + Name: fmt.Sprintf("peer-org-%d", i), + Domain: fmt.Sprintf("peer-org-%d.com", i), + PeerNodes: []cryptogen.Node{{CommonName: "peer-node", Hostname: "peer-node"}}, + }) + } + for i := range ordererOrgCount { + domain := fmt.Sprintf("orderer-org-%d.com", i) + orgs = append(orgs, cryptogen.OrganizationParameters{ + Name: fmt.Sprintf("orderer-org-%d", i), + Domain: domain, + OrdererEndpoints: []*commontypes.OrdererEndpoint{{ID: i, Host: domain, Port: 7050}}, + ConsenterNodes: []cryptogen.Node{{CommonName: "consenter", Hostname: "consenter"}}, + OrdererNodes: []cryptogen.Node{{CommonName: "orderer-node", Hostname: "orderer-node"}}, + }) + } + _, err := cryptogen.CreateOrExtendConfigBlockWithCrypto(cryptogen.ConfigBlockParameters{ + TargetPath: cryptoDir, + BaseProfile: configtxgen.SampleFabricX, + ChannelID: channelID, + Organizations: orgs, + }) + require.NoError(t, err) + return path.Join(cryptoDir, cryptogen.ConfigBlockFileName) +} + +func createConfigBlockMaterial( + t *testing.T, + peerOrgCount uint32, + ordererOrgCount uint32, +) *channelconfig.ConfigBlockMaterial { + t.Helper() + blockPath := createConfigBlockPath(t, "test-channel", peerOrgCount, ordererOrgCount) + material, err := channelconfig.LoadConfigBlockMaterialFromFile(blockPath) + require.NoError(t, err) + return material +} + +func createBlockFile(t *testing.T, block *common.Block) string { + t.Helper() + blockPath := filepath.Join(t.TempDir(), "block.block") + blockBytes, err := protoutil.Marshal(block) + require.NoError(t, err) + err = os.WriteFile(blockPath, blockBytes, 0o600) + require.NoError(t, err) + return blockPath +} diff --git a/protoutil/blockutils.go b/protoutil/blockutils.go index 4ad31d37a..aa5f621ff 100644 --- a/protoutil/blockutils.go +++ b/protoutil/blockutils.go @@ -13,6 +13,7 @@ import ( "encoding/base64" "fmt" "math/big" + "os" "github.com/cockroachdb/errors" cb "github.com/hyperledger/fabric-protos-go-apiv2/common" @@ -47,16 +48,15 @@ type asn1Header struct { } func BlockHeaderBytes(b *cb.BlockHeader) []byte { - asn1Header := asn1Header{ + result, err := asn1.Marshal(asn1Header{ PreviousHash: b.PreviousHash, DataHash: b.DataHash, Number: new(big.Int).SetUint64(b.Number), - } - result, err := asn1.Marshal(asn1Header) + }) if err != nil { // Errors should only arise for types which cannot be encoded, since the // BlockHeader type is known a-priori to contain only encodable types, an - // error here is fatal and should not be propagated + // error here is fatal and should not be propagated. panic(err) } return result @@ -80,7 +80,7 @@ func ComputeBlockDataHash(b *cb.BlockData) []byte { } // GetChannelIDFromBlockBytes returns channel ID given byte array which represents -// the block +// the block. func GetChannelIDFromBlockBytes(bytes []byte) (string, error) { block, err := UnmarshalBlock(bytes) if err != nil { @@ -90,7 +90,7 @@ func GetChannelIDFromBlockBytes(bytes []byte) (string, error) { return GetChannelIDFromBlock(block) } -// GetChannelIDFromBlock returns channel ID in the block +// GetChannelIDFromBlock returns channel ID in the block. func GetChannelIDFromBlock(block *cb.Block) (string, error) { if block == nil || block.Data == nil || block.Data.Data == nil || len(block.Data.Data) == 0 { return "", errors.New("failed to retrieve channel id - block is empty") @@ -135,7 +135,7 @@ func GetMetadataFromBlock(block *cb.Block, index cb.BlockMetadataIndex) (*cb.Met } // GetMetadataFromBlockOrPanic retrieves metadata at the specified index, or -// panics on error +// panics on error. func GetMetadataFromBlockOrPanic(block *cb.Block, index cb.BlockMetadataIndex) *cb.Metadata { md, err := GetMetadataFromBlock(block, index) if err != nil { @@ -174,7 +174,7 @@ func GetConsenterMetadataFromBlock(block *cb.Block) (*cb.Metadata, error) { } // GetLastConfigIndexFromBlock retrieves the index of the last config block as -// encoded in the block metadata +// encoded in the block metadata. func GetLastConfigIndexFromBlock(block *cb.Block) (uint64, error) { m, err := GetMetadataFromBlock(block, cb.BlockMetadataIndex_SIGNATURES) if err != nil { @@ -203,7 +203,7 @@ func GetLastConfigIndexFromBlock(block *cb.Block) (uint64, error) { } // GetLastConfigIndexFromBlockOrPanic retrieves the index of the last config -// block as encoded in the block metadata, or panics on error +// block as encoded in the block metadata, or panics on error. func GetLastConfigIndexFromBlockOrPanic(block *cb.Block) uint64 { index, err := GetLastConfigIndexFromBlock(block) if err != nil { @@ -212,15 +212,15 @@ func GetLastConfigIndexFromBlockOrPanic(block *cb.Block) uint64 { return index } -// CopyBlockMetadata copies metadata from one block into another -func CopyBlockMetadata(src *cb.Block, dst *cb.Block) { +// CopyBlockMetadata copies metadata from one block into another. +func CopyBlockMetadata(src, dst *cb.Block) { dst.Metadata = src.Metadata // Once copied initialize with rest of the // required metadata positions. InitBlockMetadata(dst) } -// InitBlockMetadata initializes metadata structure +// InitBlockMetadata initializes metadata structure. func InitBlockMetadata(block *cb.Block) { if block.Metadata == nil { block.Metadata = &cb.BlockMetadata{Metadata: [][]byte{{}, {}, {}, {}, {}}} @@ -231,10 +231,6 @@ func InitBlockMetadata(block *cb.Block) { } } -type VerifierBuilder func(block *cb.Block) BlockVerifierFunc - -type BlockVerifierFunc func(header *cb.BlockHeader, metadata *cb.BlockMetadata) error - //go:generate counterfeiter -o mocks/policy.go --fake-name Policy . policy type policy interface { // copied from common.policies to avoid circular import. // EvaluateSignedData takes a set of SignedData and evaluates whether @@ -243,64 +239,73 @@ type policy interface { // copied from common.policies to avoid circular import. EvaluateSignedData(signatureSet []*SignedData) error } -func BlockSignatureVerifier(bftEnabled bool, consenters []*cb.Consenter, policy policy) BlockVerifierFunc { - return func(header *cb.BlockHeader, metadata *cb.BlockMetadata) error { - if len(metadata.GetMetadata()) < int(cb.BlockMetadataIndex_SIGNATURES)+1 { - return errors.Errorf("no signatures in block metadata") - } +// BlockSigVerifier can be used to verify blocks using the given policy. +// If BFT is enabled, a list of Consenters must also be provided. +type BlockSigVerifier struct { + Policy policy + BFT bool + Consenters []*cb.Consenter +} - md := &cb.Metadata{} - if err := proto.Unmarshal(metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES], md); err != nil { - return errors.Wrapf(err, "error unmarshalling signatures from metadata: %v", err) - } +// Verify verifies a block's header and metadata using the verifier's policy. +func (v *BlockSigVerifier) Verify(header *cb.BlockHeader, metadata *cb.BlockMetadata) error { + md, metaErr := UnmarshalBlockMetadataSignatures(metadata.GetMetadata()) + if metaErr != nil { + return errors.Wrapf(metaErr, "error unmarshalling signatures from metadata") + } - var signatureSet []*SignedData - for _, metadataSignature := range md.Signatures { - var signerIdentity *msppb.Identity - var signedPayload []byte - // if the SignatureHeader is empty and the IdentifierHeader is present, then the consenter expects us to fetch its identity by its numeric identifier - if bftEnabled && len(metadataSignature.GetSignatureHeader()) == 0 && len(metadataSignature.GetIdentifierHeader()) > 0 { - identifierHeader, err := UnmarshalIdentifierHeader(metadataSignature.IdentifierHeader) - if err != nil { - return fmt.Errorf("failed unmarshalling identifier header for block %d: %v", header.GetNumber(), err) - } - identifier := identifierHeader.GetIdentifier() - signerIdentity = searchConsenterIdentityByID(consenters, identifier) - if signerIdentity == nil { - // The identifier is not within the consenter set - continue - } - signedPayload = util.ConcatenateBytes(md.Value, metadataSignature.IdentifierHeader, BlockHeaderBytes(header)) - } else { - signatureHeader, err := UnmarshalSignatureHeader(metadataSignature.GetSignatureHeader()) - if err != nil { - return fmt.Errorf("failed unmarshalling signature header for block %d: %v", header.GetNumber(), err) - } - - signedPayload = util.ConcatenateBytes(md.Value, metadataSignature.SignatureHeader, BlockHeaderBytes(header)) - - signerIdentity, err = UnmarshalIdentity(signatureHeader.GetCreator()) - if err != nil { - return err - } - } - - signatureSet = append( - signatureSet, - &SignedData{ - Identity: signerIdentity, - Data: signedPayload, - Signature: metadataSignature.Signature, - }, - ) + // Pre-calculate the header bytes for all the signatures. + blockHeaderBytes := BlockHeaderBytes(header) + + signatureSet := make([]*SignedData, 0, len(md.Signatures)) + for _, metadataSignature := range md.Signatures { + if metadataSignature == nil { + continue + } + sigHeader, signerIdentity, err := v.getSigningID(metadataSignature) + if err != nil { + return errors.WithMessagef(err, "for block: %d", header.GetNumber()) + } + if signerIdentity == nil { + // The identifier is not within the consenter set, or no signature header provided. + continue } + signatureSet = append(signatureSet, &SignedData{ + Identity: signerIdentity, + Data: util.ConcatenateBytes(md.Value, sigHeader, blockHeaderBytes), + Signature: metadataSignature.Signature, + }) + } + + return v.Policy.EvaluateSignedData(signatureSet) +} - return policy.EvaluateSignedData(signatureSet) +func (v *BlockSigVerifier) getSigningID(ms *cb.MetadataSignature) (header []byte, identity *msppb.Identity, err error) { + // If the SignatureHeader is empty and the IdentifierHeader is present, + // then the consenter expects us to fetch its identity by its numeric identifier. + if v.BFT && len(ms.SignatureHeader) == 0 && len(ms.IdentifierHeader) > 0 { + identifierHeader, err := UnmarshalIdentifierHeader(ms.IdentifierHeader) + if err != nil { + return nil, nil, errors.Wrap(err, "failed unmarshalling identifier header") + } + identity = v.searchConsenterIdentityByID(identifierHeader.GetIdentifier()) + return ms.IdentifierHeader, identity, nil + } else if len(ms.SignatureHeader) > 0 { + signatureHeader, err := UnmarshalSignatureHeader(ms.SignatureHeader) + if err != nil { + return nil, nil, errors.Wrap(err, "failed unmarshalling signature header") + } + identity, err = UnmarshalIdentity(signatureHeader.GetCreator()) + if err != nil { + return nil, nil, err + } + return ms.SignatureHeader, identity, nil } + return nil, nil, nil } -func searchConsenterIdentityByID(consenters []*cb.Consenter, identifier uint32) *msppb.Identity { - for _, consenter := range consenters { +func (v *BlockSigVerifier) searchConsenterIdentityByID(identifier uint32) *msppb.Identity { + for _, consenter := range v.Consenters { if consenter.Id == identifier { return msppb.NewIdentity(consenter.MspId, consenter.Identity) } @@ -308,6 +313,17 @@ func searchConsenterIdentityByID(consenters []*cb.Consenter, identifier uint32) return nil } +// BlockVerifierFunc is returned by BlockSignatureVerifier. +// It is preserved for backward compatibility. +type BlockVerifierFunc func(header *cb.BlockHeader, metadata *cb.BlockMetadata) error + +// BlockSignatureVerifier is a wrapper for BlockSigVerifier. +// It is preserved for backward compatibility. +func BlockSignatureVerifier(bftEnabled bool, consenters []*cb.Consenter, policy policy) BlockVerifierFunc { + v := BlockSigVerifier{Policy: policy, BFT: bftEnabled, Consenters: consenters} + return v.Verify +} + func VerifyTransactionsAreWellFormed(bd *cb.BlockData) error { if bd == nil || bd.Data == nil || len(bd.Data) == 0 { return fmt.Errorf("empty block") @@ -349,3 +365,16 @@ func VerifyTransactionsAreWellFormed(bd *cb.BlockData) error { return nil } + +// ReadBlockFromFile reads a block from the filesystem. +func ReadBlockFromFile(blockPath string) (*cb.Block, error) { + data, err := os.ReadFile(blockPath) + if err != nil { + return nil, errors.Wrapf(err, "could not read block %s", blockPath) + } + block, err := UnmarshalBlock(data) + if err != nil { + return nil, err + } + return block, nil +} diff --git a/protoutil/unmarshalers.go b/protoutil/unmarshalers.go index e3fa63e21..5df25e8f1 100644 --- a/protoutil/unmarshalers.go +++ b/protoutil/unmarshalers.go @@ -7,11 +7,11 @@ SPDX-License-Identifier: Apache-2.0 package protoutil import ( + "github.com/cockroachdb/errors" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset/kvrwset" "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "github.com/pkg/errors" "google.golang.org/protobuf/proto" "github.com/hyperledger/fabric-x-common/api/msppb" @@ -20,63 +20,63 @@ import ( // the implicit contract of all these unmarshalers is that they // will return a non-nil pointer whenever the error is nil -// UnmarshalBlock unmarshals bytes to a Block +// UnmarshalBlock unmarshals bytes to a Block. func UnmarshalBlock(encoded []byte) (*common.Block, error) { block := &common.Block{} err := proto.Unmarshal(encoded, block) return block, errors.Wrap(err, "error unmarshalling Block") } -// UnmarshalChaincodeDeploymentSpec unmarshals bytes to a ChaincodeDeploymentSpec +// UnmarshalChaincodeDeploymentSpec unmarshals bytes to a ChaincodeDeploymentSpec. func UnmarshalChaincodeDeploymentSpec(code []byte) (*peer.ChaincodeDeploymentSpec, error) { cds := &peer.ChaincodeDeploymentSpec{} err := proto.Unmarshal(code, cds) return cds, errors.Wrap(err, "error unmarshalling ChaincodeDeploymentSpec") } -// UnmarshalChaincodeInvocationSpec unmarshals bytes to a ChaincodeInvocationSpec +// UnmarshalChaincodeInvocationSpec unmarshals bytes to a ChaincodeInvocationSpec. func UnmarshalChaincodeInvocationSpec(encoded []byte) (*peer.ChaincodeInvocationSpec, error) { cis := &peer.ChaincodeInvocationSpec{} err := proto.Unmarshal(encoded, cis) return cis, errors.Wrap(err, "error unmarshalling ChaincodeInvocationSpec") } -// UnmarshalPayload unmarshals bytes to a Payload +// UnmarshalPayload unmarshals bytes to a Payload. func UnmarshalPayload(encoded []byte) (*common.Payload, error) { payload := &common.Payload{} err := proto.Unmarshal(encoded, payload) return payload, errors.Wrap(err, "error unmarshalling Payload") } -// UnmarshalEnvelope unmarshals bytes to a Envelope +// UnmarshalEnvelope unmarshals bytes to a Envelope. func UnmarshalEnvelope(encoded []byte) (*common.Envelope, error) { envelope := &common.Envelope{} err := proto.Unmarshal(encoded, envelope) return envelope, errors.Wrap(err, "error unmarshalling Envelope") } -// UnmarshalChannelHeader unmarshals bytes to a ChannelHeader +// UnmarshalChannelHeader unmarshals bytes to a ChannelHeader. func UnmarshalChannelHeader(bytes []byte) (*common.ChannelHeader, error) { chdr := &common.ChannelHeader{} err := proto.Unmarshal(bytes, chdr) return chdr, errors.Wrap(err, "error unmarshalling ChannelHeader") } -// UnmarshalChaincodeID unmarshals bytes to a ChaincodeID +// UnmarshalChaincodeID unmarshals bytes to a ChaincodeID. func UnmarshalChaincodeID(bytes []byte) (*peer.ChaincodeID, error) { ccid := &peer.ChaincodeID{} err := proto.Unmarshal(bytes, ccid) return ccid, errors.Wrap(err, "error unmarshalling ChaincodeID") } -// UnmarshalSignatureHeader unmarshals bytes to a SignatureHeader +// UnmarshalSignatureHeader unmarshals bytes to a SignatureHeader. func UnmarshalSignatureHeader(bytes []byte) (*common.SignatureHeader, error) { sh := &common.SignatureHeader{} err := proto.Unmarshal(bytes, sh) return sh, errors.Wrap(err, "error unmarshalling SignatureHeader") } -// UnmarshalIdentifierHeader unmarshals bytes to an IdentifierHeader +// UnmarshalIdentifierHeader unmarshals bytes to an IdentifierHeader. func UnmarshalIdentifierHeader(bytes []byte) (*common.IdentifierHeader, error) { ih := &common.IdentifierHeader{} err := proto.Unmarshal(bytes, ih) @@ -90,112 +90,124 @@ func UnmarshalIdentity(bytes []byte) (*msppb.Identity, error) { return id, errors.Wrap(err, "error unmarshalling Identity") } -// UnmarshalHeader unmarshals bytes to a Header +// UnmarshalBlockMetadataSignatures unmarshal bytes to [common.Metadata]. +func UnmarshalBlockMetadataSignatures(bytes [][]byte) (*common.Metadata, error) { + if len(bytes) <= int(common.BlockMetadataIndex_SIGNATURES) { + return nil, errors.Errorf("no signatures in block metadata") + } + md := &common.Metadata{} + if err := proto.Unmarshal(bytes[common.BlockMetadataIndex_SIGNATURES], md); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling signatures from metadata: %v", err) + } + return md, nil +} + +// UnmarshalHeader unmarshals bytes to a Header. func UnmarshalHeader(bytes []byte) (*common.Header, error) { hdr := &common.Header{} err := proto.Unmarshal(bytes, hdr) return hdr, errors.Wrap(err, "error unmarshalling Header") } -// UnmarshalConfigEnvelope unmarshals bytes to a ConfigEnvelope +// UnmarshalConfigEnvelope unmarshals bytes to a ConfigEnvelope. func UnmarshalConfigEnvelope(bytes []byte) (*common.ConfigEnvelope, error) { cfg := &common.ConfigEnvelope{} err := proto.Unmarshal(bytes, cfg) return cfg, errors.Wrap(err, "error unmarshalling ConfigEnvelope") } -// UnmarshalChaincodeHeaderExtension unmarshals bytes to a ChaincodeHeaderExtension +// UnmarshalChaincodeHeaderExtension unmarshals bytes to a ChaincodeHeaderExtension. func UnmarshalChaincodeHeaderExtension(hdrExtension []byte) (*peer.ChaincodeHeaderExtension, error) { chaincodeHdrExt := &peer.ChaincodeHeaderExtension{} err := proto.Unmarshal(hdrExtension, chaincodeHdrExt) return chaincodeHdrExt, errors.Wrap(err, "error unmarshalling ChaincodeHeaderExtension") } -// UnmarshalProposalResponse unmarshals bytes to a ProposalResponse +// UnmarshalProposalResponse unmarshals bytes to a ProposalResponse. func UnmarshalProposalResponse(prBytes []byte) (*peer.ProposalResponse, error) { proposalResponse := &peer.ProposalResponse{} err := proto.Unmarshal(prBytes, proposalResponse) return proposalResponse, errors.Wrap(err, "error unmarshalling ProposalResponse") } -// UnmarshalChaincodeAction unmarshals bytes to a ChaincodeAction +// UnmarshalChaincodeAction unmarshals bytes to a ChaincodeAction. func UnmarshalChaincodeAction(caBytes []byte) (*peer.ChaincodeAction, error) { chaincodeAction := &peer.ChaincodeAction{} err := proto.Unmarshal(caBytes, chaincodeAction) return chaincodeAction, errors.Wrap(err, "error unmarshalling ChaincodeAction") } -// UnmarshalResponse unmarshals bytes to a Response +// UnmarshalResponse unmarshals bytes to a Response. func UnmarshalResponse(resBytes []byte) (*peer.Response, error) { response := &peer.Response{} err := proto.Unmarshal(resBytes, response) return response, errors.Wrap(err, "error unmarshalling Response") } -// UnmarshalChaincodeEvents unmarshals bytes to a ChaincodeEvent +// UnmarshalChaincodeEvents unmarshals bytes to a ChaincodeEvent. func UnmarshalChaincodeEvents(eBytes []byte) (*peer.ChaincodeEvent, error) { chaincodeEvent := &peer.ChaincodeEvent{} err := proto.Unmarshal(eBytes, chaincodeEvent) return chaincodeEvent, errors.Wrap(err, "error unmarshalling ChaicnodeEvent") } -// UnmarshalProposalResponsePayload unmarshals bytes to a ProposalResponsePayload +// UnmarshalProposalResponsePayload unmarshals bytes to a ProposalResponsePayload. func UnmarshalProposalResponsePayload(prpBytes []byte) (*peer.ProposalResponsePayload, error) { prp := &peer.ProposalResponsePayload{} err := proto.Unmarshal(prpBytes, prp) return prp, errors.Wrap(err, "error unmarshalling ProposalResponsePayload") } -// UnmarshalProposal unmarshals bytes to a Proposal +// UnmarshalProposal unmarshals bytes to a Proposal. func UnmarshalProposal(propBytes []byte) (*peer.Proposal, error) { prop := &peer.Proposal{} err := proto.Unmarshal(propBytes, prop) return prop, errors.Wrap(err, "error unmarshalling Proposal") } -// UnmarshalTransaction unmarshals bytes to a Transaction +// UnmarshalTransaction unmarshals bytes to a Transaction. func UnmarshalTransaction(txBytes []byte) (*peer.Transaction, error) { tx := &peer.Transaction{} err := proto.Unmarshal(txBytes, tx) return tx, errors.Wrap(err, "error unmarshalling Transaction") } -// UnmarshalChaincodeActionPayload unmarshals bytes to a ChaincodeActionPayload +// UnmarshalChaincodeActionPayload unmarshals bytes to a ChaincodeActionPayload. func UnmarshalChaincodeActionPayload(capBytes []byte) (*peer.ChaincodeActionPayload, error) { cap := &peer.ChaincodeActionPayload{} err := proto.Unmarshal(capBytes, cap) return cap, errors.Wrap(err, "error unmarshalling ChaincodeActionPayload") } -// UnmarshalChaincodeProposalPayload unmarshals bytes to a ChaincodeProposalPayload +// UnmarshalChaincodeProposalPayload unmarshals bytes to a ChaincodeProposalPayload. func UnmarshalChaincodeProposalPayload(bytes []byte) (*peer.ChaincodeProposalPayload, error) { cpp := &peer.ChaincodeProposalPayload{} err := proto.Unmarshal(bytes, cpp) return cpp, errors.Wrap(err, "error unmarshalling ChaincodeProposalPayload") } -// UnmarshalTxReadWriteSet unmarshals bytes to a TxReadWriteSet +// UnmarshalTxReadWriteSet unmarshals bytes to a TxReadWriteSet. func UnmarshalTxReadWriteSet(bytes []byte) (*rwset.TxReadWriteSet, error) { rws := &rwset.TxReadWriteSet{} err := proto.Unmarshal(bytes, rws) return rws, errors.Wrap(err, "error unmarshalling TxReadWriteSet") } -// UnmarshalKVRWSet unmarshals bytes to a KVRWSet +// UnmarshalKVRWSet unmarshals bytes to a KVRWSet. func UnmarshalKVRWSet(bytes []byte) (*kvrwset.KVRWSet, error) { rws := &kvrwset.KVRWSet{} err := proto.Unmarshal(bytes, rws) return rws, errors.Wrap(err, "error unmarshalling KVRWSet") } -// UnmarshalHashedRWSet unmarshals bytes to a HashedRWSet +// UnmarshalHashedRWSet unmarshals bytes to a HashedRWSet. func UnmarshalHashedRWSet(bytes []byte) (*kvrwset.HashedRWSet, error) { hrws := &kvrwset.HashedRWSet{} err := proto.Unmarshal(bytes, hrws) return hrws, errors.Wrap(err, "error unmarshalling HashedRWSet") } -// UnmarshalSignaturePolicy unmarshals bytes to a SignaturePolicyEnvelope +// UnmarshalSignaturePolicy unmarshals bytes to a SignaturePolicyEnvelope. func UnmarshalSignaturePolicy(bytes []byte) (*common.SignaturePolicyEnvelope, error) { sp := &common.SignaturePolicyEnvelope{} err := proto.Unmarshal(bytes, sp) @@ -203,7 +215,7 @@ func UnmarshalSignaturePolicy(bytes []byte) (*common.SignaturePolicyEnvelope, er } // UnmarshalPayloadOrPanic unmarshals bytes to a Payload structure or panics -// on error +// on error. func UnmarshalPayloadOrPanic(encoded []byte) *common.Payload { payload, err := UnmarshalPayload(encoded) if err != nil { @@ -213,7 +225,7 @@ func UnmarshalPayloadOrPanic(encoded []byte) *common.Payload { } // UnmarshalEnvelopeOrPanic unmarshals bytes to an Envelope structure or panics -// on error +// on error. func UnmarshalEnvelopeOrPanic(encoded []byte) *common.Envelope { envelope, err := UnmarshalEnvelope(encoded) if err != nil { @@ -223,7 +235,7 @@ func UnmarshalEnvelopeOrPanic(encoded []byte) *common.Envelope { } // UnmarshalBlockOrPanic unmarshals bytes to an Block or panics -// on error +// on error. func UnmarshalBlockOrPanic(encoded []byte) *common.Block { block, err := UnmarshalBlock(encoded) if err != nil { @@ -233,7 +245,7 @@ func UnmarshalBlockOrPanic(encoded []byte) *common.Block { } // UnmarshalChannelHeaderOrPanic unmarshals bytes to a ChannelHeader or panics -// on error +// on error. func UnmarshalChannelHeaderOrPanic(bytes []byte) *common.ChannelHeader { chdr, err := UnmarshalChannelHeader(bytes) if err != nil { @@ -243,7 +255,7 @@ func UnmarshalChannelHeaderOrPanic(bytes []byte) *common.ChannelHeader { } // UnmarshalSignatureHeaderOrPanic unmarshals bytes to a SignatureHeader or panics -// on error +// on error. func UnmarshalSignatureHeaderOrPanic(bytes []byte) *common.SignatureHeader { sighdr, err := UnmarshalSignatureHeader(bytes) if err != nil { diff --git a/tools/configtxgen/tools.go b/tools/configtxgen/tools.go index 8b519888e..e9679eb91 100644 --- a/tools/configtxgen/tools.go +++ b/tools/configtxgen/tools.go @@ -88,23 +88,10 @@ func DoOutputChannelCreateTx(conf, baseProfile *Profile, channelID, outputChanne return nil } -// ReadBlock reads a block. -func ReadBlock(blockPath string) (*cb.Block, error) { - data, err := os.ReadFile(blockPath) - if err != nil { - return nil, fmt.Errorf("could not read block %s", blockPath) - } - block, err := protoutil.UnmarshalBlock(data) - if err != nil { - return nil, fmt.Errorf("error unmarshalling to block: %s", err) - } - return block, nil -} - // DoInspectBlock inspects a block from a file. func DoInspectBlock(inspectBlock string) error { logger.Info("Inspecting block") - block, err := ReadBlock(inspectBlock) + block, err := protoutil.ReadBlockFromFile(inspectBlock) if err != nil { return err } diff --git a/tools/configtxgen/tools_test.go b/tools/configtxgen/tools_test.go index a872123b1..3ad714f73 100644 --- a/tools/configtxgen/tools_test.go +++ b/tools/configtxgen/tools_test.go @@ -23,7 +23,8 @@ import ( func TestInspectMissing(t *testing.T) { t.Parallel() - require.EqualError(t, DoInspectBlock("NonSenseBlockFileThatDoesn'tActuallyExist"), "could not read block NonSenseBlockFileThatDoesn'tActuallyExist") + err := DoInspectBlock("NonSenseBlockFileThatDoesn'tActuallyExist") + require.ErrorContains(t, err, "could not read block NonSenseBlockFileThatDoesn'tActuallyExist") } func TestInspectBlock(t *testing.T) { @@ -40,8 +41,9 @@ func TestInspectBlockErr(t *testing.T) { t.Parallel() config := Load(SampleAppChannelInsecureSoloProfile, configtest.GetDevConfigDir()) - require.EqualError(t, DoOutputBlock(config, "foo", ""), "error writing genesis block: open : no such file or directory") - require.EqualError(t, DoInspectBlock(""), "could not read block ") + err := DoOutputBlock(config, "foo", "") + require.EqualError(t, err, "error writing genesis block: open : no such file or directory") + require.ErrorContains(t, DoInspectBlock(""), "could not read block ") } func TestMissingOrdererSection(t *testing.T) { @@ -51,7 +53,8 @@ func TestMissingOrdererSection(t *testing.T) { config := Load(SampleAppChannelInsecureSoloProfile, configtest.GetDevConfigDir()) config.Orderer = nil - require.EqualError(t, DoOutputBlock(config, "foo", blockDest), "refusing to generate block which is missing orderer section") + err := DoOutputBlock(config, "foo", blockDest) + require.EqualError(t, err, "refusing to generate block which is missing orderer section") } func TestApplicationChannelGenesisBlock(t *testing.T) { @@ -70,7 +73,8 @@ func TestApplicationChannelMissingApplicationSection(t *testing.T) { config := Load(SampleAppChannelInsecureSoloProfile, configtest.GetDevConfigDir()) config.Application = nil - require.EqualError(t, DoOutputBlock(config, "foo", blockDest), "refusing to generate application channel block which is missing application section") + err := DoOutputBlock(config, "foo", blockDest) + require.EqualError(t, err, "refusing to generate application channel block which is missing application section") } func TestMissingConsortiumValue(t *testing.T) { @@ -80,7 +84,9 @@ func TestMissingConsortiumValue(t *testing.T) { config := Load(SampleSingleMSPChannelProfile, configtest.GetDevConfigDir()) config.Consortium = "" - require.EqualError(t, DoOutputChannelCreateTx(config, nil, "foo", configTxDest), "config update generation failure: cannot define a new channel with no Consortium value") + err := DoOutputChannelCreateTx(config, nil, "foo", configTxDest) + require.EqualError(t, err, + "config update generation failure: cannot define a new channel with no Consortium value") } func TestUnsuccessfulChannelTxFileCreation(t *testing.T) { @@ -89,7 +95,9 @@ func TestUnsuccessfulChannelTxFileCreation(t *testing.T) { config := Load(SampleSingleMSPChannelProfile, configtest.GetDevConfigDir()) require.NoError(t, os.WriteFile(configTxDest, []byte{}, 0o440)) - require.EqualError(t, DoOutputChannelCreateTx(config, nil, "foo", configTxDest), fmt.Sprintf("error writing channel create tx: open %s: permission denied", configTxDest)) + err := DoOutputChannelCreateTx(config, nil, "foo", configTxDest) + require.EqualError(t, err, + fmt.Sprintf("error writing channel create tx: open %s: permission denied", configTxDest)) } func TestMissingApplicationValue(t *testing.T) { @@ -99,12 +107,16 @@ func TestMissingApplicationValue(t *testing.T) { config := Load(SampleSingleMSPChannelProfile, configtest.GetDevConfigDir()) config.Application = nil - require.EqualError(t, DoOutputChannelCreateTx(config, nil, "foo", configTxDest), "could not generate default config template: channel template configs must contain an application section") + err := DoOutputChannelCreateTx(config, nil, "foo", configTxDest) + require.EqualError(t, err, "could not generate default config template: "+ + "channel template configs must contain an application section") } func TestInspectMissingConfigTx(t *testing.T) { t.Parallel() - require.EqualError(t, DoInspectChannelCreateTx("ChannelCreateTxFileWhichDoesn'tReallyExist"), "could not read channel create tx: open ChannelCreateTxFileWhichDoesn'tReallyExist: no such file or directory") + err := DoInspectChannelCreateTx("ChannelCreateTxFileWhichDoesn'tReallyExist") + require.EqualError(t, err, "could not read channel create tx: "+ + "open ChannelCreateTxFileWhichDoesn'tReallyExist: no such file or directory") } func TestInspectConfigTx(t *testing.T) { @@ -113,7 +125,8 @@ func TestInspectConfigTx(t *testing.T) { config := Load(SampleSingleMSPChannelProfile, configtest.GetDevConfigDir()) - require.NoError(t, DoOutputChannelCreateTx(config, nil, "foo", configTxDest), "Good outputChannelCreateTx generation request") + err := DoOutputChannelCreateTx(config, nil, "foo", configTxDest) + require.NoError(t, err, "Good outputChannelCreateTx generation request") require.NoError(t, DoInspectChannelCreateTx(configTxDest), "Good configtx inspection request") } @@ -202,7 +215,7 @@ func TestFabricXGenesisBlock(t *testing.T) { config.Orderer.Arma.Path = armaPath require.NoError(t, DoOutputBlock(config, "foo", blockDest)) - configBlock, err := ReadBlock(blockDest) + configBlock, err := protoutil.ReadBlockFromFile(blockDest) require.NoError(t, err) require.NotNil(t, configBlock)