Skip to content

Commit

Permalink
[FAB-996] Introduce orderer/commons/util package
Browse files Browse the repository at this point in the history
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 <kostas@christidis.io>
  • Loading branch information
kchristidis committed Nov 15, 2016
1 parent 9bd29d3 commit cb9a29b
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 109 deletions.
109 changes: 21 additions & 88 deletions orderer/common/bootstrap/static/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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: &timestamp.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{
Expand Down
28 changes: 13 additions & 15 deletions orderer/common/bootstrap/static/static_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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
Expand Down
184 changes: 184 additions & 0 deletions orderer/common/util/util.go
Original file line number Diff line number Diff line change
@@ -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: &timestamp.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")
}
}

0 comments on commit cb9a29b

Please sign in to comment.