github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/follower/follower_chain_test.go (about)

     1  /*
     2  Copyright hechain. 2017 All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package follower_test
     8  
     9  import (
    10  	"sync"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/golang/protobuf/proto"
    16  	"github.com/hechain20/hechain/bccsp"
    17  	"github.com/hechain20/hechain/bccsp/sw"
    18  	"github.com/hechain20/hechain/common/flogging"
    19  	"github.com/hechain20/hechain/orderer/common/follower"
    20  	"github.com/hechain20/hechain/orderer/common/follower/mocks"
    21  	"github.com/hechain20/hechain/orderer/common/types"
    22  	"github.com/hechain20/hechain/orderer/consensus"
    23  	"github.com/hechain20/hechain/protoutil"
    24  	"github.com/hyperledger/fabric-protos-go/common"
    25  	"github.com/pkg/errors"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  //go:generate counterfeiter -o mocks/cluster_consenter.go -fake-name ClusterConsenter . clusterConsenter
    30  type clusterConsenter interface {
    31  	consensus.ClusterConsenter
    32  }
    33  
    34  var _ clusterConsenter // suppress "unused type" warning
    35  
    36  //go:generate counterfeiter -o mocks/time_after.go -fake-name TimeAfter . timeAfter
    37  type timeAfter interface {
    38  	After(d time.Duration) <-chan time.Time
    39  }
    40  
    41  var _ timeAfter // suppress "unused type" warning
    42  
    43  var testLogger = flogging.MustGetLogger("follower.test")
    44  
    45  // Global test variables
    46  var (
    47  	cryptoProvider                          bccsp.BCCSP
    48  	localBlockchain                         *memoryBlockChain
    49  	remoteBlockchain                        *memoryBlockChain
    50  	ledgerResources                         *mocks.LedgerResources
    51  	mockClusterConsenter                    *mocks.ClusterConsenter
    52  	mockChannelParticipationMetricsReporter *mocks.ChannelParticipationMetricsReporter
    53  	pullerFactory                           *mocks.BlockPullerFactory
    54  	puller                                  *mocks.ChannelPuller
    55  	mockChainCreator                        *mocks.ChainCreator
    56  	options                                 follower.Options
    57  	timeAfterCount                          *mocks.TimeAfter
    58  	maxDelay                                int64
    59  )
    60  
    61  // Before each test in all test cases
    62  func globalSetup(t *testing.T) {
    63  	var err error
    64  	cryptoProvider, err = sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
    65  	require.NoError(t, err)
    66  
    67  	localBlockchain = &memoryBlockChain{}
    68  	remoteBlockchain = &memoryBlockChain{}
    69  
    70  	ledgerResources = &mocks.LedgerResources{}
    71  	ledgerResources.ChannelIDReturns("my-channel")
    72  	ledgerResources.HeightCalls(localBlockchain.Height)
    73  	ledgerResources.BlockCalls(localBlockchain.Block)
    74  
    75  	mockClusterConsenter = &mocks.ClusterConsenter{}
    76  
    77  	pullerFactory = &mocks.BlockPullerFactory{}
    78  	puller = &mocks.ChannelPuller{}
    79  	pullerFactory.BlockPullerReturns(puller, nil)
    80  
    81  	mockChainCreator = &mocks.ChainCreator{}
    82  
    83  	mockChannelParticipationMetricsReporter = &mocks.ChannelParticipationMetricsReporter{}
    84  
    85  	options = follower.Options{
    86  		Logger:                testLogger,
    87  		PullRetryMinInterval:  1 * time.Microsecond,
    88  		PullRetryMaxInterval:  5 * time.Microsecond,
    89  		HeightPollMinInterval: 1 * time.Microsecond,
    90  		HeightPollMaxInterval: 10 * time.Microsecond,
    91  		Cert:                  []byte{1, 2, 3, 4},
    92  	}
    93  
    94  	atomic.StoreInt64(&maxDelay, 0)
    95  	timeAfterCount = &mocks.TimeAfter{}
    96  	timeAfterCount.AfterCalls(
    97  		func(d time.Duration) <-chan time.Time {
    98  			if d.Nanoseconds() > atomic.LoadInt64(&maxDelay) {
    99  				atomic.StoreInt64(&maxDelay, d.Nanoseconds())
   100  			}
   101  			c := make(chan time.Time, 1)
   102  			c <- time.Now()
   103  			return c
   104  		},
   105  	)
   106  }
   107  
   108  func TestFollowerNewChain(t *testing.T) {
   109  	joinBlockAppRaft := makeConfigBlock(10, []byte{}, 0)
   110  	require.NotNil(t, joinBlockAppRaft)
   111  
   112  	t.Run("with join block, not in channel, empty ledger", func(t *testing.T) {
   113  		globalSetup(t)
   114  		ledgerResources.HeightReturns(0)
   115  		mockClusterConsenter.IsChannelMemberReturns(false, nil)
   116  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   117  		require.NoError(t, err)
   118  
   119  		consensusRelation, status := chain.StatusReport()
   120  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   121  		require.Equal(t, types.StatusOnBoarding, status)
   122  	})
   123  
   124  	t.Run("with join block, in channel, empty ledger", func(t *testing.T) {
   125  		globalSetup(t)
   126  		ledgerResources.HeightReturns(0)
   127  		mockClusterConsenter.IsChannelMemberReturns(true, nil)
   128  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   129  		require.NoError(t, err)
   130  
   131  		consensusRelation, status := chain.StatusReport()
   132  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   133  		require.Equal(t, types.StatusOnBoarding, status)
   134  
   135  		require.NotPanics(t, chain.Start)
   136  		require.NotPanics(t, chain.Start)
   137  		require.NotPanics(t, chain.Halt)
   138  		require.NotPanics(t, chain.Halt)
   139  		require.NotPanics(t, chain.Start)
   140  	})
   141  
   142  	t.Run("bad join block", func(t *testing.T) {
   143  		globalSetup(t)
   144  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, &common.Block{}, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   145  		require.EqualError(t, err, "block header is nil")
   146  		require.Nil(t, chain)
   147  		chain, err = follower.NewChain(ledgerResources, mockClusterConsenter, &common.Block{Header: &common.BlockHeader{}}, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   148  		require.EqualError(t, err, "block data is nil")
   149  		require.Nil(t, chain)
   150  	})
   151  
   152  	t.Run("without join block", func(t *testing.T) {
   153  		globalSetup(t)
   154  		localBlockchain.fill(5)
   155  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   156  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   157  		require.NoError(t, err)
   158  
   159  		consensusRelation, status := chain.StatusReport()
   160  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   161  		require.True(t, status == types.StatusActive)
   162  	})
   163  
   164  	t.Run("can not find config block in chain", func(t *testing.T) {
   165  		globalSetup(t)
   166  		localBlockchain.fill(5)
   167  
   168  		// Set last config index to non-existing value 222
   169  		lastBlock := localBlockchain.Block(localBlockchain.Height() - 1)
   170  		err := setLastConfigIndexInBlock(lastBlock, 222)
   171  		require.NoError(t, err)
   172  
   173  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, nil, mockChannelParticipationMetricsReporter)
   174  		require.EqualError(t, err, "could not retrieve config block from index 222")
   175  		require.Nil(t, chain)
   176  	})
   177  }
   178  
   179  func TestFollowerPullUpToJoin(t *testing.T) {
   180  	joinNum := uint64(10)
   181  	joinBlockAppRaft := makeConfigBlock(joinNum, []byte{}, 1)
   182  	require.NotNil(t, joinBlockAppRaft)
   183  
   184  	var wgChain sync.WaitGroup
   185  
   186  	setup := func() {
   187  		globalSetup(t)
   188  		remoteBlockchain.fill(joinNum)
   189  		remoteBlockchain.appendConfig(1)
   190  
   191  		ledgerResources.AppendCalls(localBlockchain.Append)
   192  
   193  		puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) })
   194  		pullerFactory.BlockPullerReturns(puller, nil)
   195  
   196  		wgChain = sync.WaitGroup{}
   197  		wgChain.Add(1)
   198  		mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() })
   199  
   200  		wgChain = sync.WaitGroup{}
   201  		wgChain.Add(1)
   202  	}
   203  
   204  	t.Run("zero until join block, member", func(t *testing.T) {
   205  		setup()
   206  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   207  
   208  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   209  		require.NoError(t, err)
   210  
   211  		consensusRelation, status := chain.StatusReport()
   212  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   213  		require.Equal(t, types.StatusOnBoarding, status)
   214  
   215  		require.NotPanics(t, chain.Start)
   216  		wgChain.Wait()
   217  		require.NotPanics(t, chain.Halt)
   218  		require.False(t, chain.IsRunning())
   219  
   220  		consensusRelation, status = chain.StatusReport()
   221  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   222  		require.Equal(t, types.StatusActive, status)
   223  
   224  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount())
   225  		require.Equal(t, 11, ledgerResources.AppendCallCount())
   226  		for i := uint64(0); i <= joinNum; i++ {
   227  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   228  		}
   229  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   230  	})
   231  	t.Run("existing half chain until join block, member", func(t *testing.T) {
   232  		setup()
   233  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   234  		localBlockchain.fill(joinNum / 2) // A gap between the ledger and the join block
   235  		require.True(t, joinBlockAppRaft.Header.Number > ledgerResources.Height())
   236  		require.True(t, ledgerResources.Height() > 0)
   237  
   238  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   239  		require.NoError(t, err)
   240  
   241  		consensusRelation, status := chain.StatusReport()
   242  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   243  		require.Equal(t, types.StatusOnBoarding, status)
   244  
   245  		require.Equal(t, 1, mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsCallCount())
   246  		channel, relation, status := mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsArgsForCall(0)
   247  		require.Equal(t, "my-channel", channel)
   248  		require.Equal(t, types.ConsensusRelationConsenter, relation)
   249  		require.Equal(t, types.StatusOnBoarding, status)
   250  
   251  		require.NotPanics(t, chain.Start)
   252  		wgChain.Wait()
   253  		require.NotPanics(t, chain.Halt)
   254  		require.False(t, chain.IsRunning())
   255  
   256  		consensusRelation, status = chain.StatusReport()
   257  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   258  		require.Equal(t, types.StatusActive, status)
   259  
   260  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount())
   261  		require.Equal(t, 6, ledgerResources.AppendCallCount())
   262  		for i := uint64(0); i <= joinNum; i++ {
   263  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   264  		}
   265  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   266  
   267  		require.Equal(t, 3, mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsCallCount())
   268  		channel, relation, status = mockChannelParticipationMetricsReporter.ReportConsensusRelationAndStatusMetricsArgsForCall(2)
   269  		require.Equal(t, "my-channel", channel)
   270  		require.Equal(t, types.ConsensusRelationConsenter, relation)
   271  		require.Equal(t, types.StatusActive, status)
   272  	})
   273  	t.Run("no need to pull, member", func(t *testing.T) {
   274  		setup()
   275  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   276  		localBlockchain.fill(joinNum)
   277  		localBlockchain.appendConfig(1) // No gap between the ledger and the join block
   278  		require.True(t, joinBlockAppRaft.Header.Number < ledgerResources.Height())
   279  
   280  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   281  		require.NoError(t, err)
   282  
   283  		consensusRelation, status := chain.StatusReport()
   284  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   285  		require.Equal(t, types.StatusActive, status)
   286  
   287  		require.NotPanics(t, chain.Start)
   288  		wgChain.Wait()
   289  		require.NotPanics(t, chain.Halt)
   290  		require.False(t, chain.IsRunning())
   291  
   292  		consensusRelation, status = chain.StatusReport()
   293  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   294  		require.Equal(t, types.StatusActive, status)
   295  
   296  		require.Equal(t, 2, pullerFactory.BlockPullerCallCount())
   297  		require.Equal(t, 0, ledgerResources.AppendCallCount())
   298  
   299  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   300  	})
   301  	t.Run("overcome pull failures, member", func(t *testing.T) {
   302  		setup()
   303  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   304  
   305  		failPull := 10
   306  		pullerFactory = &mocks.BlockPullerFactory{}
   307  		puller = &mocks.ChannelPuller{}
   308  		puller.PullBlockCalls(func(i uint64) *common.Block {
   309  			if i%2 == 1 && failPull > 0 {
   310  				failPull = failPull - 1
   311  				return nil
   312  			}
   313  			failPull = 10
   314  			return remoteBlockchain.Block(i)
   315  		})
   316  		pullerFactory.BlockPullerReturns(puller, nil)
   317  		options.TimeAfter = timeAfterCount.After
   318  
   319  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   320  		require.NoError(t, err)
   321  
   322  		consensusRelation, status := chain.StatusReport()
   323  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   324  		require.Equal(t, types.StatusOnBoarding, status)
   325  
   326  		require.NotPanics(t, chain.Start)
   327  		wgChain.Wait()
   328  		require.NotPanics(t, chain.Halt)
   329  		require.False(t, chain.IsRunning())
   330  
   331  		consensusRelation, status = chain.StatusReport()
   332  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   333  		require.Equal(t, types.StatusActive, status)
   334  
   335  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount())
   336  		require.Equal(t, 11, ledgerResources.AppendCallCount())
   337  		for i := uint64(0); i <= joinNum; i++ {
   338  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   339  		}
   340  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   341  		require.Equal(t, 50, timeAfterCount.AfterCallCount())
   342  		require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay))
   343  	})
   344  }
   345  
   346  func TestFollowerPullAfterJoin(t *testing.T) {
   347  	joinNum := uint64(10)
   348  	var wgChain sync.WaitGroup
   349  
   350  	setup := func() {
   351  		globalSetup(t)
   352  		remoteBlockchain.fill(joinNum + 11)
   353  		localBlockchain.fill(joinNum + 1)
   354  
   355  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   356  
   357  		puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) })
   358  		puller.HeightsByEndpointsCalls(
   359  			func() (map[string]uint64, error) {
   360  				m := make(map[string]uint64)
   361  				m["good-node"] = remoteBlockchain.Height()
   362  				m["lazy-node"] = remoteBlockchain.Height() - 2
   363  				return m, nil
   364  			},
   365  		)
   366  		pullerFactory.BlockPullerReturns(puller, nil)
   367  
   368  		wgChain = sync.WaitGroup{}
   369  		wgChain.Add(1)
   370  	}
   371  
   372  	t.Run("No config in the middle", func(t *testing.T) {
   373  		setup()
   374  		ledgerResources.AppendCalls(func(block *common.Block) error {
   375  			_ = localBlockchain.Append(block)
   376  			// Stop when we catch-up with latest
   377  			if remoteBlockchain.Height() == localBlockchain.Height() {
   378  				wgChain.Done()
   379  			}
   380  			return nil
   381  		})
   382  		require.Equal(t, joinNum+1, localBlockchain.Height())
   383  
   384  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   385  		require.NoError(t, err)
   386  
   387  		consensusRelation, status := chain.StatusReport()
   388  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   389  		require.Equal(t, types.StatusActive, status)
   390  
   391  		require.NotPanics(t, chain.Start)
   392  		wgChain.Wait()
   393  		require.NotPanics(t, chain.Halt)
   394  		require.False(t, chain.IsRunning())
   395  
   396  		consensusRelation, status = chain.StatusReport()
   397  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   398  		require.Equal(t, types.StatusActive, status)
   399  
   400  		require.Equal(t, 1, pullerFactory.BlockPullerCallCount())
   401  		require.Equal(t, 10, ledgerResources.AppendCallCount())
   402  		require.Equal(t, uint64(21), localBlockchain.Height())
   403  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   404  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   405  		}
   406  		require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount())
   407  	})
   408  	t.Run("No config in the middle, latest height increasing", func(t *testing.T) {
   409  		setup()
   410  		ledgerResources.AppendCalls(func(block *common.Block) error {
   411  			_ = localBlockchain.Append(block)
   412  			if remoteBlockchain.Height() == localBlockchain.Height() {
   413  				if remoteBlockchain.Height() < 50 {
   414  					remoteBlockchain.fill(10)
   415  				} else {
   416  					// Stop when we catch-up with latest
   417  					wgChain.Done()
   418  				}
   419  			}
   420  			return nil
   421  		})
   422  		require.Equal(t, joinNum+1, localBlockchain.Height())
   423  
   424  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   425  
   426  		require.NoError(t, err)
   427  
   428  		consensusRelation, status := chain.StatusReport()
   429  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   430  		require.Equal(t, types.StatusActive, status)
   431  
   432  		require.NotPanics(t, chain.Start)
   433  		wgChain.Wait()
   434  		require.NotPanics(t, chain.Halt)
   435  		require.False(t, chain.IsRunning())
   436  
   437  		consensusRelation, status = chain.StatusReport()
   438  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   439  		require.Equal(t, types.StatusActive, status)
   440  
   441  		require.Equal(t, 1, pullerFactory.BlockPullerCallCount())
   442  		require.Equal(t, 40, ledgerResources.AppendCallCount())
   443  		require.Equal(t, uint64(51), localBlockchain.Height())
   444  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   445  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   446  		}
   447  		require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount())
   448  	})
   449  	t.Run("No config in the middle, latest height increasing slowly", func(t *testing.T) {
   450  		setup()
   451  
   452  		var hCount int
   453  		puller.HeightsByEndpointsCalls(
   454  			func() (map[string]uint64, error) {
   455  				m := make(map[string]uint64)
   456  				if hCount%10 == 0 {
   457  					m["good-node"] = remoteBlockchain.Height()
   458  					m["lazy-node"] = remoteBlockchain.Height() - 2
   459  				} else {
   460  					m["equal-to-local-node"] = localBlockchain.Height()
   461  				}
   462  				hCount++
   463  				return m, nil
   464  			},
   465  		)
   466  		ledgerResources.AppendCalls(func(block *common.Block) error {
   467  			_ = localBlockchain.Append(block)
   468  			if remoteBlockchain.Height() == localBlockchain.Height() {
   469  				if remoteBlockchain.Height() < 50 {
   470  					remoteBlockchain.fill(10)
   471  				} else {
   472  					// Stop when we catch-up with latest
   473  					wgChain.Done()
   474  				}
   475  			}
   476  			return nil
   477  		})
   478  		require.Equal(t, joinNum+1, localBlockchain.Height())
   479  		options.TimeAfter = timeAfterCount.After
   480  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   481  
   482  		require.NoError(t, err)
   483  
   484  		require.Equal(t, 0, timeAfterCount.AfterCallCount())
   485  
   486  		consensusRelation, status := chain.StatusReport()
   487  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   488  		require.Equal(t, types.StatusActive, status)
   489  
   490  		require.NotPanics(t, chain.Start)
   491  		wgChain.Wait()
   492  		require.NotPanics(t, chain.Halt)
   493  		require.False(t, chain.IsRunning())
   494  
   495  		consensusRelation, status = chain.StatusReport()
   496  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   497  		require.Equal(t, types.StatusActive, status)
   498  
   499  		require.Equal(t, 1, pullerFactory.BlockPullerCallCount())
   500  		require.Equal(t, 40, ledgerResources.AppendCallCount())
   501  		require.Equal(t, uint64(51), localBlockchain.Height())
   502  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   503  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   504  		}
   505  		require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount())
   506  		require.True(t, puller.HeightsByEndpointsCallCount() >= 30)
   507  		require.Equal(t, int64(10000), atomic.LoadInt64(&maxDelay))
   508  	})
   509  	t.Run("Configs in the middle, latest height increasing", func(t *testing.T) {
   510  		setup()
   511  		ledgerResources.AppendCalls(func(block *common.Block) error {
   512  			_ = localBlockchain.Append(block)
   513  
   514  			if remoteBlockchain.Height() == localBlockchain.Height() {
   515  				if remoteBlockchain.Height() < 50 {
   516  					remoteBlockchain.fill(9)
   517  					// Each config appended will trigger the creation of a new puller in the next round
   518  					remoteBlockchain.appendConfig(0)
   519  				} else {
   520  					remoteBlockchain.fill(9)
   521  					// This will trigger the creation of a new chain
   522  					remoteBlockchain.appendConfig(1)
   523  				}
   524  			}
   525  			return nil
   526  		})
   527  		mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created
   528  		require.Equal(t, joinNum+1, localBlockchain.Height())
   529  
   530  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   531  		require.NoError(t, err)
   532  
   533  		consensusRelation, status := chain.StatusReport()
   534  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   535  		require.Equal(t, types.StatusActive, status)
   536  
   537  		require.NotPanics(t, chain.Start)
   538  		wgChain.Wait()
   539  		require.NotPanics(t, chain.Halt)
   540  		require.False(t, chain.IsRunning())
   541  
   542  		consensusRelation, status = chain.StatusReport()
   543  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   544  		require.Equal(t, types.StatusActive, status)
   545  
   546  		require.Equal(t, 1, pullerFactory.BlockPullerCallCount(), "after finding a config, block puller is created")
   547  		require.Equal(t, 50, ledgerResources.AppendCallCount())
   548  		require.Equal(t, uint64(61), localBlockchain.Height())
   549  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   550  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   551  		}
   552  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   553  	})
   554  	t.Run("Overcome puller errors, configs in the middle, latest height increasing", func(t *testing.T) {
   555  		setup()
   556  		ledgerResources.AppendCalls(func(block *common.Block) error {
   557  			_ = localBlockchain.Append(block)
   558  
   559  			if remoteBlockchain.Height() == localBlockchain.Height() {
   560  				if remoteBlockchain.Height() < 50 {
   561  					remoteBlockchain.fill(9)
   562  					// Each config appended will trigger the creation of a new puller in the next round
   563  					remoteBlockchain.appendConfig(0)
   564  				} else {
   565  					remoteBlockchain.fill(9)
   566  					// This will trigger the creation of a new chain
   567  					remoteBlockchain.appendConfig(1)
   568  				}
   569  			}
   570  			return nil
   571  		})
   572  		mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created
   573  		require.Equal(t, joinNum+1, localBlockchain.Height())
   574  
   575  		failPull := 10
   576  		puller.PullBlockCalls(func(i uint64) *common.Block {
   577  			if i%2 == 1 && failPull > 0 {
   578  				failPull = failPull - 1
   579  				return nil
   580  			}
   581  			failPull = 10
   582  			return remoteBlockchain.Block(i)
   583  		})
   584  
   585  		failHeight := 1
   586  		puller.HeightsByEndpointsCalls(
   587  			func() (map[string]uint64, error) {
   588  				if failHeight > 0 {
   589  					failHeight = failHeight - 1
   590  					return nil, errors.New("failed to get heights")
   591  				}
   592  				failHeight = 1
   593  				m := make(map[string]uint64)
   594  				m["good-node"] = remoteBlockchain.Height()
   595  				m["lazy-node"] = remoteBlockchain.Height() - 2
   596  				return m, nil
   597  			},
   598  		)
   599  		pullerFactory.BlockPullerReturns(puller, nil)
   600  
   601  		options.TimeAfter = timeAfterCount.After
   602  
   603  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, nil, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   604  
   605  		require.NoError(t, err)
   606  
   607  		consensusRelation, status := chain.StatusReport()
   608  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   609  		require.Equal(t, types.StatusActive, status)
   610  
   611  		require.NotPanics(t, chain.Start)
   612  		wgChain.Wait()
   613  		require.NotPanics(t, chain.Halt)
   614  		require.False(t, chain.IsRunning())
   615  
   616  		consensusRelation, status = chain.StatusReport()
   617  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   618  		require.Equal(t, types.StatusActive, status)
   619  
   620  		require.Equal(t, 1, pullerFactory.BlockPullerCallCount(), "after finding a config, or error, block puller is created")
   621  		require.Equal(t, 50, ledgerResources.AppendCallCount())
   622  		require.Equal(t, uint64(61), localBlockchain.Height())
   623  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   624  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   625  		}
   626  
   627  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   628  		require.Equal(t, 259, timeAfterCount.AfterCallCount())
   629  		require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay))
   630  	})
   631  }
   632  
   633  func TestFollowerPullPastJoin(t *testing.T) {
   634  	joinNum := uint64(10)
   635  	var joinBlockAppRaft *common.Block
   636  	var wgChain sync.WaitGroup
   637  
   638  	setup := func() {
   639  		globalSetup(t)
   640  		remoteBlockchain.fill(joinNum)
   641  		remoteBlockchain.appendConfig(0)
   642  		joinBlockAppRaft = remoteBlockchain.Block(joinNum)
   643  		require.NotNil(t, joinBlockAppRaft)
   644  		remoteBlockchain.fill(10)
   645  
   646  		mockClusterConsenter.IsChannelMemberCalls(amIReallyInChannel)
   647  
   648  		puller.PullBlockCalls(func(i uint64) *common.Block { return remoteBlockchain.Block(i) })
   649  		puller.HeightsByEndpointsCalls(
   650  			func() (map[string]uint64, error) {
   651  				m := make(map[string]uint64)
   652  				m["good-node"] = remoteBlockchain.Height()
   653  				m["lazy-node"] = remoteBlockchain.Height() - 2
   654  				return m, nil
   655  			},
   656  		)
   657  		pullerFactory.BlockPullerReturns(puller, nil)
   658  
   659  		wgChain = sync.WaitGroup{}
   660  		wgChain.Add(1)
   661  	}
   662  
   663  	t.Run("No config in the middle", func(t *testing.T) {
   664  		setup()
   665  		ledgerResources.AppendCalls(func(block *common.Block) error {
   666  			_ = localBlockchain.Append(block)
   667  			// Stop when we catch-up with latest
   668  			if remoteBlockchain.Height() == localBlockchain.Height() {
   669  				wgChain.Done()
   670  			}
   671  			return nil
   672  		})
   673  		require.Equal(t, uint64(0), localBlockchain.Height())
   674  
   675  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   676  		require.NoError(t, err)
   677  
   678  		consensusRelation, status := chain.StatusReport()
   679  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   680  		require.Equal(t, types.StatusOnBoarding, status)
   681  
   682  		require.NotPanics(t, chain.Start)
   683  		wgChain.Wait()
   684  		require.NotPanics(t, chain.Halt)
   685  		require.False(t, chain.IsRunning())
   686  
   687  		consensusRelation, status = chain.StatusReport()
   688  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   689  		require.Equal(t, types.StatusActive, status)
   690  
   691  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount())
   692  		require.Equal(t, 21, ledgerResources.AppendCallCount())
   693  		require.Equal(t, uint64(21), localBlockchain.Height())
   694  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   695  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   696  		}
   697  		require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount())
   698  	})
   699  	t.Run("No config in the middle, latest height increasing", func(t *testing.T) {
   700  		setup()
   701  		ledgerResources.AppendCalls(func(block *common.Block) error {
   702  			_ = localBlockchain.Append(block)
   703  			if remoteBlockchain.Height() == localBlockchain.Height() {
   704  				if remoteBlockchain.Height() < 50 {
   705  					remoteBlockchain.fill(10)
   706  				} else {
   707  					// Stop when we catch-up with latest
   708  					wgChain.Done()
   709  				}
   710  			}
   711  			return nil
   712  		})
   713  		require.Equal(t, uint64(0), localBlockchain.Height())
   714  
   715  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   716  
   717  		require.NoError(t, err)
   718  
   719  		consensusRelation, status := chain.StatusReport()
   720  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   721  		require.Equal(t, types.StatusOnBoarding, status)
   722  
   723  		require.NotPanics(t, chain.Start)
   724  		wgChain.Wait()
   725  		require.NotPanics(t, chain.Halt)
   726  		require.False(t, chain.IsRunning())
   727  
   728  		consensusRelation, status = chain.StatusReport()
   729  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   730  		require.Equal(t, types.StatusActive, status)
   731  
   732  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount())
   733  		require.Equal(t, 51, ledgerResources.AppendCallCount())
   734  		require.Equal(t, uint64(51), localBlockchain.Height())
   735  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   736  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   737  		}
   738  		require.Equal(t, 0, mockChainCreator.SwitchFollowerToChainCallCount())
   739  	})
   740  	t.Run("Configs in the middle, latest height increasing", func(t *testing.T) {
   741  		setup()
   742  		localBlockchain.fill(5)
   743  		localBlockchain.appendConfig(0)
   744  		ledgerResources.AppendCalls(func(block *common.Block) error {
   745  			_ = localBlockchain.Append(block)
   746  
   747  			if remoteBlockchain.Height() == localBlockchain.Height() {
   748  				if remoteBlockchain.Height() < 50 {
   749  					remoteBlockchain.fill(9)
   750  					// Each config appended will trigger the creation of a new puller in the next round
   751  					remoteBlockchain.appendConfig(0)
   752  				} else {
   753  					remoteBlockchain.fill(9)
   754  					// This will trigger the creation of a new chain
   755  					remoteBlockchain.appendConfig(1)
   756  				}
   757  			}
   758  			return nil
   759  		})
   760  		mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created
   761  		require.Equal(t, uint64(6), localBlockchain.Height())
   762  
   763  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   764  		require.NoError(t, err)
   765  
   766  		consensusRelation, status := chain.StatusReport()
   767  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   768  		require.Equal(t, types.StatusOnBoarding, status)
   769  
   770  		require.NotPanics(t, chain.Start)
   771  		wgChain.Wait()
   772  		require.NotPanics(t, chain.Halt)
   773  		require.False(t, chain.IsRunning())
   774  
   775  		consensusRelation, status = chain.StatusReport()
   776  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   777  		require.Equal(t, types.StatusActive, status)
   778  
   779  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount(), "after finding a config, block puller is created")
   780  		require.Equal(t, 55, ledgerResources.AppendCallCount())
   781  		require.Equal(t, uint64(61), localBlockchain.Height())
   782  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   783  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   784  		}
   785  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   786  	})
   787  	t.Run("Overcome puller errors, configs in the middle, latest height increasing", func(t *testing.T) {
   788  		setup()
   789  		ledgerResources.AppendCalls(func(block *common.Block) error {
   790  			_ = localBlockchain.Append(block)
   791  
   792  			if remoteBlockchain.Height() == localBlockchain.Height() {
   793  				if remoteBlockchain.Height() < 50 {
   794  					remoteBlockchain.fill(9)
   795  					// Each config appended will trigger the creation of a new puller in the next round
   796  					remoteBlockchain.appendConfig(0)
   797  				} else {
   798  					remoteBlockchain.fill(9)
   799  					// This will trigger the creation of a new chain
   800  					remoteBlockchain.appendConfig(1)
   801  				}
   802  			}
   803  			return nil
   804  		})
   805  		mockChainCreator.SwitchFollowerToChainCalls(func(_ string) { wgChain.Done() }) // Stop when a new chain is created
   806  		require.Equal(t, uint64(0), localBlockchain.Height())
   807  
   808  		failPull := 10
   809  		puller.PullBlockCalls(func(i uint64) *common.Block {
   810  			if i%2 == 1 && failPull > 0 {
   811  				failPull = failPull - 1
   812  				return nil
   813  			}
   814  			failPull = 10
   815  			return remoteBlockchain.Block(i)
   816  		})
   817  
   818  		failHeight := 1
   819  		puller.HeightsByEndpointsCalls(
   820  			func() (map[string]uint64, error) {
   821  				if failHeight > 0 {
   822  					failHeight = failHeight - 1
   823  					return nil, errors.New("failed to get heights")
   824  				}
   825  				failHeight = 1
   826  				m := make(map[string]uint64)
   827  				m["good-node"] = remoteBlockchain.Height()
   828  				m["lazy-node"] = remoteBlockchain.Height() - 2
   829  				return m, nil
   830  			},
   831  		)
   832  		pullerFactory.BlockPullerReturns(puller, nil)
   833  
   834  		options.TimeAfter = timeAfterCount.After
   835  
   836  		chain, err := follower.NewChain(ledgerResources, mockClusterConsenter, joinBlockAppRaft, options, pullerFactory, mockChainCreator, cryptoProvider, mockChannelParticipationMetricsReporter)
   837  
   838  		require.NoError(t, err)
   839  
   840  		consensusRelation, status := chain.StatusReport()
   841  		require.Equal(t, types.ConsensusRelationFollower, consensusRelation)
   842  		require.Equal(t, types.StatusOnBoarding, status)
   843  
   844  		require.NotPanics(t, chain.Start)
   845  		wgChain.Wait()
   846  		require.NotPanics(t, chain.Halt)
   847  		require.False(t, chain.IsRunning())
   848  
   849  		consensusRelation, status = chain.StatusReport()
   850  		require.Equal(t, types.ConsensusRelationConsenter, consensusRelation)
   851  		require.Equal(t, types.StatusActive, status)
   852  
   853  		require.Equal(t, 3, pullerFactory.BlockPullerCallCount(), "after finding a config, or error, block puller is created")
   854  		require.Equal(t, 61, ledgerResources.AppendCallCount())
   855  		require.Equal(t, uint64(61), localBlockchain.Height())
   856  		for i := uint64(0); i < localBlockchain.Height(); i++ {
   857  			require.Equal(t, remoteBlockchain.Block(i).Header, localBlockchain.Block(i).Header, "failed block i=%d", i)
   858  		}
   859  
   860  		require.Equal(t, 1, mockChainCreator.SwitchFollowerToChainCallCount())
   861  		require.Equal(t, 309, timeAfterCount.AfterCallCount())
   862  		require.Equal(t, int64(5000), atomic.LoadInt64(&maxDelay))
   863  	})
   864  }
   865  
   866  type memoryBlockChain struct {
   867  	lock  sync.Mutex
   868  	chain []*common.Block
   869  }
   870  
   871  func (mbc *memoryBlockChain) Append(block *common.Block) error {
   872  	mbc.lock.Lock()
   873  	defer mbc.lock.Unlock()
   874  
   875  	mbc.chain = append(mbc.chain, block)
   876  	return nil
   877  }
   878  
   879  func (mbc *memoryBlockChain) Height() uint64 {
   880  	mbc.lock.Lock()
   881  	defer mbc.lock.Unlock()
   882  
   883  	return uint64(len(mbc.chain))
   884  }
   885  
   886  func (mbc *memoryBlockChain) Block(i uint64) *common.Block {
   887  	mbc.lock.Lock()
   888  	defer mbc.lock.Unlock()
   889  
   890  	if i < uint64(len(mbc.chain)) {
   891  		return mbc.chain[i]
   892  	}
   893  	return nil
   894  }
   895  
   896  func (mbc *memoryBlockChain) fill(numBlocks uint64) {
   897  	mbc.lock.Lock()
   898  	defer mbc.lock.Unlock()
   899  
   900  	height := uint64(len(mbc.chain))
   901  	prevHash := []byte{}
   902  
   903  	for i := height; i < height+numBlocks; i++ {
   904  		if i > 0 {
   905  			prevHash = protoutil.BlockHeaderHash(mbc.chain[i-1].Header)
   906  		}
   907  
   908  		var block *common.Block
   909  		if i == 0 {
   910  			block = makeConfigBlock(i, prevHash, 0)
   911  		} else {
   912  			block = protoutil.NewBlock(i, prevHash)
   913  			protoutil.CopyBlockMetadata(mbc.chain[i-1], block)
   914  		}
   915  
   916  		mbc.chain = append(mbc.chain, block)
   917  	}
   918  }
   919  
   920  func (mbc *memoryBlockChain) appendConfig(isMember uint8) {
   921  	mbc.lock.Lock()
   922  	defer mbc.lock.Unlock()
   923  
   924  	h := uint64(len(mbc.chain))
   925  	configBlock := makeConfigBlock(h, protoutil.BlockHeaderHash(mbc.chain[h-1].Header), isMember)
   926  	mbc.chain = append(mbc.chain, configBlock)
   927  }
   928  
   929  func amIReallyInChannel(configBlock *common.Block) (bool, error) {
   930  	if !protoutil.IsConfigBlock(configBlock) {
   931  		return false, errors.New("not a config")
   932  	}
   933  	env, err := protoutil.ExtractEnvelope(configBlock, 0)
   934  	if err != nil {
   935  		return false, err
   936  	}
   937  	payload := protoutil.UnmarshalPayloadOrPanic(env.Payload)
   938  	if len(payload.Data) == 0 {
   939  		return false, errors.New("empty data")
   940  	}
   941  	if payload.Data[0] > 0 {
   942  		return true, nil
   943  	}
   944  	return false, nil
   945  }
   946  
   947  func makeConfigBlock(num uint64, prevHash []byte, isMember uint8) *common.Block {
   948  	block := protoutil.NewBlock(num, prevHash)
   949  	env := &common.Envelope{
   950  		Payload: protoutil.MarshalOrPanic(&common.Payload{
   951  			Header: protoutil.MakePayloadHeader(
   952  				protoutil.MakeChannelHeader(common.HeaderType_CONFIG, 0, "my-chennel", 0),
   953  				protoutil.MakeSignatureHeader([]byte{}, []byte{}),
   954  			),
   955  			Data: []byte{isMember},
   956  		},
   957  		),
   958  	}
   959  	block.Data.Data = append(block.Data.Data, protoutil.MarshalOrPanic(env))
   960  	protoutil.InitBlockMetadata(block)
   961  	obm := &common.OrdererBlockMetadata{LastConfig: &common.LastConfig{Index: num}}
   962  	block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(
   963  		&common.Metadata{
   964  			Value: protoutil.MarshalOrPanic(obm),
   965  		},
   966  	)
   967  	protoutil.InitBlockMetadata(block)
   968  
   969  	return block
   970  }
   971  
   972  func TestChain_makeConfigBlock(t *testing.T) {
   973  	joinBlockAppRaft := makeConfigBlock(10, []byte{1, 2, 3, 4}, 0)
   974  	require.NotNil(t, joinBlockAppRaft)
   975  	require.True(t, protoutil.IsConfigBlock(joinBlockAppRaft))
   976  	require.NotPanics(t, func() { protoutil.GetLastConfigIndexFromBlockOrPanic(joinBlockAppRaft) })
   977  	require.Equal(t, uint64(10), protoutil.GetLastConfigIndexFromBlockOrPanic(joinBlockAppRaft))
   978  	require.NotPanics(t, func() { amIReallyInChannel(joinBlockAppRaft) })
   979  	isMem, err := amIReallyInChannel(joinBlockAppRaft)
   980  	require.NoError(t, err)
   981  	require.False(t, isMem)
   982  	joinBlockAppRaft = makeConfigBlock(11, []byte{1, 2, 3, 4}, 1)
   983  	isMem, err = amIReallyInChannel(joinBlockAppRaft)
   984  	require.NoError(t, err)
   985  	require.True(t, isMem)
   986  	isMem, err = amIReallyInChannel(protoutil.NewBlock(10, []byte{1, 2, 3, 4}))
   987  	require.EqualError(t, err, "not a config")
   988  	require.False(t, isMem)
   989  }
   990  
   991  func setLastConfigIndexInBlock(block *common.Block, lastConfigIndex uint64) error {
   992  	ordererBlockMetadata := &common.OrdererBlockMetadata{
   993  		LastConfig: &common.LastConfig{
   994  			Index: lastConfigIndex,
   995  		},
   996  	}
   997  	obmBytes, err := proto.Marshal(ordererBlockMetadata)
   998  	if err != nil {
   999  		return err
  1000  	}
  1001  	metadata := &common.Metadata{
  1002  		Value: obmBytes,
  1003  	}
  1004  	metadataBytes, err := proto.Marshal(metadata)
  1005  	if err != nil {
  1006  		return err
  1007  	}
  1008  	block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = metadataBytes
  1009  	return nil
  1010  }