642 lines
18 KiB
Go
642 lines
18 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// Package configtx provides utilities to create and modify a channel configuration transaction.
|
|
// Channel transactions contain the configuration data defining members and policies for a
|
|
// system or application channel and can be used to either create or modify existing channels.
|
|
// Both the creation of a new channel or modification of an existing channel outputs an unsigned
|
|
// transaction represented in a protobuf binary format that must be signed by the requisite number
|
|
// of members such that the transaction fulfills the channel's modification policy.
|
|
//
|
|
// See https://hyperledger-fabric.readthedocs.io/en/master/configtx.html#anatomy-of-a-configuration
|
|
// for an in-depth description of channel configuration's anatomy.
|
|
package configtx
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
mb "github.com/hyperledger/fabric-protos-go/msp"
|
|
)
|
|
|
|
// Channel is a channel configuration.
|
|
type Channel struct {
|
|
Consortium string
|
|
Application Application
|
|
Orderer Orderer
|
|
Consortiums []Consortium
|
|
Capabilities []string
|
|
Policies map[string]Policy
|
|
ModPolicy string
|
|
}
|
|
|
|
// Policy is an expression used to define rules for access to channels, chaincodes, etc.
|
|
type Policy struct {
|
|
Type string
|
|
Rule string
|
|
ModPolicy string
|
|
}
|
|
|
|
// Organization is an organization in the channel configuration.
|
|
type Organization struct {
|
|
Name string
|
|
Policies map[string]Policy
|
|
MSP MSP
|
|
|
|
// AnchorPeers contains the endpoints of anchor peers for each
|
|
// application organization.
|
|
AnchorPeers []Address
|
|
OrdererEndpoints []string
|
|
ModPolicy string
|
|
}
|
|
|
|
// Address contains the hostname and port for an endpoint.
|
|
type Address struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
type standardConfigValue struct {
|
|
key string
|
|
value proto.Message
|
|
}
|
|
|
|
type standardConfigPolicy struct {
|
|
key string
|
|
value *cb.Policy
|
|
}
|
|
|
|
// ConfigTx wraps a config transaction.
|
|
type ConfigTx struct {
|
|
// original state of the config
|
|
original *cb.Config
|
|
// modified state of the config
|
|
updated *cb.Config
|
|
}
|
|
|
|
// New creates a new ConfigTx from a Config protobuf.
|
|
// New will panic if given an empty config.
|
|
func New(config *cb.Config) ConfigTx {
|
|
return ConfigTx{
|
|
original: config,
|
|
// Clone the base config for processing updates
|
|
updated: proto.Clone(config).(*cb.Config),
|
|
}
|
|
}
|
|
|
|
// OriginalConfig returns the original unedited config.
|
|
func (c *ConfigTx) OriginalConfig() *cb.Config {
|
|
return c.original
|
|
}
|
|
|
|
// UpdatedConfig returns the modified config.
|
|
func (c *ConfigTx) UpdatedConfig() *cb.Config {
|
|
return c.updated
|
|
}
|
|
|
|
// ComputeMarshaledUpdate computes the ConfigUpdate from a base and modified
|
|
// config transaction and returns the marshaled bytes.
|
|
func (c *ConfigTx) ComputeMarshaledUpdate(channelID string) ([]byte, error) {
|
|
if channelID == "" {
|
|
return nil, errors.New("channel ID is required")
|
|
}
|
|
|
|
update, err := computeConfigUpdate(c.original, c.updated)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to compute update: %v", err)
|
|
}
|
|
|
|
update.ChannelId = channelID
|
|
|
|
marshaledUpdate, err := proto.Marshal(update)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling config update: %v", err)
|
|
}
|
|
|
|
return marshaledUpdate, nil
|
|
}
|
|
|
|
// NewEnvelope creates an envelope with the provided marshaled config update
|
|
// and config signatures.
|
|
func NewEnvelope(marshaledUpdate []byte, signatures ...*cb.ConfigSignature) (*cb.Envelope, error) {
|
|
configUpdateEnvelope := &cb.ConfigUpdateEnvelope{
|
|
ConfigUpdate: marshaledUpdate,
|
|
Signatures: signatures,
|
|
}
|
|
|
|
c := &cb.ConfigUpdate{}
|
|
err := proto.Unmarshal(marshaledUpdate, c)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unmarshaling config update: %v", err)
|
|
}
|
|
|
|
envelope, err := newEnvelope(cb.HeaderType_CONFIG_UPDATE, c.ChannelId, configUpdateEnvelope)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return envelope, nil
|
|
}
|
|
|
|
// NewMarshaledCreateChannelTx creates a create channel config update
|
|
// transaction using the provided application channel configuration and returns
|
|
// the marshaled bytes.
|
|
func NewMarshaledCreateChannelTx(channelConfig Channel, channelID string) ([]byte, error) {
|
|
if channelID == "" {
|
|
return nil, errors.New("profile's channel ID is required")
|
|
}
|
|
|
|
ct, err := defaultConfigTemplate(channelConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating default config template: %v", err)
|
|
}
|
|
|
|
update, err := newChannelCreateConfigUpdate(channelID, channelConfig, ct)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating channel create config update: %v", err)
|
|
}
|
|
|
|
marshaledUpdate, err := proto.Marshal(update)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling config update: %v", err)
|
|
}
|
|
return marshaledUpdate, nil
|
|
}
|
|
|
|
// NewSystemChannelGenesisBlock creates a genesis block using the provided
|
|
// consortiums and orderer configuration and returns a block.
|
|
func NewSystemChannelGenesisBlock(channelConfig Channel, channelID string) (*cb.Block, error) {
|
|
if channelID == "" {
|
|
return nil, errors.New("system channel ID is required")
|
|
}
|
|
|
|
systemChannelGroup, err := newSystemChannelGroup(channelConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating system channel group: %v", err)
|
|
}
|
|
|
|
block, err := newGenesisBlock(systemChannelGroup, channelID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating system channel genesis block: %v", err)
|
|
}
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// NewApplicationChannelGenesisBlock creates a genesis block using the provided
|
|
// application and orderer configuration and returns a block.
|
|
func NewApplicationChannelGenesisBlock(channelConfig Channel, channelID string) (*cb.Block, error) {
|
|
if channelID == "" {
|
|
return nil, errors.New("application channel ID is required")
|
|
}
|
|
|
|
applicationChannelGroup, err := newApplicationChannelGroup(channelConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating application channel group: %v", err)
|
|
}
|
|
|
|
block, err := newGenesisBlock(applicationChannelGroup, channelID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating application channel genesis block: %v", err)
|
|
}
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// newSystemChannelGroup defines the root of the system channel configuration.
|
|
func newSystemChannelGroup(channelConfig Channel) (*cb.ConfigGroup, error) {
|
|
channelGroup, err := newChannelGroupWithOrderer(channelConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
consortiumsGroup, err := newConsortiumsGroup(channelConfig.Consortiums)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
channelGroup.Groups[ConsortiumsGroupKey] = consortiumsGroup
|
|
|
|
channelGroup.ModPolicy = AdminsPolicyKey
|
|
|
|
if channelConfig.ModPolicy != "" {
|
|
channelGroup.ModPolicy = channelConfig.ModPolicy
|
|
}
|
|
|
|
return channelGroup, nil
|
|
}
|
|
|
|
// newApplicationChannelGroup defines the root of the application
|
|
// channel configuration.
|
|
func newApplicationChannelGroup(channelConfig Channel) (*cb.ConfigGroup, error) {
|
|
channelGroup, err := newChannelGroupWithOrderer(channelConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
applicationGroup, err := newApplicationGroup(channelConfig.Application)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
channelGroup.Groups[ApplicationGroupKey] = applicationGroup
|
|
|
|
channelGroup.ModPolicy = AdminsPolicyKey
|
|
|
|
if channelConfig.ModPolicy != "" {
|
|
channelGroup.ModPolicy = channelConfig.ModPolicy
|
|
}
|
|
|
|
return channelGroup, nil
|
|
}
|
|
|
|
func newChannelGroupWithOrderer(channelConfig Channel) (*cb.ConfigGroup, error) {
|
|
channelGroup := newConfigGroup()
|
|
|
|
err := setPolicies(channelGroup, channelConfig.Policies)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setting channel policies: %v", err)
|
|
}
|
|
|
|
err = setValue(channelGroup, hashingAlgorithmValue(), AdminsPolicyKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = setValue(channelGroup, blockDataHashingStructureValue(), AdminsPolicyKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(channelConfig.Capabilities) == 0 {
|
|
return nil, errors.New("capabilities is not defined in channel config")
|
|
}
|
|
|
|
err = setValue(channelGroup, capabilitiesValue(channelConfig.Capabilities), AdminsPolicyKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ordererGroup, err := newOrdererGroup(channelConfig.Orderer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
channelGroup.Groups[OrdererGroupKey] = ordererGroup
|
|
|
|
return channelGroup, nil
|
|
}
|
|
|
|
// newGenesisBlock generates a genesis block from the config group and
|
|
// channel ID. The block number is always zero.
|
|
func newGenesisBlock(cg *cb.ConfigGroup, channelID string) (*cb.Block, error) {
|
|
payloadChannelHeader := channelHeader(cb.HeaderType_CONFIG, msgVersion, channelID, epoch)
|
|
nonce, err := newNonce()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating nonce: %v", err)
|
|
}
|
|
payloadSignatureHeader := &cb.SignatureHeader{Creator: nil, Nonce: nonce}
|
|
payloadChannelHeader.TxId = computeTxID(payloadSignatureHeader.Nonce, payloadSignatureHeader.Creator)
|
|
payloadHeader, err := payloadHeader(payloadChannelHeader, payloadSignatureHeader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("construct payload header: %v", err)
|
|
}
|
|
payloadData, err := proto.Marshal(&cb.ConfigEnvelope{Config: &cb.Config{ChannelGroup: cg}})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling payload data: %v", err)
|
|
}
|
|
payload := &cb.Payload{Header: payloadHeader, Data: payloadData}
|
|
envelopePayload, err := proto.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling envelope payload: %v", err)
|
|
}
|
|
envelope := &cb.Envelope{Payload: envelopePayload, Signature: nil}
|
|
blockData, err := proto.Marshal(envelope)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling envelope: %v", err)
|
|
}
|
|
|
|
block := newBlock(0, nil)
|
|
block.Data = &cb.BlockData{Data: [][]byte{blockData}}
|
|
block.Header.DataHash = blockDataHash(block.Data)
|
|
|
|
lastConfigValue, err := proto.Marshal(&cb.LastConfig{Index: 0})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling metadata last config value: %v", err)
|
|
}
|
|
lastConfigMetadata, err := proto.Marshal(&cb.Metadata{Value: lastConfigValue})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling metadata last config: %v", err)
|
|
}
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = lastConfigMetadata
|
|
|
|
signatureValue, err := proto.Marshal(&cb.OrdererBlockMetadata{
|
|
LastConfig: &cb.LastConfig{Index: 0},
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling metadata signature value: %v", err)
|
|
}
|
|
signatureMetadata, err := proto.Marshal(&cb.Metadata{Value: signatureValue})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling metadata signature: %v", err)
|
|
}
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = signatureMetadata
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// setValue sets the value as ConfigValue in the ConfigGroup.
|
|
func setValue(cg *cb.ConfigGroup, value *standardConfigValue, modPolicy string) error {
|
|
v, err := proto.Marshal(value.value)
|
|
if err != nil {
|
|
return fmt.Errorf("marshaling standard config value '%s': %v", value.key, err)
|
|
}
|
|
|
|
if cg.Values == nil {
|
|
cg.Values = map[string]*cb.ConfigValue{}
|
|
}
|
|
|
|
cg.Values[value.key] = &cb.ConfigValue{
|
|
Value: v,
|
|
ModPolicy: modPolicy,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// implicitMetaFromString parses a *cb.ImplicitMetaPolicy from an input string.
|
|
func implicitMetaFromString(input string) (*cb.ImplicitMetaPolicy, error) {
|
|
args := strings.Split(input, " ")
|
|
if len(args) != 2 {
|
|
return nil, fmt.Errorf("expected two space separated tokens, but got %d", len(args))
|
|
}
|
|
|
|
res := &cb.ImplicitMetaPolicy{
|
|
SubPolicy: args[1],
|
|
}
|
|
|
|
switch args[0] {
|
|
case cb.ImplicitMetaPolicy_ANY.String():
|
|
res.Rule = cb.ImplicitMetaPolicy_ANY
|
|
case cb.ImplicitMetaPolicy_ALL.String():
|
|
res.Rule = cb.ImplicitMetaPolicy_ALL
|
|
case cb.ImplicitMetaPolicy_MAJORITY.String():
|
|
res.Rule = cb.ImplicitMetaPolicy_MAJORITY
|
|
default:
|
|
return nil, fmt.Errorf("unknown rule type '%s', expected ALL, ANY, or MAJORITY", args[0])
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// mspValue returns the config definition for an MSP.
|
|
// It is a value for the /Channel/Orderer/*, /Channel/Application/*, and /Channel/Consortiums/*/*/* groups.
|
|
func mspValue(mspDef *mb.MSPConfig) *standardConfigValue {
|
|
return &standardConfigValue{
|
|
key: MSPKey,
|
|
value: mspDef,
|
|
}
|
|
}
|
|
|
|
// defaultConfigTemplate generates a config template based on the assumption that
|
|
// the input profile is a channel creation template and no system channel context
|
|
// is available.
|
|
func defaultConfigTemplate(channelConfig Channel) (*cb.ConfigGroup, error) {
|
|
channelGroup, err := newChannelGroup(channelConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, ok := channelGroup.Groups[ApplicationGroupKey]; !ok {
|
|
return nil, errors.New("channel template config must contain an application section")
|
|
}
|
|
|
|
channelGroup.Groups[ApplicationGroupKey].Values = nil
|
|
channelGroup.Groups[ApplicationGroupKey].Policies = nil
|
|
|
|
return channelGroup, nil
|
|
}
|
|
|
|
// newChannelGroup defines the root of the channel configuration.
|
|
func newChannelGroup(channelConfig Channel) (*cb.ConfigGroup, error) {
|
|
channelGroup := newConfigGroup()
|
|
|
|
if channelConfig.Consortium == "" {
|
|
return nil, errors.New("consortium is not defined in channel config")
|
|
}
|
|
|
|
err := setValue(channelGroup, consortiumValue(channelConfig.Consortium), "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
channelGroup.Groups[ApplicationGroupKey], err = newApplicationGroupTemplate(channelConfig.Application)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create application group: %v", err)
|
|
}
|
|
|
|
channelGroup.ModPolicy = AdminsPolicyKey
|
|
|
|
if channelConfig.ModPolicy != "" {
|
|
channelGroup.ModPolicy = channelConfig.ModPolicy
|
|
}
|
|
|
|
return channelGroup, nil
|
|
}
|
|
|
|
// newChannelCreateConfigUpdate generates a ConfigUpdate which can be sent to the orderer to create a new channel.
|
|
// Optionally, the channel group of the ordering system channel may be passed in, and the resulting ConfigUpdate
|
|
// will extract the appropriate versions from this file.
|
|
func newChannelCreateConfigUpdate(channelID string, channelConfig Channel, templateConfig *cb.ConfigGroup) (*cb.ConfigUpdate, error) {
|
|
newChannelGroup, err := newChannelGroup(channelConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updt, err := computeConfigUpdate(&cb.Config{ChannelGroup: templateConfig}, &cb.Config{ChannelGroup: newChannelGroup})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("computing update: %v", err)
|
|
}
|
|
|
|
wsValue, err := proto.Marshal(&cb.Consortium{
|
|
Name: channelConfig.Consortium,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling consortium: %v", err)
|
|
}
|
|
|
|
// Add the consortium name to create the channel for into the write set as required
|
|
updt.ChannelId = channelID
|
|
updt.ReadSet.Values[ConsortiumKey] = &cb.ConfigValue{Version: 0}
|
|
updt.WriteSet.Values[ConsortiumKey] = &cb.ConfigValue{
|
|
Version: 0,
|
|
Value: wsValue,
|
|
}
|
|
|
|
return updt, nil
|
|
}
|
|
|
|
// newConfigGroup creates an empty *cb.ConfigGroup.
|
|
func newConfigGroup() *cb.ConfigGroup {
|
|
return &cb.ConfigGroup{
|
|
Groups: map[string]*cb.ConfigGroup{},
|
|
Values: map[string]*cb.ConfigValue{},
|
|
Policies: map[string]*cb.ConfigPolicy{},
|
|
}
|
|
}
|
|
|
|
// newEnvelope creates an unsigned envelope of the desired type containing
|
|
// a payload Header and the marshaled proto message as the payload Data.
|
|
func newEnvelope(
|
|
txType cb.HeaderType,
|
|
channelID string,
|
|
dataMsg proto.Message,
|
|
) (*cb.Envelope, error) {
|
|
payloadChannelHeader := channelHeader(txType, msgVersion, channelID, epoch)
|
|
payloadSignatureHeader := &cb.SignatureHeader{}
|
|
|
|
data, err := proto.Marshal(dataMsg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling envelope data: %v", err)
|
|
}
|
|
|
|
payloadHeader, err := payloadHeader(payloadChannelHeader, payloadSignatureHeader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("making payload header: %v", err)
|
|
}
|
|
|
|
paylBytes, err := proto.Marshal(
|
|
&cb.Payload{
|
|
Header: payloadHeader,
|
|
Data: data,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling payload: %v", err)
|
|
}
|
|
|
|
env := &cb.Envelope{
|
|
Payload: paylBytes,
|
|
}
|
|
|
|
return env, nil
|
|
}
|
|
|
|
// channelHeader creates a ChannelHeader.
|
|
func channelHeader(headerType cb.HeaderType, version int32, channelID string, epoch uint64) *cb.ChannelHeader {
|
|
return &cb.ChannelHeader{
|
|
Type: int32(headerType),
|
|
Version: version,
|
|
Timestamp: ×tamp.Timestamp{
|
|
Seconds: ptypes.TimestampNow().GetSeconds(),
|
|
},
|
|
ChannelId: channelID,
|
|
Epoch: epoch,
|
|
}
|
|
}
|
|
|
|
// payloadHeader creates a Payload Header.
|
|
func payloadHeader(ch *cb.ChannelHeader, sh *cb.SignatureHeader) (*cb.Header, error) {
|
|
channelHeader, err := proto.Marshal(ch)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling channel header: %v", err)
|
|
}
|
|
|
|
signatureHeader, err := proto.Marshal(sh)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling signature header: %v", err)
|
|
}
|
|
|
|
return &cb.Header{
|
|
ChannelHeader: channelHeader,
|
|
SignatureHeader: signatureHeader,
|
|
}, nil
|
|
}
|
|
|
|
// concatenateBytes combines multiple arrays of bytes, for signatures or digests
|
|
// over multiple fields.
|
|
func concatenateBytes(data ...[]byte) []byte {
|
|
res := []byte{}
|
|
for i := range data {
|
|
res = append(res, data[i]...)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// unmarshalConfigValueAtKey unmarshals the value for the specified key in a config group
|
|
// into the designated proto message.
|
|
func unmarshalConfigValueAtKey(group *cb.ConfigGroup, key string, msg proto.Message) error {
|
|
valueAtKey, ok := group.Values[key]
|
|
if !ok {
|
|
return fmt.Errorf("config does not contain value for %s", key)
|
|
}
|
|
|
|
err := proto.Unmarshal(valueAtKey.Value, msg)
|
|
if err != nil {
|
|
return fmt.Errorf("unmarshaling %s: %v", key, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseAddress(address string) (string, int, error) {
|
|
hostport := strings.Split(address, ":")
|
|
if len(hostport) != 2 {
|
|
return "", 0, fmt.Errorf("unable to parse host and port from %s", address)
|
|
}
|
|
|
|
host := hostport[0]
|
|
port := hostport[1]
|
|
|
|
portNum, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
return host, portNum, nil
|
|
}
|
|
|
|
// newBlock constructs a block with no data and no metadata.
|
|
func newBlock(seqNum uint64, previousHash []byte) *cb.Block {
|
|
block := &cb.Block{}
|
|
block.Header = &cb.BlockHeader{}
|
|
block.Header.Number = seqNum
|
|
block.Header.PreviousHash = previousHash
|
|
block.Header.DataHash = []byte{}
|
|
block.Data = &cb.BlockData{}
|
|
|
|
var metadataContents [][]byte
|
|
for i := 0; i < len(cb.BlockMetadataIndex_name); i++ {
|
|
metadataContents = append(metadataContents, []byte{})
|
|
}
|
|
block.Metadata = &cb.BlockMetadata{Metadata: metadataContents}
|
|
|
|
return block
|
|
}
|
|
|
|
// computeTxID computes TxID as the Hash computed
|
|
// over the concatenation of nonce and creator.
|
|
func computeTxID(nonce, creator []byte) string {
|
|
hasher := sha256.New()
|
|
hasher.Write(nonce)
|
|
hasher.Write(creator)
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
}
|
|
|
|
// blockDataHash computes block data as the Hash
|
|
func blockDataHash(b *cb.BlockData) []byte {
|
|
sum := sha256.Sum256(bytes.Join(b.Data, nil))
|
|
return sum[:]
|
|
}
|