github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/gossip/state/state.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package state 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 pb "github.com/golang/protobuf/proto" 28 "github.com/hyperledger/fabric/core/committer" 29 "github.com/hyperledger/fabric/gossip/api" 30 "github.com/hyperledger/fabric/gossip/comm" 31 common2 "github.com/hyperledger/fabric/gossip/common" 32 "github.com/hyperledger/fabric/gossip/discovery" 33 "github.com/hyperledger/fabric/gossip/util" 34 "github.com/hyperledger/fabric/protos/common" 35 proto "github.com/hyperledger/fabric/protos/gossip" 36 "github.com/op/go-logging" 37 "github.com/spf13/viper" 38 ) 39 40 // GossipStateProvider is the interface to acquire sequences of the ledger blocks 41 // capable to full fill missing blocks by running state replication and 42 // sending request to get missing block to other nodes 43 type GossipStateProvider interface { 44 // Retrieve block with sequence number equal to index 45 GetBlock(index uint64) *common.Block 46 47 AddPayload(payload *proto.Payload) error 48 49 // Stop terminates state transfer object 50 Stop() 51 } 52 53 const ( 54 defAntiEntropyInterval = 10 * time.Second 55 defAntiEntropyStateResponseTimeout = 3 * time.Second 56 defAntiEntropyBatchSize = 10 57 58 defChannelBufferSize = 100 59 defAntiEntropyMaxRetries = 3 60 61 defMaxBlockDistance = 100 62 63 blocking = true 64 nonBlocking = false 65 66 enqueueRetryInterval = time.Millisecond * 100 67 ) 68 69 // GossipAdapter defines gossip/communication required interface for state provider 70 type GossipAdapter interface { 71 // Send sends a message to remote peers 72 Send(msg *proto.GossipMessage, peers ...*comm.RemotePeer) 73 74 // Accept returns a dedicated read-only channel for messages sent by other nodes that match a certain predicate. 75 // If passThrough is false, the messages are processed by the gossip layer beforehand. 76 // If passThrough is true, the gossip layer doesn't intervene and the messages 77 // can be used to send a reply back to the sender 78 Accept(acceptor common2.MessageAcceptor, passThrough bool) (<-chan *proto.GossipMessage, <-chan proto.ReceivedMessage) 79 80 // UpdateChannelMetadata updates the self metadata the peer 81 // publishes to other peers about its channel-related state 82 UpdateChannelMetadata(metadata []byte, chainID common2.ChainID) 83 84 // PeersOfChannel returns the NetworkMembers considered alive 85 // and also subscribed to the channel given 86 PeersOfChannel(common2.ChainID) []discovery.NetworkMember 87 } 88 89 // GossipStateProviderImpl the implementation of the GossipStateProvider interface 90 // the struct to handle in memory sliding window of 91 // new ledger block to be acquired by hyper ledger 92 type GossipStateProviderImpl struct { 93 // MessageCryptoService 94 mcs api.MessageCryptoService 95 96 // Chain id 97 chainID string 98 99 // The gossiping service 100 gossip GossipAdapter 101 102 // Channel to read gossip messages from 103 gossipChan <-chan *proto.GossipMessage 104 105 commChan <-chan proto.ReceivedMessage 106 107 // Queue of payloads which wasn't acquired yet 108 payloads PayloadsBuffer 109 110 committer committer.Committer 111 112 stateResponseCh chan proto.ReceivedMessage 113 114 stateRequestCh chan proto.ReceivedMessage 115 116 stopCh chan struct{} 117 118 done sync.WaitGroup 119 120 once sync.Once 121 122 stateTransferActive int32 123 } 124 125 var logger *logging.Logger // package-level logger 126 127 func init() { 128 logger = util.GetLogger(util.LoggingStateModule, "") 129 } 130 131 // NewGossipStateProvider creates initialized instance of gossip state provider 132 func NewGossipStateProvider(chainID string, g GossipAdapter, committer committer.Committer, mcs api.MessageCryptoService) GossipStateProvider { 133 logger := util.GetLogger(util.LoggingStateModule, "") 134 135 gossipChan, _ := g.Accept(func(message interface{}) bool { 136 // Get only data messages 137 return message.(*proto.GossipMessage).IsDataMsg() && 138 bytes.Equal(message.(*proto.GossipMessage).Channel, []byte(chainID)) 139 }, false) 140 141 remoteStateMsgFilter := func(message interface{}) bool { 142 receivedMsg := message.(proto.ReceivedMessage) 143 msg := receivedMsg.GetGossipMessage() 144 if !msg.IsRemoteStateMessage() { 145 return false 146 } 147 // If we're not running with authentication, no point 148 // in enforcing access control 149 if !receivedMsg.GetConnectionInfo().IsAuthenticated() { 150 return true 151 } 152 connInfo := receivedMsg.GetConnectionInfo() 153 authErr := mcs.VerifyByChannel(msg.Channel, connInfo.Identity, connInfo.Auth.Signature, connInfo.Auth.SignedData) 154 if authErr != nil { 155 logger.Warning("Got unauthorized nodeMetastate transfer request from", string(connInfo.Identity)) 156 return false 157 } 158 return true 159 } 160 161 // Filter message which are only relevant for nodeMetastate transfer 162 _, commChan := g.Accept(remoteStateMsgFilter, true) 163 164 height, err := committer.LedgerHeight() 165 if height == 0 { 166 // Panic here since this is an indication of invalid situation which should not happen in normal 167 // code path. 168 logger.Panic("Committer height cannot be zero, ledger should include at least one block (genesis).") 169 } 170 171 if err != nil { 172 logger.Error("Could not read ledger info to obtain current ledger height due to: ", err) 173 // Exiting as without ledger it will be impossible 174 // to deliver new blocks 175 return nil 176 } 177 178 s := &GossipStateProviderImpl{ 179 // MessageCryptoService 180 mcs: mcs, 181 182 // Chain ID 183 chainID: chainID, 184 185 // Instance of the gossip 186 gossip: g, 187 188 // Channel to read new messages from 189 gossipChan: gossipChan, 190 191 // Channel to read direct messages from other peers 192 commChan: commChan, 193 194 // Create a queue for payload received 195 payloads: NewPayloadsBuffer(height), 196 197 committer: committer, 198 199 stateResponseCh: make(chan proto.ReceivedMessage, defChannelBufferSize), 200 201 stateRequestCh: make(chan proto.ReceivedMessage, defChannelBufferSize), 202 203 stopCh: make(chan struct{}, 1), 204 205 stateTransferActive: 0, 206 207 once: sync.Once{}, 208 } 209 210 nodeMetastate := NewNodeMetastate(height - 1) 211 212 logger.Infof("Updating node metadata information, "+ 213 "current ledger sequence is at = %d, next expected block is = %d", nodeMetastate.LedgerHeight, s.payloads.Next()) 214 215 b, err := nodeMetastate.Bytes() 216 if err == nil { 217 logger.Debug("Updating gossip metadate nodeMetastate", nodeMetastate) 218 g.UpdateChannelMetadata(b, common2.ChainID(s.chainID)) 219 } else { 220 logger.Errorf("Unable to serialize node meta nodeMetastate, error = %s", err) 221 } 222 223 s.done.Add(4) 224 225 // Listen for incoming communication 226 go s.listen() 227 // Deliver in order messages into the incoming channel 228 go s.deliverPayloads() 229 // Execute anti entropy to fill missing gaps 230 go s.antiEntropy() 231 // Taking care of state request messages 232 go s.processStateRequests() 233 234 return s 235 } 236 237 func (s *GossipStateProviderImpl) listen() { 238 defer s.done.Done() 239 240 for { 241 select { 242 case msg := <-s.gossipChan: 243 logger.Debug("Received new message via gossip channel") 244 go s.queueNewMessage(msg) 245 case msg := <-s.commChan: 246 logger.Debug("Direct message ", msg) 247 go s.directMessage(msg) 248 case <-s.stopCh: 249 s.stopCh <- struct{}{} 250 logger.Debug("Stop listening for new messages") 251 return 252 } 253 } 254 } 255 256 func (s *GossipStateProviderImpl) directMessage(msg proto.ReceivedMessage) { 257 logger.Debug("[ENTER] -> directMessage") 258 defer logger.Debug("[EXIT] -> directMessage") 259 260 if msg == nil { 261 logger.Error("Got nil message via end-to-end channel, should not happen!") 262 return 263 } 264 265 if !bytes.Equal(msg.GetGossipMessage().Channel, []byte(s.chainID)) { 266 logger.Warning("Received state transfer request for channel", 267 string(msg.GetGossipMessage().Channel), "while expecting channel", s.chainID, "skipping request...") 268 return 269 } 270 271 incoming := msg.GetGossipMessage() 272 273 if incoming.GetStateRequest() != nil { 274 if len(s.stateRequestCh) < defChannelBufferSize { 275 // Forward state request to the channel, if there are too 276 // many message of state request ignore to avoid flooding. 277 s.stateRequestCh <- msg 278 } 279 } else if incoming.GetStateResponse() != nil { 280 // If no state transfer procedure activate there is 281 // no reason to process the message 282 if atomic.LoadInt32(&s.stateTransferActive) == 1 { 283 // Send signal of state response message 284 s.stateResponseCh <- msg 285 } 286 } 287 } 288 289 func (s *GossipStateProviderImpl) processStateRequests() { 290 defer s.done.Done() 291 292 for { 293 select { 294 case msg := <-s.stateRequestCh: 295 s.handleStateRequest(msg) 296 case <-s.stopCh: 297 s.stopCh <- struct{}{} 298 return 299 } 300 } 301 } 302 303 // Handle state request message, validate batch size, read current leader state to 304 // obtain required blocks, build response message and send it back 305 func (s *GossipStateProviderImpl) handleStateRequest(msg proto.ReceivedMessage) { 306 if msg == nil { 307 return 308 } 309 request := msg.GetGossipMessage().GetStateRequest() 310 311 batchSize := request.EndSeqNum - request.StartSeqNum 312 if batchSize > defAntiEntropyBatchSize { 313 logger.Errorf("Requesting blocks batchSize size (%d) greater than configured allowed"+ 314 " (%d) batching for anti-entropy. Ignoring request...", batchSize, defAntiEntropyBatchSize) 315 return 316 } 317 318 if request.StartSeqNum > request.EndSeqNum { 319 logger.Errorf("Invalid sequence interval [%d...%d], ignoring request...", request.StartSeqNum, request.EndSeqNum) 320 return 321 } 322 323 currentHeight, err := s.committer.LedgerHeight() 324 if err != nil { 325 logger.Errorf("Cannot access to current ledger height, due to %s", err) 326 return 327 } 328 if currentHeight < request.EndSeqNum { 329 logger.Warningf("Received state request to transfer blocks with sequence numbers higher [%d...%d] "+ 330 "than available in ledger (%d)", request.StartSeqNum, request.StartSeqNum, currentHeight) 331 } 332 333 endSeqNum := min(currentHeight, request.EndSeqNum) 334 335 response := &proto.RemoteStateResponse{Payloads: make([]*proto.Payload, 0)} 336 for seqNum := request.StartSeqNum; seqNum <= endSeqNum; seqNum++ { 337 logger.Debug("Reading block ", seqNum, " from the committer service") 338 blocks := s.committer.GetBlocks([]uint64{seqNum}) 339 340 if len(blocks) == 0 { 341 logger.Errorf("Wasn't able to read block with sequence number %d from ledger, skipping....", seqNum) 342 continue 343 } 344 345 blockBytes, err := pb.Marshal(blocks[0]) 346 if err != nil { 347 logger.Errorf("Could not marshal block: %s", err) 348 } 349 350 response.Payloads = append(response.Payloads, &proto.Payload{ 351 SeqNum: seqNum, 352 Data: blockBytes, 353 }) 354 } 355 // Sending back response with missing blocks 356 msg.Respond(&proto.GossipMessage{ 357 // Copy nonce field from the request, so it will be possible to match response 358 Nonce: msg.GetGossipMessage().Nonce, 359 Tag: proto.GossipMessage_CHAN_OR_ORG, 360 Channel: []byte(s.chainID), 361 Content: &proto.GossipMessage_StateResponse{response}, 362 }) 363 } 364 365 func (s *GossipStateProviderImpl) handleStateResponse(msg proto.ReceivedMessage) (uint64, error) { 366 max := uint64(0) 367 // Send signal that response for given nonce has been received 368 response := msg.GetGossipMessage().GetStateResponse() 369 // Extract payloads, verify and push into buffer 370 if len(response.GetPayloads()) == 0 { 371 return uint64(0), errors.New("Received state tranfer response without payload") 372 } 373 for _, payload := range response.GetPayloads() { 374 logger.Debugf("Received payload with sequence number %d.", payload.SeqNum) 375 if err := s.mcs.VerifyBlock(common2.ChainID(s.chainID), payload.SeqNum, payload.Data); err != nil { 376 logger.Warningf("Error verifying block with sequence number %d, due to %s", payload.SeqNum, err) 377 return uint64(0), err 378 } 379 if max < payload.SeqNum { 380 max = payload.SeqNum 381 } 382 383 err := s.addPayload(payload, blocking) 384 if err != nil { 385 logger.Warningf("Payload with sequence number %d wasn't added to payload buffer: %v", payload.SeqNum, err) 386 } 387 } 388 return max, nil 389 } 390 391 // Stop function send halting signal to all go routines 392 func (s *GossipStateProviderImpl) Stop() { 393 // Make sure stop won't be executed twice 394 // and stop channel won't be used again 395 s.once.Do(func() { 396 s.stopCh <- struct{}{} 397 // Make sure all go-routines has finished 398 s.done.Wait() 399 // Close all resources 400 s.committer.Close() 401 close(s.stateRequestCh) 402 close(s.stateResponseCh) 403 close(s.stopCh) 404 }) 405 } 406 407 // New message notification/handler 408 func (s *GossipStateProviderImpl) queueNewMessage(msg *proto.GossipMessage) { 409 if !bytes.Equal(msg.Channel, []byte(s.chainID)) { 410 logger.Warning("Received enqueue for channel", 411 string(msg.Channel), "while expecting channel", s.chainID, "ignoring enqueue") 412 return 413 } 414 415 dataMsg := msg.GetDataMsg() 416 if dataMsg != nil { 417 if err := s.addPayload(dataMsg.GetPayload(), nonBlocking); err != nil { 418 logger.Warning("Failed adding payload:", err) 419 return 420 } 421 logger.Debugf("Received new payload with sequence number = [%d]", dataMsg.Payload.SeqNum) 422 } else { 423 logger.Debug("Gossip message received is not of data message type, usually this should not happen.") 424 } 425 } 426 427 func (s *GossipStateProviderImpl) deliverPayloads() { 428 defer s.done.Done() 429 430 for { 431 select { 432 // Wait for notification that next seq has arrived 433 case <-s.payloads.Ready(): 434 logger.Debugf("Ready to transfer payloads to the ledger, next sequence number is = [%d]", s.payloads.Next()) 435 // Collect all subsequent payloads 436 for payload := s.payloads.Pop(); payload != nil; payload = s.payloads.Pop() { 437 rawBlock := &common.Block{} 438 if err := pb.Unmarshal(payload.Data, rawBlock); err != nil { 439 logger.Errorf("Error getting block with seqNum = %d due to (%s)...dropping block", payload.SeqNum, err) 440 continue 441 } 442 if rawBlock.Data == nil || rawBlock.Header == nil { 443 logger.Errorf("Block with claimed sequence %d has no header (%v) or data (%v)", 444 payload.SeqNum, rawBlock.Header, rawBlock.Data) 445 continue 446 } 447 logger.Debug("New block with claimed sequence number ", payload.SeqNum, " transactions num ", len(rawBlock.Data.Data)) 448 if err := s.commitBlock(rawBlock); err != nil { 449 logger.Panicf("Cannot commit block to the ledger due to %s", err) 450 } 451 } 452 case <-s.stopCh: 453 s.stopCh <- struct{}{} 454 logger.Debug("State provider has been stoped, finishing to push new blocks.") 455 return 456 } 457 } 458 } 459 460 func (s *GossipStateProviderImpl) antiEntropy() { 461 defer s.done.Done() 462 defer logger.Debug("State Provider stopped, stopping anti entropy procedure.") 463 464 for { 465 select { 466 case <-s.stopCh: 467 s.stopCh <- struct{}{} 468 return 469 case <-time.After(defAntiEntropyInterval): 470 current, err := s.committer.LedgerHeight() 471 if err != nil { 472 // Unable to read from ledger continue to the next round 473 logger.Error("Cannot obtain ledger height, due to", err) 474 continue 475 } 476 if current == 0 { 477 logger.Error("Ledger reported block height of 0 but this should be impossible") 478 continue 479 } 480 max := s.maxAvailableLedgerHeight() 481 482 if current-1 >= max { 483 continue 484 } 485 486 s.requestBlocksInRange(uint64(current), uint64(max)) 487 } 488 } 489 } 490 491 // Iterate over all available peers and check advertised meta state to 492 // find maximum available ledger height across peers 493 func (s *GossipStateProviderImpl) maxAvailableLedgerHeight() uint64 { 494 max := uint64(0) 495 for _, p := range s.gossip.PeersOfChannel(common2.ChainID(s.chainID)) { 496 if nodeMetastate, err := FromBytes(p.Metadata); err == nil { 497 if max < nodeMetastate.LedgerHeight { 498 max = nodeMetastate.LedgerHeight 499 } 500 } 501 } 502 return max 503 } 504 505 // GetBlocksInRange capable to acquire blocks with sequence 506 // numbers in the range [start...end]. 507 func (s *GossipStateProviderImpl) requestBlocksInRange(start uint64, end uint64) { 508 atomic.StoreInt32(&s.stateTransferActive, 1) 509 defer atomic.StoreInt32(&s.stateTransferActive, 0) 510 511 for prev := start; prev <= end; { 512 next := min(end, prev+defAntiEntropyBatchSize) 513 514 gossipMsg := s.stateRequestMessage(prev, next) 515 516 responseReceived := false 517 tryCounts := 0 518 519 for !responseReceived { 520 if tryCounts > defAntiEntropyMaxRetries { 521 logger.Warningf("Wasn't able to get blocks in range [%d...%d], after %d retries", 522 prev, next, tryCounts) 523 return 524 } 525 // Select peers to ask for blocks 526 peer, err := s.selectPeerToRequestFrom(next) 527 if err != nil { 528 logger.Warningf("Cannot send state request for blocks in range [%d...%d], due to", 529 prev, next, err) 530 return 531 } 532 533 logger.Debugf("State transfer, with peer %s, requesting blocks in range [%d...%d], "+ 534 "for chainID %s", peer.Endpoint, prev, next, s.chainID) 535 536 s.gossip.Send(gossipMsg, peer) 537 tryCounts++ 538 539 // Wait until timeout or response arrival 540 select { 541 case msg := <-s.stateResponseCh: 542 if msg.GetGossipMessage().Nonce != gossipMsg.Nonce { 543 continue 544 } 545 // Got corresponding response for state request, can continue 546 index, err := s.handleStateResponse(msg) 547 if err != nil { 548 logger.Warningf("Wasn't able to process state response for "+ 549 "blocks [%d...%d], due to %s", prev, next, err) 550 continue 551 } 552 prev = index + 1 553 responseReceived = true 554 case <-time.After(defAntiEntropyStateResponseTimeout): 555 case <-s.stopCh: 556 s.stopCh <- struct{}{} 557 return 558 } 559 } 560 } 561 } 562 563 // Generate state request message for given blocks in range [beginSeq...endSeq] 564 func (s *GossipStateProviderImpl) stateRequestMessage(beginSeq uint64, endSeq uint64) *proto.GossipMessage { 565 return &proto.GossipMessage{ 566 Nonce: util.RandomUInt64(), 567 Tag: proto.GossipMessage_CHAN_OR_ORG, 568 Channel: []byte(s.chainID), 569 Content: &proto.GossipMessage_StateRequest{ 570 StateRequest: &proto.RemoteStateRequest{ 571 StartSeqNum: beginSeq, 572 EndSeqNum: endSeq, 573 }, 574 }, 575 } 576 } 577 578 // Select peer which has required blocks to ask missing blocks from 579 func (s *GossipStateProviderImpl) selectPeerToRequestFrom(height uint64) (*comm.RemotePeer, error) { 580 // Filter peers which posses required range of missing blocks 581 peers := s.filterPeers(s.hasRequiredHeight(height)) 582 583 n := len(peers) 584 if n == 0 { 585 return nil, errors.New("there are no peers to ask for missing blocks from") 586 } 587 588 // Select peers to ask for blocks 589 return peers[util.RandomInt(n)], nil 590 } 591 592 // filterPeers return list of peers which aligns the predicate provided 593 func (s *GossipStateProviderImpl) filterPeers(predicate func(peer discovery.NetworkMember) bool) []*comm.RemotePeer { 594 var peers []*comm.RemotePeer 595 596 for _, member := range s.gossip.PeersOfChannel(common2.ChainID(s.chainID)) { 597 if predicate(member) { 598 peers = append(peers, &comm.RemotePeer{Endpoint: member.PreferredEndpoint(), PKIID: member.PKIid}) 599 } 600 } 601 602 return peers 603 } 604 605 // hasRequiredHeight returns predicate which is capable to filter peers with ledger height above than indicated 606 // by provided input parameter 607 func (s *GossipStateProviderImpl) hasRequiredHeight(height uint64) func(peer discovery.NetworkMember) bool { 608 return func(peer discovery.NetworkMember) bool { 609 if nodeMetadata, err := FromBytes(peer.Metadata); err != nil { 610 logger.Errorf("Unable to de-serialize node meta state, error = %s", err) 611 } else if nodeMetadata.LedgerHeight >= height { 612 return true 613 } 614 615 return false 616 } 617 } 618 619 // GetBlock return ledger block given its sequence number as a parameter 620 func (s *GossipStateProviderImpl) GetBlock(index uint64) *common.Block { 621 // Try to read missing block from the ledger, should return no nil with 622 // content including at least one block 623 if blocks := s.committer.GetBlocks([]uint64{index}); blocks != nil && len(blocks) > 0 { 624 return blocks[0] 625 } 626 627 return nil 628 } 629 630 // AddPayload add new payload into state. 631 func (s *GossipStateProviderImpl) AddPayload(payload *proto.Payload) error { 632 blockingMode := blocking 633 if viper.GetBool("peer.gossip.nonBlockingCommitMode") { 634 blockingMode = false 635 } 636 return s.addPayload(payload, blockingMode) 637 } 638 639 // addPayload add new payload into state. It may (or may not) block according to the 640 // given parameter. If it gets a block while in blocking mode - it would wait until 641 // the block is sent into the payloads buffer. 642 // Else - it may drop the block, if the payload buffer is too full. 643 func (s *GossipStateProviderImpl) addPayload(payload *proto.Payload, blockingMode bool) error { 644 if payload == nil { 645 return errors.New("Given payload is nil") 646 } 647 logger.Debug("Adding new payload into the buffer, seqNum = ", payload.SeqNum) 648 height, err := s.committer.LedgerHeight() 649 if err != nil { 650 return fmt.Errorf("Failed obtaining ledger height: %v", err) 651 } 652 653 if !blockingMode && payload.SeqNum-height >= defMaxBlockDistance { 654 return fmt.Errorf("Ledger height is at %d, cannot enqueue block with sequence of %d", height, payload.SeqNum) 655 } 656 657 for blockingMode && s.payloads.Size() > defMaxBlockDistance*2 { 658 time.Sleep(enqueueRetryInterval) 659 } 660 661 return s.payloads.Push(payload) 662 } 663 664 func (s *GossipStateProviderImpl) commitBlock(block *common.Block) error { 665 if err := s.committer.Commit(block); err != nil { 666 logger.Errorf("Got error while committing(%s)", err) 667 return err 668 } 669 670 // Update ledger level within node metadata 671 nodeMetastate := NewNodeMetastate(block.Header.Number) 672 // Decode nodeMetastate to byte array 673 b, err := nodeMetastate.Bytes() 674 if err == nil { 675 s.gossip.UpdateChannelMetadata(b, common2.ChainID(s.chainID)) 676 } else { 677 678 logger.Errorf("Unable to serialize node meta nodeMetastate, error = %s", err) 679 } 680 681 logger.Debugf("Channel [%s]: Created block [%d] with %d transaction(s)", 682 s.chainID, block.Header.Number, len(block.Data.Data)) 683 684 return nil 685 } 686 687 func min(a uint64, b uint64) uint64 { 688 return b ^ ((a ^ b) & (-(uint64(a-b) >> 63))) 689 }