From cb9a29bdeba224776b316a37fc2204eab019ecdc Mon Sep 17 00:00:00 2001 From: Kostas Christidis Date: Sun, 13 Nov 2016 04:17:59 -0500 Subject: [PATCH] [FAB-996] Introduce orderer/commons/util package https://jira.hyperledger.org/browse/FAB-996 The goal is to avoid rewriting the same functions again and again. For functions that may return an error, two variants and provided: - The plain one that returns the error and allows the caller to handle it - The *OrPanic one that panics if an error comes up. It makes sense to use the *OrPanic functions in contexts where you either know the chances of an error are zero, or you're willing to defer/recover in the calling code. The static package is a perfect example of this. (Also see note below.) The *OrPanic functions can be used inline as function arguments since they return a single object. This changeset ripples into the static bootstrapper, simplifying it substantially, and providing a clear guide into creating a sample genesis block. The Make* functions of the util package are tested in the static bootstrapper's tests. Unit tests for functions that just create proto messages don't make sense, as any errors in the message construction results in immediate 'go build' errors. (All the other functions in the util package come with unit tests, as they should.) UPDATE: Revised for the latest changes in protos. Change-Id: I4212d1366cca556fe4c5c6cb16ea3c1f52dd7503 Signed-off-by: Kostas Christidis --- orderer/common/bootstrap/static/static.go | 109 ++--------- .../common/bootstrap/static/static_test.go | 28 ++- orderer/common/util/util.go | 184 ++++++++++++++++++ orderer/common/util/util_test.go | 135 +++++++++++++ orderer/main.go | 9 +- 5 files changed, 356 insertions(+), 109 deletions(-) create mode 100644 orderer/common/util/util.go create mode 100644 orderer/common/util/util_test.go diff --git a/orderer/common/bootstrap/static/static.go b/orderer/common/bootstrap/static/static.go index b133e9de2d5..5d7c046d95f 100644 --- a/orderer/common/bootstrap/static/static.go +++ b/orderer/common/bootstrap/static/static.go @@ -18,18 +18,17 @@ package static import ( "fmt" - "time" "github.com/hyperledger/fabric/core/crypto/primitives" "github.com/hyperledger/fabric/orderer/common/bootstrap" "github.com/hyperledger/fabric/orderer/common/cauthdsl" "github.com/hyperledger/fabric/orderer/common/configtx" + "github.com/hyperledger/fabric/orderer/common/util" cb "github.com/hyperledger/fabric/protos/common" - - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/timestamp" ) +const msgVersion = int32(1) + type bootstrapper struct { chainID []byte } @@ -43,93 +42,27 @@ func New() bootstrap.Helper { return &bootstrapper{chainID} } -// errorlessMarshal prevents poluting this code with panics -// If the genesis block cannot be created, the system cannot start so panic is correct -func errorlessMarshal(thing proto.Message) []byte { - data, err := proto.Marshal(thing) - if err != nil { - panic(err) - } - return data -} - -func makeChainHeader(headerType cb.HeaderType, version int32, chainID []byte, epoch uint64) *cb.ChainHeader { - return &cb.ChainHeader{ - Type: int32(headerType), - Version: version, - Timestamp: ×tamp.Timestamp{ - Seconds: time.Now().Unix(), - Nanos: 0, - }, - ChainID: chainID, - Epoch: epoch, - } -} - -func makeSignatureHeader(serializedCreatorCertChain []byte, nonce []byte) *cb.SignatureHeader { - return &cb.SignatureHeader{ - Creator: serializedCreatorCertChain, - Nonce: nonce, - } -} - -func (b *bootstrapper) makeSignedConfigurationItem(configurationItemType cb.ConfigurationItem_ConfigurationType, modificationPolicyID string, key string, value []byte) *cb.SignedConfigurationItem { - marshaledConfigurationItem := errorlessMarshal(&cb.ConfigurationItem{ - Header: makeChainHeader(cb.HeaderType_CONFIGURATION_ITEM, 1, b.chainID, 0), - Type: configurationItemType, - LastModified: 0, - ModificationPolicy: modificationPolicyID, - Key: key, - Value: value, - }) - - return &cb.SignedConfigurationItem{ - ConfigurationItem: marshaledConfigurationItem, - Signatures: nil, - } -} - -func (b *bootstrapper) makeConfigurationEnvelope(items ...*cb.SignedConfigurationItem) *cb.ConfigurationEnvelope { - return &cb.ConfigurationEnvelope{ - Items: items, - } -} - -func (b *bootstrapper) makeEnvelope(configurationEnvelope *cb.ConfigurationEnvelope) *cb.Envelope { - nonce, err := primitives.GetRandomNonce() - if err != nil { - panic(fmt.Errorf("Cannot generate random nonce: %s", err)) - } - marshaledPayload := errorlessMarshal(&cb.Payload{ - Header: &cb.Header{ - ChainHeader: makeChainHeader(cb.HeaderType_CONFIGURATION_TRANSACTION, 1, b.chainID, 0), - SignatureHeader: makeSignatureHeader(nil, nonce), - }, - Data: errorlessMarshal(configurationEnvelope), - }) - return &cb.Envelope{ - Payload: marshaledPayload, - Signature: nil, - } -} - -func sigPolicyToPolicy(sigPolicy *cb.SignaturePolicyEnvelope) []byte { - policy := &cb.Policy{ - Type: &cb.Policy_SignaturePolicy{ - SignaturePolicy: sigPolicy, - }, - } - return errorlessMarshal(policy) -} - // GenesisBlock returns the genesis block to be used for bootstrapping func (b *bootstrapper) GenesisBlock() (*cb.Block, error) { // Lock down the default modification policy to prevent any further policy modifications - lockdownDefaultModificationPolicy := b.makeSignedConfigurationItem(cb.ConfigurationItem_Policy, configtx.DefaultModificationPolicyID, configtx.DefaultModificationPolicyID, sigPolicyToPolicy(cauthdsl.RejectAllPolicy)) - - blockData := &cb.BlockData{ - Data: [][]byte{errorlessMarshal(b.makeEnvelope(b.makeConfigurationEnvelope(lockdownDefaultModificationPolicy)))}, - } + configItemKey := configtx.DefaultModificationPolicyID + configItemValue := util.MarshalOrPanic(util.MakePolicyOrPanic(cauthdsl.RejectAllPolicy)) + modPolicy := configtx.DefaultModificationPolicyID + + lastModified := uint64(0) + epoch := uint64(0) + configItemChainHeader := util.MakeChainHeader(cb.HeaderType_CONFIGURATION_ITEM, msgVersion, b.chainID, epoch) + configItem := util.MakeConfigurationItem(configItemChainHeader, cb.ConfigurationItem_Policy, lastModified, modPolicy, configItemKey, configItemValue) + signedConfigItem := &cb.SignedConfigurationItem{ConfigurationItem: util.MarshalOrPanic(configItem), Signatures: nil} + + configEnvelope := util.MakeConfigurationEnvelope(signedConfigItem) + payloadChainHeader := util.MakeChainHeader(cb.HeaderType_CONFIGURATION_TRANSACTION, configItemChainHeader.Version, b.chainID, epoch) + payloadSignatureHeader := util.MakeSignatureHeader(nil, util.CreateNonceOrPanic()) + payloadHeader := util.MakePayloadHeader(payloadChainHeader, payloadSignatureHeader) + payload := &cb.Payload{Header: payloadHeader, Data: util.MarshalOrPanic(configEnvelope)} + envelope := &cb.Envelope{Payload: util.MarshalOrPanic(payload), Signature: nil} + + blockData := &cb.BlockData{Data: [][]byte{util.MarshalOrPanic(envelope)}} return &cb.Block{ Header: &cb.BlockHeader{ diff --git a/orderer/common/bootstrap/static/static_test.go b/orderer/common/bootstrap/static/static_test.go index 888ed85a205..cde51e1b127 100644 --- a/orderer/common/bootstrap/static/static_test.go +++ b/orderer/common/bootstrap/static/static_test.go @@ -20,11 +20,11 @@ import ( "bytes" "testing" + "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/orderer/common/cauthdsl" "github.com/hyperledger/fabric/orderer/common/configtx" + "github.com/hyperledger/fabric/orderer/common/util" cb "github.com/hyperledger/fabric/protos/common" - - "github.com/golang/protobuf/proto" ) func TestGenesisBlockCreation(t *testing.T) { @@ -49,13 +49,19 @@ func TestGenesisBlockHeader(t *testing.T) { } func TestGenesisBlockData(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("OrPanicked unexpectedly: %s", r) + } + }() + expectedBlockDataLength := 1 expectedPayloadChainHeaderType := int32(cb.HeaderType_CONFIGURATION_TRANSACTION) - expectedChainHeaderVersion := int32(1) + expectedChainHeaderVersion := msgVersion expectedChainHeaderEpoch := uint64(0) expectedConfigEnvelopeItemsLength := 1 expectedConfigurationItemChainHeaderType := int32(cb.HeaderType_CONFIGURATION_ITEM) - expectedConfigurationItemChainHeaderVersion := int32(1) + expectedConfigurationItemChainHeaderVersion := msgVersion expectedConfigurationItemType := cb.ConfigurationItem_Policy expectedConfigEnvelopeSequence := uint64(0) expectedConfigurationItemModificationPolicy := configtx.DefaultModificationPolicyID @@ -67,22 +73,14 @@ func TestGenesisBlockData(t *testing.T) { t.Fatalf("Expected genesis block data length %d, got %d", expectedBlockDataLength, len(genesisBlock.Data.Data)) } - marshaledEnvelope := genesisBlock.Data.Data[0] - envelope := &cb.Envelope{} - if err := proto.Unmarshal(marshaledEnvelope, envelope); err != nil { - t.Fatalf("Expected genesis block to carry an Envelope") - } + envelope := util.ExtractEnvelopeOrPanic(genesisBlock, 0) envelopeSignature := envelope.Signature if !bytes.Equal(envelopeSignature, nil) { t.Fatalf("Expected envelope signature to be nil, got %x", envelopeSignature) } - marshaledPayload := envelope.Payload - payload := &cb.Payload{} - if err := proto.Unmarshal(marshaledPayload, payload); err != nil { - t.Fatalf("Expected genesis block to carry a Payload") - } + payload := util.ExtractPayloadOrPanic(envelope) signatureHeader := payload.Header.SignatureHeader if !bytes.Equal(signatureHeader.Creator, nil) { @@ -100,7 +98,7 @@ func TestGenesisBlockData(t *testing.T) { t.Fatalf("Expected payload chain header version %d, got %d", expectedChainHeaderVersion, payloadChainHeader.Version) } if payloadChainHeader.Epoch != expectedChainHeaderEpoch { - t.Fatalf("Expected payload chain header epoch to be %d, got %d", expectedChainHeaderEpoch, payloadChainHeader.Epoch) + t.Fatalf("Expected payload chain header header epoch to be %d, got %d", expectedChainHeaderEpoch, payloadChainHeader.Epoch) } marshaledConfigurationEnvelope := payload.Data diff --git a/orderer/common/util/util.go b/orderer/common/util/util.go new file mode 100644 index 00000000000..cc390e35cab --- /dev/null +++ b/orderer/common/util/util.go @@ -0,0 +1,184 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "time" + + "github.com/hyperledger/fabric/core/crypto/primitives" + cb "github.com/hyperledger/fabric/protos/common" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/timestamp" +) + +// MarshalOrPanic serializes a protobuf message and panics if this operation fails. +func MarshalOrPanic(pb proto.Message) []byte { + data, err := proto.Marshal(pb) + if err != nil { + panic(err) + } + return data +} + +// Marshal serializes a protobuf message. +func Marshal(pb proto.Message) ([]byte, error) { + return proto.Marshal(pb) +} + +// CreateNonceOrPanic generates a nonce using the crypto/primitives package +// and panics if this operation fails. +func CreateNonceOrPanic() []byte { + nonce, err := primitives.GetRandomNonce() + if err != nil { + panic(fmt.Errorf("Cannot generate random nonce: %s", err)) + } + return nonce +} + +// CreateNonce generates a nonce using the crypto/primitives package. +func CreateNonce() ([]byte, error) { + nonce, err := primitives.GetRandomNonce() + if err != nil { + return nil, fmt.Errorf("Cannot generate random nonce: %s", err) + } + return nonce, nil +} + +// ExtractEnvelopeOrPanic retrieves the requested envelope from a given block and unmarshals it -- it panics if either of these operation fail. +func ExtractEnvelopeOrPanic(block *cb.Block, index int) *cb.Envelope { + envelopeCount := len(block.Data.Data) + if index < 0 || index >= envelopeCount { + panic("Envelope index out of bounds") + } + marshaledEnvelope := block.Data.Data[index] + envelope := &cb.Envelope{} + if err := proto.Unmarshal(marshaledEnvelope, envelope); err != nil { + panic(fmt.Errorf("Block data does not carry an envelope at index %d: %s", index, err)) + } + return envelope +} + +// ExtractEnvelope retrieves the requested envelope from a given block and unmarshals it. +func ExtractEnvelope(block *cb.Block, index int) (*cb.Envelope, error) { + envelopeCount := len(block.Data.Data) + if index < 0 || index >= envelopeCount { + return nil, fmt.Errorf("Envelope index out of bounds") + } + marshaledEnvelope := block.Data.Data[index] + envelope := &cb.Envelope{} + if err := proto.Unmarshal(marshaledEnvelope, envelope); err != nil { + return nil, fmt.Errorf("Block data does not carry an envelope at index %d: %s", index, err) + } + return envelope, nil +} + +// ExtractPayloadOrPanic retrieves the payload of a given envelope and unmarshals it -- it panics if either of these operations fail. +func ExtractPayloadOrPanic(envelope *cb.Envelope) *cb.Payload { + payload := &cb.Payload{} + if err := proto.Unmarshal(envelope.Payload, payload); err != nil { + panic(fmt.Errorf("Envelope does not carry a Payload: %s", err)) + } + return payload +} + +// ExtractPayload retrieves the payload of a given envelope and unmarshals it. +func ExtractPayload(envelope *cb.Envelope) (*cb.Payload, error) { + payload := &cb.Payload{} + if err := proto.Unmarshal(envelope.Payload, payload); err != nil { + return nil, fmt.Errorf("Envelope does not carry a Payload: %s", err) + } + return payload, nil +} + +// MakeChainHeader creates a ChainHeader. +func MakeChainHeader(headerType cb.HeaderType, version int32, chainID []byte, epoch uint64) *cb.ChainHeader { + return &cb.ChainHeader{ + Type: int32(headerType), + Version: version, + Timestamp: ×tamp.Timestamp{ + Seconds: time.Now().Unix(), + Nanos: 0, + }, + ChainID: chainID, + Epoch: epoch, + } +} + +// MakeSignatureHeader creates a SignatureHeader. +func MakeSignatureHeader(serializedCreatorCertChain []byte, nonce []byte) *cb.SignatureHeader { + return &cb.SignatureHeader{ + Creator: serializedCreatorCertChain, + Nonce: nonce, + } +} + +// MakePayloadHeader creates a Payload Header. +func MakePayloadHeader(ch *cb.ChainHeader, sh *cb.SignatureHeader) *cb.Header { + return &cb.Header{ + ChainHeader: ch, + SignatureHeader: sh, + } +} + +// MakeConfigurationItem makes a ConfigurationItem. +func MakeConfigurationItem(ch *cb.ChainHeader, configItemType cb.ConfigurationItem_ConfigurationType, lastModified uint64, modPolicyID string, key string, value []byte) *cb.ConfigurationItem { + return &cb.ConfigurationItem{ + Header: ch, + Type: configItemType, + LastModified: lastModified, + ModificationPolicy: modPolicyID, + Key: key, + Value: value, + } +} + +// MakeConfigurationEnvelope makes a ConfigurationEnvelope. +func MakeConfigurationEnvelope(items ...*cb.SignedConfigurationItem) *cb.ConfigurationEnvelope { + return &cb.ConfigurationEnvelope{Items: items} +} + +// MakePolicyOrPanic creates a Policy proto message out of a SignaturePolicyEnvelope, and panics if this operation fails. +// NOTE Expand this as more policy types as supported. +func MakePolicyOrPanic(policyEnvelope interface{}) *cb.Policy { + switch pe := policyEnvelope.(type) { + case *cb.SignaturePolicyEnvelope: + return &cb.Policy{ + Type: &cb.Policy_SignaturePolicy{ + SignaturePolicy: pe, + }, + } + default: + panic("Unknown policy envelope type given") + } +} + +// MakePolicy creates a Policy proto message out of a SignaturePolicyEnvelope. +// NOTE Expand this as more policy types as supported. +func MakePolicy(policyEnvelope interface{}) (*cb.Policy, error) { + switch pe := policyEnvelope.(type) { + case *cb.SignaturePolicyEnvelope: + return &cb.Policy{ + Type: &cb.Policy_SignaturePolicy{ + SignaturePolicy: pe, + }, + }, nil + default: + return nil, fmt.Errorf("Unknown policy envelope type given") + } +} diff --git a/orderer/common/util/util_test.go b/orderer/common/util/util_test.go new file mode 100644 index 00000000000..6178f2a6a0f --- /dev/null +++ b/orderer/common/util/util_test.go @@ -0,0 +1,135 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "bytes" + "testing" + + "github.com/hyperledger/fabric/core/crypto/primitives" + + "github.com/golang/protobuf/proto" + cb "github.com/hyperledger/fabric/protos/common" +) + +func TestNonceRandomness(t *testing.T) { + n1, err := CreateNonce() + if err != nil { + t.Fatal(err) + } + n2, err := CreateNonce() + if err != nil { + t.Fatal(err) + } + if bytes.Equal(n1, n2) { + t.Fatalf("Expected nonces to be different, got %x and %x", n1, n2) + } +} + +func TestNonceLength(t *testing.T) { + n, err := CreateNonce() + if err != nil { + t.Fatal(err) + } + actual := len(n) + expected := primitives.NonceSize + if actual != expected { + t.Fatalf("Expected nonce to be of size %d, got %d instead", expected, actual) + } + +} + +func TestExtractEnvelopeWrongIndex(t *testing.T) { + block := testBlock() + if _, err := ExtractEnvelope(block, len(block.GetData().Data)); err == nil { + t.Fatal("Expected envelope extraction to fail (wrong index)") + } +} + +func TestExtractEnvelopeWrongIndexOrPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("Expected envelope extraction to panic (wrong index)") + } + }() + + block := testBlock() + ExtractEnvelopeOrPanic(block, len(block.GetData().Data)) +} + +func TestExtractEnvelope(t *testing.T) { + if envelope, err := ExtractEnvelope(testBlock(), 0); err != nil { + t.Fatalf("Expected envelop extraction to succeed: %s", err) + } else if !proto.Equal(envelope, testEnvelope()) { + t.Fatal("Expected extracted envelope to match test envelope") + } +} + +func TestExtractEnvelopeOrPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatal("Expected envelope extraction to succeed") + } + }() + + if !proto.Equal(ExtractEnvelopeOrPanic(testBlock(), 0), testEnvelope()) { + t.Fatal("Expected extracted envelope to match test envelope") + } +} + +func TestExtractPayload(t *testing.T) { + if payload, err := ExtractPayload(testEnvelope()); err != nil { + t.Fatalf("Expected payload extraction to succeed: %s", err) + } else if !proto.Equal(payload, testPayload()) { + t.Fatal("Expected extracted payload to match test payload") + } +} + +func TestExtractPayloadOrPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatal("Expected payload extraction to succeed") + } + }() + + if !proto.Equal(ExtractPayloadOrPanic(testEnvelope()), testPayload()) { + t.Fatal("Expected extracted payload to match test payload") + } +} + +// Helper functions + +func testPayload() *cb.Payload { + return &cb.Payload{ + Header: MakePayloadHeader(MakeChainHeader(cb.HeaderType_MESSAGE, int32(1), []byte("test"), 0), nil), + Data: []byte("test"), + } +} + +func testEnvelope() *cb.Envelope { + // No need to set the signature + return &cb.Envelope{Payload: MarshalOrPanic(testPayload())} +} + +func testBlock() *cb.Block { + // No need to set the block's Header, or Metadata + return &cb.Block{ + Data: &cb.BlockData{ + Data: [][]byte{MarshalOrPanic(testEnvelope())}, + }, + } +} diff --git a/orderer/main.go b/orderer/main.go index 988604c0084..37bd53f9f87 100644 --- a/orderer/main.go +++ b/orderer/main.go @@ -33,6 +33,7 @@ import ( // "github.com/hyperledger/fabric/orderer/common/broadcastfilter/configfilter" "github.com/hyperledger/fabric/orderer/common/configtx" "github.com/hyperledger/fabric/orderer/common/policies" + "github.com/hyperledger/fabric/orderer/common/util" "github.com/hyperledger/fabric/orderer/config" "github.com/hyperledger/fabric/orderer/kafka" "github.com/hyperledger/fabric/orderer/rawledger" @@ -104,12 +105,8 @@ func retrieveConfiguration(rl rawledger.Reader) *cb.ConfigurationEnvelope { if len(block.Data.Data) != 1 { continue } - if err := proto.Unmarshal(block.Data.Data[0], envelope); err != nil { - panic(fmt.Errorf("Block doesn't carry a message envelope: %s", err)) - } - if err := proto.Unmarshal(envelope.Payload, payload); err != nil { - panic(fmt.Errorf("Message envelope doesn't carry a payload: %s", err)) - } + envelope = util.ExtractEnvelopeOrPanic(block, 0) + payload = util.ExtractPayloadOrPanic(envelope) if payload.Header.ChainHeader.Type != int32(cb.HeaderType_CONFIGURATION_TRANSACTION) { continue }