Skip to content

Commit

Permalink
Follower: check if join block different from fetched block
Browse files Browse the repository at this point in the history
- before fetched block is committed
- don't panic
- continue to retry
- improve channel participation Verifier to check DataHash== hash(block.Data)

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I7089df4f1bfc200a5f17582df0da7d4172f8f09b
  • Loading branch information
tock-ibm committed Jun 7, 2023
1 parent 9e6efe9 commit d45719c
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 102 deletions.
69 changes: 36 additions & 33 deletions cmd/osnadmin/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var _ = Describe("osnadmin", func() {

BeforeEach(func() {
var err error
tempDir, err = ioutil.TempDir("", "osnadmin")
tempDir, err = os.MkdirTemp("", "osnadmin")
Expect(err).NotTo(HaveOccurred())

generateCertificates(tempDir)
Expand Down Expand Up @@ -782,46 +782,49 @@ func generateCertificates(tempDir string) {
}

func blockWithGroups(groups map[string]*cb.ConfigGroup, channelID string) *cb.Block {
return &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Groups: groups,
Values: map[string]*cb.ConfigValue{
"HashingAlgorithm": {
Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
Name: bccsp.SHA256,
}),
},
"BlockDataHashingStructure": {
Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
Width: math.MaxUint32,
}),
},
"OrdererAddresses": {
Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
Addresses: []string{"localhost"},
}),
},
block := protoutil.NewBlock(0, []byte{})
block.Data = &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Groups: groups,
Values: map[string]*cb.ConfigValue{
"HashingAlgorithm": {
Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
Name: bccsp.SHA256,
}),
},
"BlockDataHashingStructure": {
Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
Width: math.MaxUint32,
}),
},
"OrdererAddresses": {
Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
Addresses: []string{"localhost"},
}),
},
},
},
}),
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_CONFIG),
ChannelId: channelID,
}),
},
}),
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_CONFIG),
ChannelId: channelID,
}),
},
}),
},
}),
},
}
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
protoutil.InitBlockMetadata(block)

return block
}

func createBlockFile(tempDir string, configBlock *cb.Block) string {
Expand Down
10 changes: 10 additions & 0 deletions orderer/common/channelparticipation/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package channelparticipation

import (
"bytes"

cb "github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/hyperledger/fabric/common/channelconfig"
Expand All @@ -25,6 +27,14 @@ func ValidateJoinBlock(configBlock *cb.Block) (channelID string, err error) {
return "", errors.New("block is not a config block")
}

if configBlock.Metadata == nil || len(configBlock.Metadata.Metadata) == 0 {
return "", errors.New("invalid block: does not have metadata")
}

if !bytes.Equal(protoutil.BlockDataHash(configBlock.Data), configBlock.Header.DataHash) {
return "", errors.New("invalid block: Header.DataHash is different from Hash(block.Data)")
}

envelope, err := protoutil.ExtractEnvelope(configBlock, 0)
if err != nil {
return "", err
Expand Down
139 changes: 84 additions & 55 deletions orderer/common/channelparticipation/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"math"
"testing"

"github.com/golang/protobuf/proto"
cb "github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/orderer/common/channelparticipation"
Expand All @@ -19,12 +20,45 @@ import (
)

func TestValidateJoinBlock(t *testing.T) {
validJoinBlock := blockWithGroups(
map[string]*cb.ConfigGroup{
"Application": {},
},
"my-channel",
)

tests := []struct {
testName string
joinBlock *cb.Block
expectedChannelID string
expectedErr error
}{
{
testName: "Valid application channel join block",
joinBlock: validJoinBlock,
expectedChannelID: "my-channel",
expectedErr: nil,
},
{
testName: "Invalid block data hash",
joinBlock: func() *cb.Block {
b := proto.Clone(validJoinBlock).(*cb.Block)
b.Header.DataHash = []byte("bogus")
return b
}(),
expectedChannelID: "",
expectedErr: errors.New("invalid block: Header.DataHash is different from Hash(block.Data)"),
},
{
testName: "Invalid block metadata",
joinBlock: func() *cb.Block {
b := proto.Clone(validJoinBlock).(*cb.Block)
b.Metadata = nil
return b
}(),
expectedChannelID: "",
expectedErr: errors.New("invalid block: does not have metadata"),
},
{
testName: "Not supported: system channel join block",
joinBlock: blockWithGroups(
Expand All @@ -36,17 +70,6 @@ func TestValidateJoinBlock(t *testing.T) {
expectedChannelID: "",
expectedErr: errors.New("invalid config: contains consortiums: system channel not supported"),
},
{
testName: "Valid application channel join block",
joinBlock: blockWithGroups(
map[string]*cb.ConfigGroup{
"Application": {},
},
"my-channel",
),
expectedChannelID: "my-channel",
expectedErr: nil,
},
{
testName: "Join block not a config block",
joinBlock: nonConfigBlock(),
Expand Down Expand Up @@ -100,62 +123,68 @@ func TestValidateJoinBlock(t *testing.T) {
}

func blockWithGroups(groups map[string]*cb.ConfigGroup, channelID string) *cb.Block {
return &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Groups: groups,
Values: map[string]*cb.ConfigValue{
"HashingAlgorithm": {
Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
Name: bccsp.SHA256,
}),
},
"BlockDataHashingStructure": {
Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
Width: math.MaxUint32,
}),
},
"OrdererAddresses": {
Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
Addresses: []string{"localhost"},
}),
},
block := protoutil.NewBlock(0, []byte{})
block.Data = &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Groups: groups,
Values: map[string]*cb.ConfigValue{
"HashingAlgorithm": {
Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
Name: bccsp.SHA256,
}),
},
"BlockDataHashingStructure": {
Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
Width: math.MaxUint32,
}),
},
"OrdererAddresses": {
Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
Addresses: []string{"localhost"},
}),
},
},
},
}),
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_CONFIG),
ChannelId: channelID,
}),
},
}),
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_CONFIG),
ChannelId: channelID,
}),
},
}),
},
}),
},
}
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
protoutil.InitBlockMetadata(block)

return block
}

func nonConfigBlock() *cb.Block {
return &cb.Block{
Data: &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_ENDORSER_TRANSACTION),
}),
},
}),
block := protoutil.NewBlock(0, []byte{})
block.Data = &cb.BlockData{
Data: [][]byte{
protoutil.MarshalOrPanic(&cb.Envelope{
Payload: protoutil.MarshalOrPanic(&cb.Payload{
Header: &cb.Header{
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
Type: int32(cb.HeaderType_ENDORSER_TRANSACTION),
}),
},
}),
},
}),
},
}
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
protoutil.InitBlockMetadata(block)

return block
}
25 changes: 16 additions & 9 deletions orderer/common/follower/follower_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"time"

"github.com/golang/protobuf/proto"

"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/common/flogging"
Expand Down Expand Up @@ -297,7 +296,7 @@ func (c *Chain) run() {
}()

if err := c.pull(); err != nil {
c.logger.Warnf("Pull failed, error: %s", err)
c.logger.Warnf("Pull failed, follower chain stopped, error: %s", err)
// TODO set the status to StatusError (see FAB-18106)
}
}
Expand Down Expand Up @@ -347,10 +346,6 @@ func (c *Chain) pull() error {
c.logger.Info("Onboarding finished successfully, pulled blocks up to join-block")
}

if c.joinBlock != nil && !proto.Equal(c.ledgerResources.Block(c.joinBlock.Header.Number).Data, c.joinBlock.Data) {
c.logger.Panicf("Join block (%d) we pulled mismatches block we joined with", c.joinBlock.Header.Number)
}

err = c.pullAfterJoin()
if err != nil {
return errors.WithMessage(err, "failed to pull after join block")
Expand Down Expand Up @@ -479,8 +474,9 @@ func (c *Chain) pullUntilLatestWithRetry(latestNetworkHeight uint64, updateEndpo
break
}

c.logger.Debugf("Error while trying to pull to latest height: %d; going to try again in %v",
latestNetworkHeight, retryInterval)
c.logger.Debugf("Error while trying to pull to latest height: %d; going to try again in %v; error: %s",
latestNetworkHeight, retryInterval, errPull)

select {
case <-c.stopChan:
c.logger.Debug("Received a stop signal")
Expand Down Expand Up @@ -527,11 +523,22 @@ func (c *Chain) pullUntilTarget(targetHeight uint64, updateEndpoints bool) (uint
if nextBlock == nil {
return n, errors.WithMessagef(cluster.ErrRetryCountExhausted, "failed to pull block %d", seq)
}

reportedPrevHash := nextBlock.Header.PreviousHash
if (nextBlock.Header.Number > 0) && !bytes.Equal(reportedPrevHash, actualPrevHash) {
return n, errors.Errorf("block header mismatch on sequence %d, expected %x, got %x",
return n, errors.Errorf("block header previous hash mismatch on sequence %d, expected %x, got %x",
nextBlock.Header.Number, actualPrevHash, reportedPrevHash)
}

if c.joinBlock != nil && c.joinBlock.Header.Number == nextBlock.Header.Number {
// We don't need to verify the block.Data because we verify the join-block's DataHash against the
// hash(join-block.Data) when we verify it during the `Join` REST API call
if !proto.Equal(nextBlock.Header, c.joinBlock.Header) {
c.logger.Errorf("Block header mismatch between the block we pulled and the block we joined with, sequence %d", c.joinBlock.Header.Number)
return n, errors.Errorf("block header mismatch between the block we pulled and the block we joined with, sequence %d", c.joinBlock.Header.Number)
}
}

actualPrevHash = protoutil.BlockHeaderHash(nextBlock.Header)
if err := c.ledgerResources.Append(nextBlock); err != nil {
return n, errors.WithMessagef(err, "failed to append block %d to the ledger", nextBlock.Header.Number)
Expand Down
Loading

0 comments on commit d45719c

Please sign in to comment.