github.com/myafeier/fabric@v1.0.1-0.20170722181825-3a4b1f2bce86/orderer/common/multichannel/manager_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package multichannel 8 9 import ( 10 "reflect" 11 "testing" 12 "time" 13 14 "github.com/golang/protobuf/proto" 15 "github.com/hyperledger/fabric/common/config" 16 "github.com/hyperledger/fabric/common/configtx" 17 genesisconfig "github.com/hyperledger/fabric/common/configtx/tool/localconfig" 18 "github.com/hyperledger/fabric/common/configtx/tool/provisional" 19 mockcrypto "github.com/hyperledger/fabric/common/mocks/crypto" 20 "github.com/hyperledger/fabric/msp" 21 "github.com/hyperledger/fabric/orderer/common/ledger" 22 ramledger "github.com/hyperledger/fabric/orderer/common/ledger/ram" 23 cb "github.com/hyperledger/fabric/protos/common" 24 ab "github.com/hyperledger/fabric/protos/orderer" 25 "github.com/hyperledger/fabric/protos/utils" 26 27 mmsp "github.com/hyperledger/fabric/common/mocks/msp" 28 logging "github.com/op/go-logging" 29 "github.com/stretchr/testify/assert" 30 ) 31 32 var conf, singleMSPConf, noConsortiumConf *genesisconfig.Profile 33 var genesisBlock, singleMSPGenesisBlock, noConsortiumGenesisBlock *cb.Block 34 var mockSigningIdentity msp.SigningIdentity 35 36 const NoConsortiumChain = "no-consortium-chain" 37 38 func init() { 39 logging.SetLevel(logging.DEBUG, "") 40 mockSigningIdentity, _ = mmsp.NewNoopMsp().GetDefaultSigningIdentity() 41 42 conf = genesisconfig.Load(genesisconfig.SampleInsecureProfile) 43 genesisBlock = provisional.New(conf).GenesisBlock() 44 45 singleMSPConf = genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile) 46 singleMSPGenesisBlock = provisional.New(singleMSPConf).GenesisBlock() 47 48 noConsortiumConf = genesisconfig.Load("SampleNoConsortium") 49 noConsortiumGenesisBlock = provisional.New(noConsortiumConf).GenesisBlockForChannel(NoConsortiumChain) 50 } 51 52 func mockCrypto() *mockCryptoHelper { 53 return &mockCryptoHelper{LocalSigner: mockcrypto.FakeLocalSigner} 54 } 55 56 type mockCryptoHelper struct { 57 *mockcrypto.LocalSigner 58 } 59 60 func (mch mockCryptoHelper) VerifySignature(sd *cb.SignedData) error { 61 return nil 62 } 63 64 func NewRAMLedgerAndFactory(maxSize int) (ledger.Factory, ledger.ReadWriter) { 65 rlf := ramledger.New(10) 66 rl, err := rlf.GetOrCreate(provisional.TestChainID) 67 if err != nil { 68 panic(err) 69 } 70 err = rl.Append(genesisBlock) 71 if err != nil { 72 panic(err) 73 } 74 return rlf, rl 75 } 76 77 func NewRAMLedgerAndFactoryWithMSP() (ledger.Factory, ledger.ReadWriter) { 78 rlf := ramledger.New(10) 79 80 rl, err := rlf.GetOrCreate(provisional.TestChainID) 81 if err != nil { 82 panic(err) 83 } 84 err = rl.Append(singleMSPGenesisBlock) 85 if err != nil { 86 panic(err) 87 } 88 return rlf, rl 89 } 90 91 func NewRAMLedger(maxSize int) ledger.ReadWriter { 92 _, rl := NewRAMLedgerAndFactory(maxSize) 93 return rl 94 } 95 96 // Tests for a normal chain which contains 3 config transactions and other normal transactions to make sure the right one returned 97 func TestGetConfigTx(t *testing.T) { 98 rl := NewRAMLedger(10) 99 for i := 0; i < 5; i++ { 100 rl.Append(ledger.CreateNextBlock(rl, []*cb.Envelope{makeNormalTx(provisional.TestChainID, i)})) 101 } 102 rl.Append(ledger.CreateNextBlock(rl, []*cb.Envelope{makeConfigTx(provisional.TestChainID, 5)})) 103 ctx := makeConfigTx(provisional.TestChainID, 6) 104 rl.Append(ledger.CreateNextBlock(rl, []*cb.Envelope{ctx})) 105 106 block := ledger.CreateNextBlock(rl, []*cb.Envelope{makeNormalTx(provisional.TestChainID, 7)}) 107 block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = utils.MarshalOrPanic(&cb.Metadata{Value: utils.MarshalOrPanic(&cb.LastConfig{Index: 7})}) 108 rl.Append(block) 109 110 pctx := getConfigTx(rl) 111 assert.Equal(t, pctx, ctx, "Did not select most recent config transaction") 112 } 113 114 // Tests a chain which contains blocks with multi-transactions mixed with config txs, and a single tx which is not a config tx, none count as config blocks so nil should return 115 func TestGetConfigTxFailure(t *testing.T) { 116 rl := NewRAMLedger(10) 117 for i := 0; i < 10; i++ { 118 rl.Append(ledger.CreateNextBlock(rl, []*cb.Envelope{ 119 makeNormalTx(provisional.TestChainID, i), 120 makeConfigTx(provisional.TestChainID, i), 121 })) 122 } 123 rl.Append(ledger.CreateNextBlock(rl, []*cb.Envelope{makeNormalTx(provisional.TestChainID, 11)})) 124 assert.Panics(t, func() { getConfigTx(rl) }, "Should have panicked because there was no config tx") 125 126 block := ledger.CreateNextBlock(rl, []*cb.Envelope{makeNormalTx(provisional.TestChainID, 12)}) 127 block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG] = []byte("bad metadata") 128 assert.Panics(t, func() { getConfigTx(rl) }, "Should have panicked because of bad last config metadata") 129 } 130 131 // This test checks to make sure the orderer refuses to come up if it cannot find a system channel 132 func TestNoSystemChain(t *testing.T) { 133 lf := ramledger.New(10) 134 135 consenters := make(map[string]Consenter) 136 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 137 138 assert.Panics(t, func() { NewManagerImpl(lf, consenters, mockCrypto()) }, "Should have panicked when starting without a system chain") 139 } 140 141 // This test checks to make sure that the orderer refuses to come up if there are multiple system channels 142 func TestMultiSystemChannel(t *testing.T) { 143 lf := ramledger.New(10) 144 145 for _, id := range []string{"foo", "bar"} { 146 rl, err := lf.GetOrCreate(id) 147 assert.NoError(t, err) 148 149 err = rl.Append(provisional.New(conf).GenesisBlockForChannel(id)) 150 assert.NoError(t, err) 151 } 152 153 consenters := make(map[string]Consenter) 154 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 155 156 assert.Panics(t, func() { NewManagerImpl(lf, consenters, mockCrypto()) }, "Two system channels should have caused panic") 157 } 158 159 // This test essentially brings the entire system up and is ultimately what main.go will replicate 160 func TestManagerImpl(t *testing.T) { 161 lf, rl := NewRAMLedgerAndFactory(10) 162 163 consenters := make(map[string]Consenter) 164 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 165 166 manager := NewManagerImpl(lf, consenters, mockCrypto()) 167 168 _, ok := manager.GetChain("Fake") 169 assert.False(t, ok, "Should not have found a chain that was not created") 170 171 chainSupport, ok := manager.GetChain(provisional.TestChainID) 172 assert.True(t, ok, "Should have gotten chain which was initialized by ramledger") 173 174 messages := make([]*cb.Envelope, conf.Orderer.BatchSize.MaxMessageCount) 175 for i := 0; i < int(conf.Orderer.BatchSize.MaxMessageCount); i++ { 176 messages[i] = makeNormalTx(provisional.TestChainID, i) 177 } 178 179 for _, message := range messages { 180 chainSupport.Enqueue(message) 181 } 182 183 it, _ := rl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 1}}}) 184 defer it.Close() 185 select { 186 case <-it.ReadyChan(): 187 block, status := it.Next() 188 assert.Equal(t, cb.Status_SUCCESS, status, "Could not retrieve block") 189 for i := 0; i < int(conf.Orderer.BatchSize.MaxMessageCount); i++ { 190 assert.Equal(t, messages[i], utils.ExtractEnvelopeOrPanic(block, i), "Block contents wrong at index %d", i) 191 } 192 case <-time.After(time.Second): 193 t.Fatalf("Block 1 not produced after timeout") 194 } 195 } 196 197 func TestNewChannelConfig(t *testing.T) { 198 lf, _ := NewRAMLedgerAndFactoryWithMSP() 199 200 consenters := make(map[string]Consenter) 201 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 202 manager := NewManagerImpl(lf, consenters, mockCrypto()) 203 204 t.Run("BadPayload", func(t *testing.T) { 205 _, err := manager.NewChannelConfig(&cb.Envelope{Payload: []byte("bad payload")}) 206 assert.Error(t, err, "Should not be able to create new channel config from bad payload.") 207 }) 208 209 for _, tc := range []struct { 210 name string 211 payload *cb.Payload 212 regex string 213 }{ 214 { 215 "BadPayloadData", 216 &cb.Payload{ 217 Data: []byte("bad payload data"), 218 }, 219 "^Failing initial channel config creation because of config update envelope unmarshaling error:", 220 }, 221 { 222 "BadConfigUpdate", 223 &cb.Payload{ 224 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 225 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 226 ConfigUpdate: []byte("bad config update envelope data"), 227 }), 228 }, 229 "^Failing initial channel config creation because of config update unmarshaling error:", 230 }, 231 { 232 "EmptyConfigUpdateWriteSet", 233 &cb.Payload{ 234 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 235 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 236 ConfigUpdate: utils.MarshalOrPanic( 237 &cb.ConfigUpdate{}, 238 ), 239 }), 240 }, 241 "^Config update has an empty writeset$", 242 }, 243 { 244 "WriteSetNoGroups", 245 &cb.Payload{ 246 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 247 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 248 ConfigUpdate: utils.MarshalOrPanic( 249 &cb.ConfigUpdate{ 250 WriteSet: &cb.ConfigGroup{}, 251 }, 252 ), 253 }), 254 }, 255 "^Config update has missing application group$", 256 }, 257 { 258 "WriteSetNoApplicationGroup", 259 &cb.Payload{ 260 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 261 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 262 ConfigUpdate: utils.MarshalOrPanic( 263 &cb.ConfigUpdate{ 264 WriteSet: &cb.ConfigGroup{ 265 Groups: map[string]*cb.ConfigGroup{}, 266 }, 267 }, 268 ), 269 }), 270 }, 271 "^Config update has missing application group$", 272 }, 273 { 274 "BadWriteSetApplicationGroupVersion", 275 &cb.Payload{ 276 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 277 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 278 ConfigUpdate: utils.MarshalOrPanic( 279 &cb.ConfigUpdate{ 280 WriteSet: &cb.ConfigGroup{ 281 Groups: map[string]*cb.ConfigGroup{ 282 config.ApplicationGroupKey: &cb.ConfigGroup{ 283 Version: 100, 284 }, 285 }, 286 }, 287 }, 288 ), 289 }), 290 }, 291 "^Config update for channel creation does not set application group version to 1,", 292 }, 293 { 294 "MissingWriteSetConsortiumValue", 295 &cb.Payload{ 296 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 297 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 298 ConfigUpdate: utils.MarshalOrPanic( 299 &cb.ConfigUpdate{ 300 WriteSet: &cb.ConfigGroup{ 301 Groups: map[string]*cb.ConfigGroup{ 302 config.ApplicationGroupKey: &cb.ConfigGroup{ 303 Version: 1, 304 }, 305 }, 306 Values: map[string]*cb.ConfigValue{}, 307 }, 308 }, 309 ), 310 }), 311 }, 312 "^Consortium config value missing$", 313 }, 314 { 315 "BadWriteSetConsortiumValueValue", 316 &cb.Payload{ 317 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 318 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 319 ConfigUpdate: utils.MarshalOrPanic( 320 &cb.ConfigUpdate{ 321 WriteSet: &cb.ConfigGroup{ 322 Groups: map[string]*cb.ConfigGroup{ 323 config.ApplicationGroupKey: &cb.ConfigGroup{ 324 Version: 1, 325 }, 326 }, 327 Values: map[string]*cb.ConfigValue{ 328 config.ConsortiumKey: &cb.ConfigValue{ 329 Value: []byte("bad consortium value"), 330 }, 331 }, 332 }, 333 }, 334 ), 335 }), 336 }, 337 "^Error reading unmarshaling consortium name:", 338 }, 339 { 340 "UnknownConsortiumName", 341 &cb.Payload{ 342 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 343 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 344 ConfigUpdate: utils.MarshalOrPanic( 345 &cb.ConfigUpdate{ 346 WriteSet: &cb.ConfigGroup{ 347 Groups: map[string]*cb.ConfigGroup{ 348 config.ApplicationGroupKey: &cb.ConfigGroup{ 349 Version: 1, 350 }, 351 }, 352 Values: map[string]*cb.ConfigValue{ 353 config.ConsortiumKey: &cb.ConfigValue{ 354 Value: utils.MarshalOrPanic( 355 &cb.Consortium{ 356 Name: "NotTheNameYouAreLookingFor", 357 }, 358 ), 359 }, 360 }, 361 }, 362 }, 363 ), 364 }), 365 }, 366 "^Unknown consortium name:", 367 }, 368 { 369 "Missing consortium members", 370 &cb.Payload{ 371 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 372 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 373 ConfigUpdate: utils.MarshalOrPanic( 374 &cb.ConfigUpdate{ 375 WriteSet: &cb.ConfigGroup{ 376 Groups: map[string]*cb.ConfigGroup{ 377 config.ApplicationGroupKey: &cb.ConfigGroup{ 378 Version: 1, 379 }, 380 }, 381 Values: map[string]*cb.ConfigValue{ 382 config.ConsortiumKey: &cb.ConfigValue{ 383 Value: utils.MarshalOrPanic( 384 &cb.Consortium{ 385 Name: genesisconfig.SampleConsortiumName, 386 }, 387 ), 388 }, 389 }, 390 }, 391 }, 392 ), 393 }), 394 }, 395 "Proposed configuration has no application group members, but consortium contains members", 396 }, 397 { 398 "Member not in consortium", 399 &cb.Payload{ 400 Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, 0, "", epoch))}, 401 Data: utils.MarshalOrPanic(&cb.ConfigUpdateEnvelope{ 402 ConfigUpdate: utils.MarshalOrPanic( 403 &cb.ConfigUpdate{ 404 WriteSet: &cb.ConfigGroup{ 405 Groups: map[string]*cb.ConfigGroup{ 406 config.ApplicationGroupKey: &cb.ConfigGroup{ 407 Version: 1, 408 Groups: map[string]*cb.ConfigGroup{ 409 "BadOrgName": &cb.ConfigGroup{}, 410 }, 411 }, 412 }, 413 Values: map[string]*cb.ConfigValue{ 414 config.ConsortiumKey: &cb.ConfigValue{ 415 Value: utils.MarshalOrPanic( 416 &cb.Consortium{ 417 Name: genesisconfig.SampleConsortiumName, 418 }, 419 ), 420 }, 421 }, 422 }, 423 }, 424 ), 425 }), 426 }, 427 "Attempted to include a member which is not in the consortium", 428 }, 429 } { 430 t.Run(tc.name, func(t *testing.T) { 431 _, err := manager.NewChannelConfig(&cb.Envelope{Payload: utils.MarshalOrPanic(tc.payload)}) 432 if assert.Error(t, err) { 433 assert.Regexp(t, tc.regex, err.Error()) 434 } 435 }) 436 } 437 // SampleConsortium 438 } 439 440 func TestMismatchedChannelIDs(t *testing.T) { 441 innerChannelID := "foo" 442 outerChannelID := "bar" 443 template := configtx.NewChainCreationTemplate(genesisconfig.SampleConsortiumName, nil) 444 configUpdateEnvelope, err := template.Envelope(innerChannelID) 445 createTx, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, outerChannelID, nil, configUpdateEnvelope, msgVersion, epoch) 446 assert.NoError(t, err) 447 448 lf, _ := NewRAMLedgerAndFactory(10) 449 450 consenters := make(map[string]Consenter) 451 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 452 453 manager := NewManagerImpl(lf, consenters, mockCrypto()) 454 455 _, err = manager.NewChannelConfig(createTx) 456 assert.Error(t, err, "Mismatched channel IDs") 457 assert.Regexp(t, "mismatched channel IDs", err.Error()) 458 } 459 460 // This test brings up the entire system, with the mock consenter, including the broadcasters etc. and creates a new chain 461 func TestNewChain(t *testing.T) { 462 expectedLastConfigBlockNumber := uint64(0) 463 expectedLastConfigSeq := uint64(1) 464 newChainID := "test-new-chain" 465 466 lf, rl := NewRAMLedgerAndFactory(10) 467 468 consenters := make(map[string]Consenter) 469 consenters[conf.Orderer.OrdererType] = &mockConsenter{} 470 471 manager := NewManagerImpl(lf, consenters, mockCrypto()) 472 473 envConfigUpdate, err := configtx.MakeChainCreationTransaction(newChainID, genesisconfig.SampleConsortiumName, mockSigningIdentity) 474 assert.NoError(t, err, "Constructing chain creation tx") 475 476 cm, err := manager.NewChannelConfig(envConfigUpdate) 477 assert.NoError(t, err, "Constructing initial channel config") 478 479 configEnv, err := cm.ProposeConfigUpdate(envConfigUpdate) 480 assert.NoError(t, err, "Proposing initial update") 481 assert.Equal(t, expectedLastConfigSeq, configEnv.GetConfig().Sequence, "Sequence of config envelope for new channel should always be set to %d", expectedLastConfigSeq) 482 483 ingressTx, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG, newChainID, mockCrypto(), configEnv, msgVersion, epoch) 484 assert.NoError(t, err, "Creating ingresstx") 485 486 wrapped := wrapConfigTx(ingressTx) 487 488 chainSupport, ok := manager.GetChain(manager.SystemChannelID()) 489 assert.True(t, ok, "Could not find system channel") 490 491 chainSupport.Enqueue(wrapped) 492 func() { 493 it, _ := rl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 1}}}) 494 defer it.Close() 495 select { 496 case <-it.ReadyChan(): 497 block, status := it.Next() 498 if status != cb.Status_SUCCESS { 499 t.Fatalf("Could not retrieve block") 500 } 501 if len(block.Data.Data) != 1 { 502 t.Fatalf("Should have had only one message in the orderer transaction block") 503 } 504 505 assert.Equal(t, wrapped, utils.UnmarshalEnvelopeOrPanic(block.Data.Data[0]), "Orderer config block contains wrong transaction") 506 case <-time.After(time.Second): 507 t.Fatalf("Block 1 not produced after timeout in system chain") 508 } 509 }() 510 511 chainSupport, ok = manager.GetChain(newChainID) 512 513 if !ok { 514 t.Fatalf("Should have gotten new chain which was created") 515 } 516 517 messages := make([]*cb.Envelope, conf.Orderer.BatchSize.MaxMessageCount) 518 for i := 0; i < int(conf.Orderer.BatchSize.MaxMessageCount); i++ { 519 messages[i] = makeNormalTx(newChainID, i) 520 } 521 522 for _, message := range messages { 523 chainSupport.Enqueue(message) 524 } 525 526 it, _ := chainSupport.Reader().Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 0}}}) 527 defer it.Close() 528 select { 529 case <-it.ReadyChan(): 530 block, status := it.Next() 531 if status != cb.Status_SUCCESS { 532 t.Fatalf("Could not retrieve new chain genesis block") 533 } 534 testLastConfigBlockNumber(t, block, expectedLastConfigBlockNumber) 535 if len(block.Data.Data) != 1 { 536 t.Fatalf("Should have had only one message in the new genesis block") 537 } 538 539 assert.Equal(t, ingressTx, utils.UnmarshalEnvelopeOrPanic(block.Data.Data[0]), "Genesis block contains wrong transaction") 540 case <-time.After(time.Second): 541 t.Fatalf("Block 1 not produced after timeout in system chain") 542 } 543 544 select { 545 case <-it.ReadyChan(): 546 block, status := it.Next() 547 if status != cb.Status_SUCCESS { 548 t.Fatalf("Could not retrieve block on new chain") 549 } 550 testLastConfigBlockNumber(t, block, expectedLastConfigBlockNumber) 551 for i := 0; i < int(conf.Orderer.BatchSize.MaxMessageCount); i++ { 552 if !reflect.DeepEqual(utils.ExtractEnvelopeOrPanic(block, i), messages[i]) { 553 t.Errorf("Block contents wrong at index %d in new chain", i) 554 } 555 } 556 case <-time.After(time.Second): 557 t.Fatalf("Block 1 not produced after timeout on new chain") 558 } 559 560 testRestartedChainSupport(t, chainSupport, consenters, expectedLastConfigSeq) 561 } 562 563 func testRestartedChainSupport(t *testing.T, cs ChainSupport, consenters map[string]Consenter, expectedLastConfigSeq uint64) { 564 ccs, ok := cs.(*chainSupport) 565 assert.True(t, ok, "Casting error") 566 rcs := newChainSupport(ccs.filters, ccs.ledgerResources, consenters, mockCrypto()) 567 assert.Equal(t, expectedLastConfigSeq, rcs.lastConfigSeq, "On restart, incorrect lastConfigSeq") 568 } 569 570 func testLastConfigBlockNumber(t *testing.T, block *cb.Block, expectedBlockNumber uint64) { 571 metadataItem := &cb.Metadata{} 572 err := proto.Unmarshal(block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIG], metadataItem) 573 assert.NoError(t, err, "Block should carry LAST_CONFIG metadata item") 574 lastConfig := &cb.LastConfig{} 575 err = proto.Unmarshal(metadataItem.Value, lastConfig) 576 assert.NoError(t, err, "LAST_CONFIG metadata item should carry last config value") 577 assert.Equal(t, expectedBlockNumber, lastConfig.Index, "LAST_CONFIG value should point to last config block") 578 }