244 lines
9.3 KiB
Go
244 lines
9.3 KiB
Go
/*
|
|
Copyright IBM Corp. 2017 All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package multichannel
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
newchannelconfig "github.com/hyperledger/fabric/common/channelconfig"
|
|
"github.com/hyperledger/fabric/common/configtx"
|
|
"github.com/hyperledger/fabric/common/ledger/blockledger"
|
|
"github.com/hyperledger/fabric/common/util"
|
|
"github.com/hyperledger/fabric/internal/pkg/identity"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
)
|
|
|
|
type blockWriterSupport interface {
|
|
identity.SignerSerializer
|
|
blockledger.ReadWriter
|
|
configtx.Validator
|
|
Update(*newchannelconfig.Bundle)
|
|
CreateBundle(channelID string, config *cb.Config) (*newchannelconfig.Bundle, error)
|
|
SharedConfig() newchannelconfig.Orderer
|
|
}
|
|
|
|
// BlockWriter efficiently writes the blockchain to disk.
|
|
// To safely use BlockWriter, only one thread should interact with it.
|
|
// BlockWriter will spawn additional committing go routines and handle locking
|
|
// so that these other go routines safely interact with the calling one.
|
|
type BlockWriter struct {
|
|
support blockWriterSupport
|
|
lastConfigBlockNum uint64
|
|
lastConfigSeq uint64
|
|
lastBlock *cb.Block
|
|
committingBlock sync.Mutex
|
|
}
|
|
|
|
func newBlockWriter(lastBlock *cb.Block, support blockWriterSupport) *BlockWriter {
|
|
bw := &BlockWriter{
|
|
support: support,
|
|
lastConfigSeq: support.Sequence(),
|
|
lastBlock: lastBlock,
|
|
}
|
|
|
|
// If this is the genesis block, the lastconfig field may be empty, and, the last config block is necessarily block 0
|
|
// so no need to initialize lastConfig
|
|
if lastBlock.Header.Number != 0 {
|
|
var err error
|
|
bw.lastConfigBlockNum, err = protoutil.GetLastConfigIndexFromBlock(lastBlock)
|
|
if err != nil {
|
|
logger.Panicf("[channel: %s] Error extracting last config block from block metadata: %s", support.ChannelID(), err)
|
|
}
|
|
}
|
|
|
|
logger.Debugf("[channel: %s] Creating block writer for tip of chain (blockNumber=%d, lastConfigBlockNum=%d, lastConfigSeq=%d)", support.ChannelID(), lastBlock.Header.Number, bw.lastConfigBlockNum, bw.lastConfigSeq)
|
|
return bw
|
|
}
|
|
|
|
// CreateNextBlock creates a new block with the next block number, and the given contents.
|
|
func (bw *BlockWriter) CreateNextBlock(messages []*cb.Envelope) *cb.Block {
|
|
previousBlockHash := protoutil.BlockHeaderHash(bw.lastBlock.Header)
|
|
|
|
data := &cb.BlockData{
|
|
Data: make([][]byte, len(messages)),
|
|
}
|
|
|
|
var err error
|
|
for i, msg := range messages {
|
|
data.Data[i], err = proto.Marshal(msg)
|
|
if err != nil {
|
|
logger.Panicf("Could not marshal envelope: %s", err)
|
|
}
|
|
}
|
|
|
|
block := protoutil.NewBlock(bw.lastBlock.Header.Number+1, previousBlockHash)
|
|
block.Header.DataHash = protoutil.BlockDataHash(data)
|
|
block.Data = data
|
|
|
|
return block
|
|
}
|
|
|
|
// WriteConfigBlock should be invoked for blocks which contain a config transaction.
|
|
// This call will block until the new config has taken effect, then will return
|
|
// while the block is written asynchronously to disk.
|
|
func (bw *BlockWriter) WriteConfigBlock(block *cb.Block, encodedMetadataValue []byte) {
|
|
ctx, err := protoutil.ExtractEnvelope(block, 0)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block, but could not get configtx: %s", err)
|
|
}
|
|
|
|
payload, err := protoutil.UnmarshalPayload(ctx.Payload)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block, but configtx payload is invalid: %s", err)
|
|
}
|
|
|
|
if payload.Header == nil {
|
|
logger.Panicf("Told to write a config block, but configtx payload header is missing")
|
|
}
|
|
|
|
chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block with an invalid channel header: %s", err)
|
|
}
|
|
|
|
switch chdr.Type {
|
|
case int32(cb.HeaderType_ORDERER_TRANSACTION):
|
|
logger.Panicf("[channel: %s] Told to write a config block of type HeaderType_ORDERER_TRANSACTION, but the system channel is no longer supported", bw.support.ChannelID())
|
|
|
|
case int32(cb.HeaderType_CONFIG):
|
|
configEnvelope, err := configtx.UnmarshalConfigEnvelope(payload.Data)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block with new channel, but did not have config envelope encoded: %s", err)
|
|
}
|
|
|
|
err = bw.support.Validate(configEnvelope)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block with new config, but could not apply it: %s", err)
|
|
}
|
|
|
|
bundle, err := bw.support.CreateBundle(chdr.ChannelId, configEnvelope.Config)
|
|
if err != nil {
|
|
logger.Panicf("Told to write a config block with a new config, but could not convert it to a bundle: %s", err)
|
|
}
|
|
|
|
oc, ok := bundle.OrdererConfig()
|
|
if !ok {
|
|
logger.Panicf("[channel: %s] OrdererConfig missing from bundle", bw.support.ChannelID())
|
|
}
|
|
|
|
currentType := bw.support.SharedConfig().ConsensusType()
|
|
nextType := oc.ConsensusType()
|
|
if currentType != nextType {
|
|
encodedMetadataValue = nil
|
|
logger.Debugf("[channel: %s] Consensus-type migration: maintenance mode, change from %s to %s, setting metadata to nil",
|
|
bw.support.ChannelID(), currentType, nextType)
|
|
}
|
|
|
|
// Avoid Bundle update before the go-routine in WriteBlock() finished writing the previous block.
|
|
// We do this (in particular) to prevent bw.support.Sequence() from advancing before the go-routine reads it.
|
|
// In general, this prevents the StableBundle from changing before the go-routine in WriteBlock() finishes.
|
|
bw.committingBlock.Lock()
|
|
bw.committingBlock.Unlock()
|
|
bw.support.Update(bundle)
|
|
default:
|
|
logger.Panicf("Told to write a config block with unknown header type: %v", chdr.Type)
|
|
}
|
|
|
|
bw.WriteBlockSync(block, encodedMetadataValue)
|
|
}
|
|
|
|
// WriteBlock should be invoked for blocks which contain normal transactions.
|
|
// It sets the target block as the pending next block, and returns before it is committed.
|
|
// Before returning, it acquires the committing lock, and spawns a go routine which will
|
|
// annotate the block with metadata and signatures, and write the block to the ledger
|
|
// then release the lock. This allows the calling thread to begin assembling the next block
|
|
// before the commit phase is complete.
|
|
func (bw *BlockWriter) WriteBlock(block *cb.Block, encodedMetadataValue []byte) {
|
|
bw.committingBlock.Lock()
|
|
bw.lastBlock = block
|
|
|
|
go func() {
|
|
defer bw.committingBlock.Unlock()
|
|
bw.commitBlock(encodedMetadataValue)
|
|
}()
|
|
}
|
|
|
|
// WriteBlockSync is same as WriteBlock, but commits block synchronously.
|
|
// Note: WriteConfigBlock should use WriteBlockSync instead of WriteBlock.
|
|
//
|
|
// If the block contains a transaction that remove the node from consenters,
|
|
// the node will switch to follower and pull blocks from other nodes.
|
|
// Suppose writing block asynchronously, the block maybe not persist to disk
|
|
// when the follower chain starts working. The follower chain will read a block
|
|
// before the config block, in which the node is still a consenter, so the follower
|
|
// chain will switch to the consensus chain. That's a dead loop!
|
|
// So WriteConfigBlock should use WriteBlockSync instead of WriteBlock.
|
|
func (bw *BlockWriter) WriteBlockSync(block *cb.Block, encodedMetadataValue []byte) {
|
|
bw.committingBlock.Lock()
|
|
bw.lastBlock = block
|
|
|
|
defer bw.committingBlock.Unlock()
|
|
bw.commitBlock(encodedMetadataValue)
|
|
}
|
|
|
|
// commitBlock should only ever be invoked with the bw.committingBlock held
|
|
// this ensures that the encoded config sequence numbers stay in sync
|
|
func (bw *BlockWriter) commitBlock(encodedMetadataValue []byte) {
|
|
bw.addLastConfig(bw.lastBlock)
|
|
|
|
if len(bw.lastBlock.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES]) == 0 {
|
|
bw.addBlockSignature(bw.lastBlock, encodedMetadataValue)
|
|
}
|
|
|
|
err := bw.support.Append(bw.lastBlock)
|
|
if err != nil {
|
|
logger.Panicf("[channel: %s] Could not append block: %s", bw.support.ChannelID(), err)
|
|
}
|
|
logger.Debugf("[channel: %s] Wrote block [%d]", bw.support.ChannelID(), bw.lastBlock.GetHeader().Number)
|
|
}
|
|
|
|
func (bw *BlockWriter) addBlockSignature(block *cb.Block, consenterMetadata []byte) {
|
|
blockSignature := &cb.MetadataSignature{
|
|
SignatureHeader: protoutil.MarshalOrPanic(protoutil.NewSignatureHeaderOrPanic(bw.support)),
|
|
}
|
|
|
|
blockSignatureValue := protoutil.MarshalOrPanic(&cb.OrdererBlockMetadata{
|
|
LastConfig: &cb.LastConfig{Index: bw.lastConfigBlockNum},
|
|
ConsenterMetadata: protoutil.MarshalOrPanic(&cb.Metadata{Value: consenterMetadata}),
|
|
})
|
|
|
|
blockSignature.Signature = protoutil.SignOrPanic(
|
|
bw.support,
|
|
util.ConcatenateBytes(blockSignatureValue, blockSignature.SignatureHeader, protoutil.BlockHeaderBytes(block.Header)),
|
|
)
|
|
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&cb.Metadata{
|
|
Value: blockSignatureValue,
|
|
Signatures: []*cb.MetadataSignature{
|
|
blockSignature,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (bw *BlockWriter) addLastConfig(block *cb.Block) {
|
|
configSeq := bw.support.Sequence()
|
|
if configSeq > bw.lastConfigSeq {
|
|
logger.Debugf("[channel: %s] Detected lastConfigSeq transitioning from %d to %d, setting lastConfigBlockNum from %d to %d", bw.support.ChannelID(), bw.lastConfigSeq, configSeq, bw.lastConfigBlockNum, block.Header.Number)
|
|
bw.lastConfigBlockNum = block.Header.Number
|
|
bw.lastConfigSeq = configSeq
|
|
}
|
|
|
|
lastConfigValue := protoutil.MarshalOrPanic(&cb.LastConfig{Index: bw.lastConfigBlockNum})
|
|
logger.Debugf("[channel: %s] About to write block, setting its LAST_CONFIG to %d", bw.support.ChannelID(), bw.lastConfigBlockNum)
|
|
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = protoutil.MarshalOrPanic(&cb.Metadata{
|
|
Value: lastConfigValue,
|
|
})
|
|
}
|