Skip to content

Commit

Permalink
[FAB-1094] util to parse config tx blocks
Browse files Browse the repository at this point in the history
These functions parse a Block into Envelopes, Payloads,
ConfigurationEnvelopes and ConfigurationItems

Each function has an *OrPanic version which can be used
when the caller wants to stop all operations on
error. These can also be useful when making an inline
call.

added unit tests, functions from Kosta's [FAB-996] and
updates from Murali's comments

11/15/2016 added *OrPanic versions of the functions,
updated code per comments

11/16/2016 clean up *OrPanic function signature

11/17/2016 force new commit to clean up gerrit patch setss

Change-Id: Ic47cfed75652896b53115fb46b096acf0cb2f86a
Signed-off-by: tuand27613 <tdang@us.ibm.com>
  • Loading branch information
tuand27613 committed Nov 17, 2016
1 parent 3b6c70d commit 60706a7
Show file tree
Hide file tree
Showing 2 changed files with 384 additions and 0 deletions.
158 changes: 158 additions & 0 deletions protos/utils/configtxutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
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 utils

import (
"fmt"

pb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

// no need to break out block header as none of its parts are serialized

// no need to break out block metadata as it's just a byte slice

// no need to break Block into constituents. Nothing to unmarshall

// BreakOutBlockDataOrPanic executes BreakOutBlockData() but panics on error
func BreakOutBlockDataOrPanic(blockData *pb.BlockData) ([]*pb.Payload, [][]byte) {
payloads, envelopeSignatures, err := BreakOutBlockData(blockData)
if err != nil {
panic(err)
}
return payloads, envelopeSignatures
} // BreakOutBlockDataOrPanic

// BreakOutBlockData decomposes a blockData into its payloads and signatures.
// since a BlockData contains a slice of Envelopes, this functions returns slices of Payloads and Envelope.Signatures.
// the Payload/Signature pair has the same array index
func BreakOutBlockData(blockData *pb.BlockData) ([]*pb.Payload, [][]byte, error) {
var err error

var envelopeSignatures [][]byte
var payloads []*pb.Payload

var envelope *pb.Envelope
var payload *pb.Payload
for _, envelopeBytes := range blockData.Data {
envelope = &pb.Envelope{}
err = proto.Unmarshal(envelopeBytes, envelope)
if err != nil {
return nil, nil, err
}
payload = &pb.Payload{}
err = proto.Unmarshal(envelope.Payload, payload)
if err != nil {
return nil, nil, err
}
envelopeSignatures = append(envelopeSignatures, envelope.Signature)
payloads = append(payloads, payload)
}

return payloads, envelopeSignatures, nil
} // BreakOutBlockData

// BreakOutPayloadDataToConfigurationEnvelopeOrPanic calls BreakOutPayloadDataToConfigurationEnvelope() but panics on error
func BreakOutPayloadDataToConfigurationEnvelopeOrPanic(payloadData []byte) *pb.ConfigurationEnvelope {
configEnvelope, err := BreakOutPayloadDataToConfigurationEnvelope(payloadData)
if err != nil {
panic(err)
}
return configEnvelope
} // BreakOutPayloadDataToConfigurationEnvelopeOrPanic

// BreakOutPayloadDataToConfigurationEnvelope decomposes a Payload.Data item into its constituent ConfigurationEnvelope
func BreakOutPayloadDataToConfigurationEnvelope(payloadData []byte) (*pb.ConfigurationEnvelope, error) {
if payloadData == nil {
return nil, fmt.Errorf("input Payload data is null\n")
}

configEnvelope := &pb.ConfigurationEnvelope{}
err := proto.Unmarshal(payloadData, configEnvelope)
if err != nil {
return nil, err
}

return configEnvelope, nil
} //BreakOutPayloadToConfigurationEnvelope

// BreakOutConfigEnvelopeToConfigItemsOrPanic calls BreakOutConfigEnvelopeToConfigItems() but panics on error
func BreakOutConfigEnvelopeToConfigItemsOrPanic(configEnvelope *pb.ConfigurationEnvelope) ([]*pb.ConfigurationItem, [][]*pb.ConfigurationSignature) {
configItems, configSignatures, err := BreakOutConfigEnvelopeToConfigItems(configEnvelope)
if err != nil {
panic(err)
}
return configItems, configSignatures
} // BreakOutConfigEnvelopeToConfigItemsOrPanic

// BreakOutConfigEnvelopeToConfigItems decomposes a ConfigurationEnvelope to its constituent ConfigurationItems and ConfigurationSignatures
// Note that a ConfigurationItem can have multiple signatures so each element in the returned ConfigurationItems slice is associated with a slice of ConfigurationSignatures
func BreakOutConfigEnvelopeToConfigItems(configEnvelope *pb.ConfigurationEnvelope) ([]*pb.ConfigurationItem, [][]*pb.ConfigurationSignature, error) {
if configEnvelope == nil {
return nil, nil, fmt.Errorf("BreakOutConfigEnvelopeToConfigItems received null input\n")
}

var configItems []*pb.ConfigurationItem
var configSignatures [][]*pb.ConfigurationSignature

var err error
var configItem *pb.ConfigurationItem
for i, signedConfigItem := range configEnvelope.Items {
configItem = &pb.ConfigurationItem{}
err = proto.Unmarshal(signedConfigItem.ConfigurationItem, configItem)
if err != nil {
return nil, nil, fmt.Errorf("BreakOutConfigEnvelopToConfigItems cannot unmarshall signedConfigurationItem: %v\n", err)
}
configItems = append(configItems, configItem)
for _, signedConfigItemSignature := range signedConfigItem.Signatures {
configSignatures[i] = append(configSignatures[i], signedConfigItemSignature)
}
}

return configItems, configSignatures, nil
} // BreakOutConfigEnvelopeToConfigItems

// BreakOutBlockToConfigurationEnvelopeOrPanic calls BreakOutBlockToConfigurationEnvelope() but panics on error
func BreakOutBlockToConfigurationEnvelopeOrPanic(block *pb.Block) (*pb.ConfigurationEnvelope, []byte) {
configEnvelope, envelopeSignature, err := BreakOutBlockToConfigurationEnvelope(block)
if err != nil {
panic(err)
}
return configEnvelope, envelopeSignature
} // BreakOutBlockToConfigurationEnvelopeOrPanic

// BreakOutBlockToConfigurationEnvelope decomposes a configuration transaction Block to its ConfigurationEnvelope
func BreakOutBlockToConfigurationEnvelope(block *pb.Block) (*pb.ConfigurationEnvelope, []byte, error) {
if block == nil || block.Data == nil || len(block.Data.Data) > 1 {
return nil, nil, fmt.Errorf("Block.BlockData is not an array of 1. This is not a configuration transaction\n")
}

payloads, envelopeSignatures, err := BreakOutBlockData(block.Data)

if payloads[0].Header.ChainHeader.Type != int32(pb.HeaderType_CONFIGURATION_TRANSACTION) {
return nil, nil, fmt.Errorf("Payload Header type is not configuration_transaction. This is not a configuration transaction\n")
}
var configEnvelope *pb.ConfigurationEnvelope
configEnvelope, err = BreakOutPayloadDataToConfigurationEnvelope(payloads[0].Data)
if err != nil {
return nil, nil, fmt.Errorf("Error breaking out configurationEnvelope: %v\n", err)
}

return configEnvelope, envelopeSignatures[0], nil
} // BreakOutPayloadToConfigurationEnvelope
226 changes: 226 additions & 0 deletions protos/utils/configtxutils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
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 utils

import (
"bytes"
"testing"
"time"

pb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/timestamp"
)

// added comment to force gerrit commit

func TestBreakOutBlockDataBadData(t *testing.T) {
fakeBlockData := &pb.BlockData{}
payloads, sigs, _ := BreakOutBlockData(fakeBlockData)
if len(payloads) > 0 || len(sigs) > 0 {
t.Errorf("TestBreakOutBlockData should not work with blank input.\n")
} // TestBreakOutBlockDataBadData
} // TestBreakOutBlockDataBadData

func TestBreakOutBlockData(t *testing.T) {
block := testBlock()
payloads, _, _ := BreakOutBlockData(block.Data) // TODO: test for signature
if len(payloads) != 1 {
t.Errorf("TestBreakOutBlock did not unmarshall to array of 1 payloads\n")
}
if payloads[0].Header.ChainHeader.Version != 1 || payloads[0].Header.ChainHeader.Type != int32(pb.HeaderType_CONFIGURATION_TRANSACTION) || !bytes.Equal(payloads[0].Header.ChainHeader.ChainID, []byte("test")) {
t.Errorf("TestBreakOutBlockData payload header is %+v . Expected type is %v and Version == 1\n", payloads[0].Header.ChainHeader, int32(pb.HeaderType_CONFIGURATION_TRANSACTION))
}
if !bytes.Equal(payloads[0].Data, []byte("test")) {
t.Errorf("TestBreakOutBlockData payload data is %s . Expected 'test'\n", payloads[0].Data)
}

} // TesttBreakOutBlockData

func TestBreakOutPayloadDataToConfigurationEnvelopePanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("TestBreakOutPayloadDataToConfigurationEnvelopePanic should have panicked")
}
}()
_ = BreakOutPayloadDataToConfigurationEnvelopeOrPanic(nil)
} // TestBreakOutPayloadDataToConfigurationEnvelopePanic

func TestBreakOutPayloadDataToConfigurationEnvelopeBadData(t *testing.T) {
_, err := BreakOutPayloadDataToConfigurationEnvelope(nil)
if err == nil {
t.Errorf("TestBreakOutPayloadDataToConfigurationEnvelopeBadData should have returned error on null input\n ")
}
} // TestBreakOutPayloadDataToConfigurationEnvelopeBadData

func TestBreakOutPayloadDataToConfigurationEnvelope(t *testing.T) {
payload := testPayloadConfigEnvelope()
configEnvelope, _ := BreakOutPayloadDataToConfigurationEnvelope(payload.Data)
if len(configEnvelope.Items) != 1 {
t.Errorf("TestBreakOutPayloadDataToConfigurationEnvelope: configEnvelope.Items array should have 1 item")
}
} // TestBreakOutPayloadDataToConfigurationEnvelope

func TestBreakOutConfigEnvelopeToConfigItemsPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("TestBreakOutConfigEnvelopeToConfigItemsPanic should have panicked")
}
}()
_, _ = BreakOutConfigEnvelopeToConfigItemsOrPanic(nil)
} // TestBreakOutConfigEnvelopeToConfigItemsPanic

func TestBreakOutConfigEnvelopeToConfigItemsBadData(t *testing.T) {
_, _, err := BreakOutConfigEnvelopeToConfigItems(nil)
if err == nil {
t.Errorf("TestBreakOutConfigEnvelopeToConfigItemsBadData not handling nil input\n")
}
} // TestBreakOutConfigEnvelopeToConfigItemsBadData

func TestBreakOutConfigEnvelopeToConfigItems(t *testing.T) {
configEnv := testConfigurationEnvelope()
configItems, _, _ := BreakOutConfigEnvelopeToConfigItems(configEnv) // TODO: test signatures
if len(configItems) != 1 {
t.Errorf("TestBreakOutPayloadDataToConfigurationEnvelope did not return array of 1 config item\n")
}
if configItems[0].Header.Type != int32(pb.HeaderType_CONFIGURATION_TRANSACTION) || !bytes.Equal(configItems[0].Header.ChainID, []byte("test")) {
t.Errorf("TestBreakOutConfigEnvelopeToConfigItems, configItem header does not match original %+v . Expected config_transaction and chainid 'test'\n", configItems[0].Header)
}
if configItems[0].Type != pb.ConfigurationItem_Orderer || configItems[0].Key != "abc" || !bytes.Equal(configItems[0].Value, []byte("test")) {
t.Errorf("TestBreakOutConfigEnvelopeToConfigItems configItem type,Key,Value do not match original %+v\n. Expected orderer, 'abc', 'test'", configItems[0])
}
} // TestBreakOutConfigEnvelopeToConfigItems

func TestBreakOutBlockToConfigurationEnvelopePanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("TestBreakOutBlockToConfigurationEnvelopePanic should have panicked")
}
}()
_, _ = BreakOutBlockToConfigurationEnvelopeOrPanic(nil)
} // TestBreakOutBlockToConfigurationEnvelopePanic

func TestBreakOutBlockToConfigurationEnvelopeBadData(t *testing.T) {
_, _, err := BreakOutBlockToConfigurationEnvelope(nil)
if err == nil {
t.Errorf("TestBreakOutBlockToConfigurationEnvelopeBadData should have rejected null input\n")
}
} // TestBreakOutBlockToConfigurationEnvelopeBadData

func TestBreakOutBlockToConfigurationEnvelope(t *testing.T) {
block := testConfigurationBlock()
configEnvelope, _, _ := BreakOutBlockToConfigurationEnvelope(block) // TODO: test envelope signature
if len(configEnvelope.Items) != 1 {
t.Errorf("TestBreakOutBlockToConfigurationEnvelope should have an array of 1 signedConfigurationItems\n")
}
} // TestBreakOutBlockToConfigurationEnvelopeBadData

// Helper functions
func testChainHeader() *pb.ChainHeader {
return &pb.ChainHeader{
Type: int32(pb.HeaderType_CONFIGURATION_TRANSACTION),
Version: 1,
Timestamp: &timestamp.Timestamp{
Seconds: time.Now().Unix(),
Nanos: 0,
},
ChainID: []byte("test"),
}
}

func testPayloadHeader() *pb.Header {
return &pb.Header{
ChainHeader: testChainHeader(),
SignatureHeader: nil,
}
}

func testPayload() *pb.Payload {
return &pb.Payload{
Header: testPayloadHeader(),
Data: []byte("test"),
}
}

func testEnvelope() *pb.Envelope {
// No need to set the signature
payloadBytes, _ := proto.Marshal(testPayload())
return &pb.Envelope{Payload: payloadBytes}
}

func testPayloadConfigEnvelope() *pb.Payload {
data, _ := proto.Marshal(testConfigurationEnvelope())
return &pb.Payload{
Header: testPayloadHeader(),
Data: data,
}
}

func testEnvelopePayloadConfigEnv() *pb.Envelope {
payloadBytes, _ := proto.Marshal(testPayloadConfigEnvelope())
return &pb.Envelope{Payload: payloadBytes}
} // testEnvelopePayloadConfigEnv

func testConfigurationBlock() *pb.Block {
envelopeBytes, _ := proto.Marshal(testEnvelopePayloadConfigEnv())
return &pb.Block{
Data: &pb.BlockData{
Data: [][]byte{envelopeBytes},
},
}
}

func testConfigurationEnvelope() *pb.ConfigurationEnvelope {
chainHeader := testChainHeader()
configItem := makeConfigurationItem(chainHeader, pb.ConfigurationItem_Orderer, 0, "defaultPolicyID", "abc", []byte("test"))
signedConfigItem, _ := makeSignedConfigurationItem(configItem, nil)
return makeConfigurationEnvelope(signedConfigItem)
} // testConfigurationEnvelope

func testBlock() *pb.Block {
// No need to set the block's Header, or Metadata
envelopeBytes, _ := proto.Marshal(testEnvelope())
return &pb.Block{
Data: &pb.BlockData{
Data: [][]byte{envelopeBytes},
},
}
}

func makeConfigurationItem(ch *pb.ChainHeader, configItemType pb.ConfigurationItem_ConfigurationType, lastModified uint64, modPolicyID string, key string, value []byte) *pb.ConfigurationItem {
return &pb.ConfigurationItem{
Header: ch,
Type: configItemType,
LastModified: lastModified,
ModificationPolicy: modPolicyID,
Key: key,
Value: value,
}
}

func makeSignedConfigurationItem(configItem *pb.ConfigurationItem, signatures []*pb.ConfigurationSignature) (*pb.SignedConfigurationItem, error) {
configItemBytes, _ := proto.Marshal(configItem)
return &pb.SignedConfigurationItem{
ConfigurationItem: configItemBytes,
Signatures: signatures,
}, nil
} // makeSignedConfigurationItem

func makeConfigurationEnvelope(items ...*pb.SignedConfigurationItem) *pb.ConfigurationEnvelope {
return &pb.ConfigurationEnvelope{Items: items}
}

0 comments on commit 60706a7

Please sign in to comment.