github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/gossip/privdata/reconcile_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package privdata
     8  
     9  import (
    10  	"errors"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	gossip2 "github.com/hyperledger/fabric-protos-go/gossip"
    16  	"github.com/hyperledger/fabric-protos-go/peer"
    17  	"github.com/osdi23p228/fabric/common/metrics/disabled"
    18  	util2 "github.com/osdi23p228/fabric/common/util"
    19  	"github.com/osdi23p228/fabric/core/ledger"
    20  	"github.com/osdi23p228/fabric/gossip/metrics"
    21  	gmetricsmocks "github.com/osdi23p228/fabric/gossip/metrics/mocks"
    22  	privdatacommon "github.com/osdi23p228/fabric/gossip/privdata/common"
    23  	"github.com/osdi23p228/fabric/gossip/privdata/mocks"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/mock"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  func TestNoItemsToReconcile(t *testing.T) {
    30  	// Scenario: there is no missing private data to reconcile.
    31  	// reconciler should identify that we don't have missing data and it doesn't need to call reconciliationFetcher to
    32  	// fetch missing items.
    33  	// reconciler shouldn't get an error.
    34  	committer := &mocks.Committer{}
    35  	fetcher := &mocks.ReconciliationFetcher{}
    36  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
    37  	missingInfo := ledger.MissingPvtDataInfo{}
    38  
    39  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(missingInfo, nil)
    40  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
    41  	fetcher.On("FetchReconciledItems", mock.Anything).Return(nil, errors.New("this function shouldn't be called"))
    42  
    43  	r := &Reconciler{
    44  		channel:                "mychannel",
    45  		logger:                 logger.With("channel", "mychannel"),
    46  		metrics:                metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics,
    47  		ReconcileSleepInterval: time.Minute,
    48  		ReconcileBatchSize:     1,
    49  		ReconciliationFetcher:  fetcher, Committer: committer,
    50  	}
    51  	err := r.reconcile()
    52  
    53  	assert.NoError(t, err)
    54  }
    55  
    56  func TestNotReconcilingWhenCollectionConfigNotAvailable(t *testing.T) {
    57  	// Scenario: reconciler gets an error when trying to read collection config for the missing private data.
    58  	// as a result it removes the digest slice, and there are no digests to pull.
    59  	// shouldn't get an error.
    60  	committer := &mocks.Committer{}
    61  	fetcher := &mocks.ReconciliationFetcher{}
    62  	configHistoryRetriever := &mocks.ConfigHistoryRetriever{}
    63  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
    64  
    65  	missingInfo := ledger.MissingPvtDataInfo{
    66  		1: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
    67  			1: {{Collection: "col1", Namespace: "chain1"}},
    68  		},
    69  	}
    70  
    71  	var collectionConfigInfo ledger.CollectionConfigInfo
    72  
    73  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(missingInfo, nil)
    74  	configHistoryRetriever.On("MostRecentCollectionConfigBelow", mock.Anything, mock.Anything).Return(&collectionConfigInfo, errors.New("fail to get collection config"))
    75  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
    76  	committer.On("GetConfigHistoryRetriever").Return(configHistoryRetriever, nil)
    77  
    78  	var fetchCalled bool
    79  	fetcher.On("FetchReconciledItems", mock.Anything).Run(func(args mock.Arguments) {
    80  		var dig2CollectionConfig = args.Get(0).(privdatacommon.Dig2CollectionConfig)
    81  		assert.Equal(t, 0, len(dig2CollectionConfig))
    82  		fetchCalled = true
    83  	}).Return(nil, errors.New("called with no digests"))
    84  
    85  	r := &Reconciler{
    86  		channel:                "mychannel",
    87  		logger:                 logger.With("channel", "mychannel"),
    88  		metrics:                metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics,
    89  		ReconcileSleepInterval: time.Minute,
    90  		ReconcileBatchSize:     1,
    91  		ReconciliationFetcher:  fetcher, Committer: committer,
    92  	}
    93  	err := r.reconcile()
    94  
    95  	assert.Error(t, err)
    96  	assert.Equal(t, "called with no digests", err.Error())
    97  	assert.True(t, fetchCalled)
    98  }
    99  
   100  func TestReconciliationHappyPathWithoutScheduler(t *testing.T) {
   101  	// Scenario: happy path when trying to reconcile missing private data.
   102  	committer := &mocks.Committer{}
   103  	fetcher := &mocks.ReconciliationFetcher{}
   104  	configHistoryRetriever := &mocks.ConfigHistoryRetriever{}
   105  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
   106  
   107  	missingInfo := ledger.MissingPvtDataInfo{
   108  		3: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
   109  			1: {{Collection: "col1", Namespace: "ns1"}},
   110  		},
   111  		4: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
   112  			4: {{Collection: "col1", Namespace: "ns1"}},
   113  			5: {{Collection: "col1", Namespace: "ns1"}},
   114  		},
   115  	}
   116  
   117  	collectionConfigInfo := ledger.CollectionConfigInfo{
   118  		CollectionConfig: &peer.CollectionConfigPackage{
   119  			Config: []*peer.CollectionConfig{
   120  				{Payload: &peer.CollectionConfig_StaticCollectionConfig{
   121  					StaticCollectionConfig: &peer.StaticCollectionConfig{
   122  						Name: "col1",
   123  					},
   124  				}},
   125  			},
   126  		},
   127  		CommittingBlockNum: 1,
   128  	}
   129  
   130  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(missingInfo, nil).Run(func(_ mock.Arguments) {
   131  		missingPvtDataTracker.Mock = mock.Mock{}
   132  		missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(nil, nil)
   133  	})
   134  	configHistoryRetriever.On("MostRecentCollectionConfigBelow", mock.Anything, mock.Anything).Return(&collectionConfigInfo, nil)
   135  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
   136  	committer.On("GetConfigHistoryRetriever").Return(configHistoryRetriever, nil)
   137  
   138  	result := &privdatacommon.FetchedPvtDataContainer{}
   139  	fetcher.On("FetchReconciledItems", mock.Anything).Run(func(args mock.Arguments) {
   140  		var dig2CollectionConfig = args.Get(0).(privdatacommon.Dig2CollectionConfig)
   141  		require.Equal(t, 3, len(dig2CollectionConfig))
   142  		for digest := range dig2CollectionConfig {
   143  			if digest.BlockSeq != 3 {
   144  				// fetch private data only for block 3. Assume that the other
   145  				// block's private data could not be fetched
   146  				continue
   147  			}
   148  			hash := util2.ComputeSHA256([]byte("rws-pre-image"))
   149  			element := &gossip2.PvtDataElement{
   150  				Digest: &gossip2.PvtDataDigest{
   151  					TxId:       digest.TxId,
   152  					BlockSeq:   digest.BlockSeq,
   153  					Collection: digest.Collection,
   154  					Namespace:  digest.Namespace,
   155  					SeqInBlock: digest.SeqInBlock,
   156  				},
   157  				Payload: [][]byte{hash},
   158  			}
   159  			result.AvailableElements = append(result.AvailableElements, element)
   160  		}
   161  	}).Return(result, nil)
   162  
   163  	expectedUnreconciledMissingData := ledger.MissingPvtDataInfo{
   164  		4: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
   165  			4: {{Collection: "col1", Namespace: "ns1"}},
   166  			5: {{Collection: "col1", Namespace: "ns1"}},
   167  		},
   168  	}
   169  
   170  	var commitPvtDataOfOldBlocksHappened bool
   171  	var blockNum, seqInBlock uint64
   172  	blockNum = 3
   173  	seqInBlock = 1
   174  	committer.On("CommitPvtDataOfOldBlocks", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   175  		require.Len(t, args, 2)
   176  		var reconciledPvtdata = args.Get(0).([]*ledger.ReconciledPvtdata)
   177  		assert.Equal(t, 1, len(reconciledPvtdata))
   178  		assert.Equal(t, blockNum, reconciledPvtdata[0].BlockNum)
   179  		assert.Equal(t, seqInBlock, reconciledPvtdata[0].WriteSets[1].SeqInBlock)
   180  		assert.Equal(t, "ns1", reconciledPvtdata[0].WriteSets[1].WriteSet.NsPvtRwset[0].Namespace)
   181  		assert.Equal(t, "col1", reconciledPvtdata[0].WriteSets[1].WriteSet.NsPvtRwset[0].CollectionPvtRwset[0].CollectionName)
   182  		commitPvtDataOfOldBlocksHappened = true
   183  
   184  		var unreconciledPvtdata = args.Get(1).(ledger.MissingPvtDataInfo)
   185  		require.Equal(t, expectedUnreconciledMissingData, unreconciledPvtdata)
   186  	}).Return([]*ledger.PvtdataHashMismatch{}, nil)
   187  
   188  	testMetricProvider := gmetricsmocks.TestUtilConstructMetricProvider()
   189  	metrics := metrics.NewGossipMetrics(testMetricProvider.FakeProvider).PrivdataMetrics
   190  
   191  	r := &Reconciler{
   192  		channel:                "mychannel",
   193  		logger:                 logger.With("channel", "mychannel"),
   194  		metrics:                metrics,
   195  		ReconcileSleepInterval: time.Minute,
   196  		ReconcileBatchSize:     1,
   197  		ReconciliationFetcher:  fetcher, Committer: committer,
   198  	}
   199  	err := r.reconcile()
   200  
   201  	assert.NoError(t, err)
   202  	assert.True(t, commitPvtDataOfOldBlocksHappened)
   203  
   204  	assert.Equal(t,
   205  		[]string{"channel", "mychannel"},
   206  		testMetricProvider.FakeReconciliationDuration.WithArgsForCall(0),
   207  	)
   208  }
   209  
   210  func TestReconciliationHappyPathWithScheduler(t *testing.T) {
   211  	// Scenario: happy path when trying to reconcile missing private data.
   212  	committer := &mocks.Committer{}
   213  	fetcher := &mocks.ReconciliationFetcher{}
   214  	configHistoryRetriever := &mocks.ConfigHistoryRetriever{}
   215  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
   216  
   217  	missingInfo := ledger.MissingPvtDataInfo{
   218  		3: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
   219  			1: {{Collection: "col1", Namespace: "ns1"}},
   220  		},
   221  	}
   222  
   223  	collectionConfigInfo := ledger.CollectionConfigInfo{
   224  		CollectionConfig: &peer.CollectionConfigPackage{
   225  			Config: []*peer.CollectionConfig{
   226  				{Payload: &peer.CollectionConfig_StaticCollectionConfig{
   227  					StaticCollectionConfig: &peer.StaticCollectionConfig{
   228  						Name: "col1",
   229  					},
   230  				}},
   231  			},
   232  		},
   233  		CommittingBlockNum: 1,
   234  	}
   235  
   236  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(missingInfo, nil).Run(func(_ mock.Arguments) {
   237  		missingPvtDataTracker.Mock = mock.Mock{}
   238  		missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(nil, nil)
   239  	})
   240  	configHistoryRetriever.On("MostRecentCollectionConfigBelow", mock.Anything, mock.Anything).Return(&collectionConfigInfo, nil)
   241  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
   242  	committer.On("GetConfigHistoryRetriever").Return(configHistoryRetriever, nil)
   243  
   244  	result := &privdatacommon.FetchedPvtDataContainer{}
   245  	fetcher.On("FetchReconciledItems", mock.Anything).Run(func(args mock.Arguments) {
   246  		var dig2CollectionConfig = args.Get(0).(privdatacommon.Dig2CollectionConfig)
   247  		assert.Equal(t, 1, len(dig2CollectionConfig))
   248  		for digest := range dig2CollectionConfig {
   249  			hash := util2.ComputeSHA256([]byte("rws-pre-image"))
   250  			element := &gossip2.PvtDataElement{
   251  				Digest: &gossip2.PvtDataDigest{
   252  					TxId:       digest.TxId,
   253  					BlockSeq:   digest.BlockSeq,
   254  					Collection: digest.Collection,
   255  					Namespace:  digest.Namespace,
   256  					SeqInBlock: digest.SeqInBlock,
   257  				},
   258  				Payload: [][]byte{hash},
   259  			}
   260  			result.AvailableElements = append(result.AvailableElements, element)
   261  		}
   262  	}).Return(result, nil)
   263  
   264  	var wg sync.WaitGroup
   265  	wg.Add(1)
   266  
   267  	var commitPvtDataOfOldBlocksHappened bool
   268  	var blockNum, seqInBlock uint64
   269  	blockNum = 3
   270  	seqInBlock = 1
   271  	committer.On("CommitPvtDataOfOldBlocks", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   272  		var reconciledPvtdata = args.Get(0).([]*ledger.ReconciledPvtdata)
   273  		assert.Equal(t, 1, len(reconciledPvtdata))
   274  		assert.Equal(t, blockNum, reconciledPvtdata[0].BlockNum)
   275  		assert.Equal(t, seqInBlock, reconciledPvtdata[0].WriteSets[1].SeqInBlock)
   276  		assert.Equal(t, "ns1", reconciledPvtdata[0].WriteSets[1].WriteSet.NsPvtRwset[0].Namespace)
   277  		assert.Equal(t, "col1", reconciledPvtdata[0].WriteSets[1].WriteSet.NsPvtRwset[0].CollectionPvtRwset[0].CollectionName)
   278  		commitPvtDataOfOldBlocksHappened = true
   279  
   280  		require.Nil(t, args.Get(1))
   281  		wg.Done()
   282  	}).Return([]*ledger.PvtdataHashMismatch{}, nil)
   283  
   284  	r := NewReconciler(
   285  		"",
   286  		metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics,
   287  		committer,
   288  		fetcher,
   289  		&PrivdataConfig{
   290  			ReconcileSleepInterval: time.Millisecond * 100,
   291  			ReconcileBatchSize:     1,
   292  			ReconciliationEnabled:  true,
   293  		})
   294  	r.Start()
   295  	wg.Wait()
   296  	r.Stop()
   297  
   298  	assert.True(t, commitPvtDataOfOldBlocksHappened)
   299  }
   300  
   301  func TestReconciliationPullingMissingPrivateDataAtOnePass(t *testing.T) {
   302  	// Scenario: define batch size to retrieve missing private data to 1
   303  	// and make sure that even though there are missing data for two blocks
   304  	// they will be reconciled with one shot.
   305  	committer := &mocks.Committer{}
   306  	fetcher := &mocks.ReconciliationFetcher{}
   307  	configHistoryRetriever := &mocks.ConfigHistoryRetriever{}
   308  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
   309  
   310  	missingInfo := ledger.MissingPvtDataInfo{
   311  		4: ledger.MissingBlockPvtdataInfo{
   312  			1: {{Collection: "col1", Namespace: "ns1"}},
   313  		},
   314  	}
   315  
   316  	collectionConfigInfo := &ledger.CollectionConfigInfo{
   317  		CollectionConfig: &peer.CollectionConfigPackage{
   318  			Config: []*peer.CollectionConfig{
   319  				{Payload: &peer.CollectionConfig_StaticCollectionConfig{
   320  					StaticCollectionConfig: &peer.StaticCollectionConfig{
   321  						Name: "col1",
   322  					},
   323  				}},
   324  				{Payload: &peer.CollectionConfig_StaticCollectionConfig{
   325  					StaticCollectionConfig: &peer.StaticCollectionConfig{
   326  						Name: "col2",
   327  					},
   328  				}},
   329  			},
   330  		},
   331  		CommittingBlockNum: 1,
   332  	}
   333  
   334  	stopC := make(chan struct{})
   335  	nextC := make(chan struct{})
   336  
   337  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).
   338  		Return(missingInfo, nil).Run(func(_ mock.Arguments) {
   339  		missingInfo := ledger.MissingPvtDataInfo{
   340  			3: ledger.MissingBlockPvtdataInfo{
   341  				2: {{Collection: "col2", Namespace: "ns2"}},
   342  			},
   343  		}
   344  		missingPvtDataTracker.Mock = mock.Mock{}
   345  		missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).
   346  			Return(missingInfo, nil).Run(func(_ mock.Arguments) {
   347  			// here we are making sure that we will first stop
   348  			// reconciliation so next call to GetMissingPvtDataInfoForMostRecentBlocks
   349  			// will go into same round
   350  			<-nextC
   351  			missingPvtDataTracker.Mock = mock.Mock{}
   352  			missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).
   353  				Return(nil, nil)
   354  		})
   355  		// make sure we calling stop reconciliation loop, so for sure
   356  		// in this test we won't get to second round though making sure
   357  		// we are retrieving on single pass
   358  		stopC <- struct{}{}
   359  	})
   360  
   361  	configHistoryRetriever.
   362  		On("MostRecentCollectionConfigBelow", mock.Anything, mock.Anything).
   363  		Return(collectionConfigInfo, nil)
   364  
   365  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
   366  	committer.On("GetConfigHistoryRetriever").Return(configHistoryRetriever, nil)
   367  
   368  	result := &privdatacommon.FetchedPvtDataContainer{}
   369  	fetcher.On("FetchReconciledItems", mock.Anything).Run(func(args mock.Arguments) {
   370  		result.AvailableElements = make([]*gossip2.PvtDataElement, 0)
   371  		var dig2CollectionConfig = args.Get(0).(privdatacommon.Dig2CollectionConfig)
   372  		assert.Equal(t, 1, len(dig2CollectionConfig))
   373  		for digest := range dig2CollectionConfig {
   374  			hash := util2.ComputeSHA256([]byte("rws-pre-image"))
   375  			element := &gossip2.PvtDataElement{
   376  				Digest: &gossip2.PvtDataDigest{
   377  					TxId:       digest.TxId,
   378  					BlockSeq:   digest.BlockSeq,
   379  					Collection: digest.Collection,
   380  					Namespace:  digest.Namespace,
   381  					SeqInBlock: digest.SeqInBlock,
   382  				},
   383  				Payload: [][]byte{hash},
   384  			}
   385  			result.AvailableElements = append(result.AvailableElements, element)
   386  		}
   387  	}).Return(result, nil)
   388  
   389  	var wg sync.WaitGroup
   390  	wg.Add(2)
   391  
   392  	var commitPvtDataOfOldBlocksHappened bool
   393  	pvtDataStore := make([][]*ledger.ReconciledPvtdata, 0)
   394  	committer.On("CommitPvtDataOfOldBlocks", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   395  		var reconciledPvtdata = args.Get(0).([]*ledger.ReconciledPvtdata)
   396  		assert.Equal(t, 1, len(reconciledPvtdata))
   397  		pvtDataStore = append(pvtDataStore, reconciledPvtdata)
   398  		commitPvtDataOfOldBlocksHappened = true
   399  
   400  		require.Nil(t, args.Get(1))
   401  		wg.Done()
   402  	}).Return([]*ledger.PvtdataHashMismatch{}, nil)
   403  
   404  	r := NewReconciler(
   405  		"",
   406  		metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics,
   407  		committer,
   408  		fetcher,
   409  		&PrivdataConfig{
   410  			ReconcileSleepInterval: time.Millisecond * 100,
   411  			ReconcileBatchSize:     1,
   412  			ReconciliationEnabled:  true,
   413  		})
   414  	r.Start()
   415  	<-stopC
   416  	r.Stop()
   417  	nextC <- struct{}{}
   418  	wg.Wait()
   419  
   420  	assert.Equal(t, 2, len(pvtDataStore))
   421  	assert.Equal(t, uint64(4), pvtDataStore[0][0].BlockNum)
   422  	assert.Equal(t, uint64(3), pvtDataStore[1][0].BlockNum)
   423  
   424  	assert.Equal(t, uint64(1), pvtDataStore[0][0].WriteSets[1].SeqInBlock)
   425  	assert.Equal(t, uint64(2), pvtDataStore[1][0].WriteSets[2].SeqInBlock)
   426  
   427  	assert.Equal(t, "ns1", pvtDataStore[0][0].WriteSets[1].WriteSet.NsPvtRwset[0].Namespace)
   428  	assert.Equal(t, "ns2", pvtDataStore[1][0].WriteSets[2].WriteSet.NsPvtRwset[0].Namespace)
   429  
   430  	assert.Equal(t, "col1", pvtDataStore[0][0].WriteSets[1].WriteSet.NsPvtRwset[0].CollectionPvtRwset[0].CollectionName)
   431  	assert.Equal(t, "col2", pvtDataStore[1][0].WriteSets[2].WriteSet.NsPvtRwset[0].CollectionPvtRwset[0].CollectionName)
   432  
   433  	assert.True(t, commitPvtDataOfOldBlocksHappened)
   434  }
   435  
   436  func TestReconciliationFailedToCommit(t *testing.T) {
   437  	committer := &mocks.Committer{}
   438  	fetcher := &mocks.ReconciliationFetcher{}
   439  	configHistoryRetriever := &mocks.ConfigHistoryRetriever{}
   440  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
   441  
   442  	missingInfo := ledger.MissingPvtDataInfo{
   443  		3: map[uint64][]*ledger.MissingCollectionPvtDataInfo{
   444  			1: {{Collection: "col1", Namespace: "ns1"}},
   445  		},
   446  	}
   447  
   448  	collectionConfigInfo := ledger.CollectionConfigInfo{
   449  		CollectionConfig: &peer.CollectionConfigPackage{
   450  			Config: []*peer.CollectionConfig{
   451  				{Payload: &peer.CollectionConfig_StaticCollectionConfig{
   452  					StaticCollectionConfig: &peer.StaticCollectionConfig{
   453  						Name: "col1",
   454  					},
   455  				}},
   456  			},
   457  		},
   458  		CommittingBlockNum: 1,
   459  	}
   460  
   461  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(missingInfo, nil).Run(func(_ mock.Arguments) {
   462  		missingPvtDataTracker.Mock = mock.Mock{}
   463  		missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(nil, nil)
   464  	})
   465  	configHistoryRetriever.On("MostRecentCollectionConfigBelow", mock.Anything, mock.Anything).Return(&collectionConfigInfo, nil)
   466  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
   467  	committer.On("GetConfigHistoryRetriever").Return(configHistoryRetriever, nil)
   468  
   469  	result := &privdatacommon.FetchedPvtDataContainer{}
   470  	fetcher.On("FetchReconciledItems", mock.Anything).Run(func(args mock.Arguments) {
   471  		var dig2CollectionConfig = args.Get(0).(privdatacommon.Dig2CollectionConfig)
   472  		assert.Equal(t, 1, len(dig2CollectionConfig))
   473  		for digest := range dig2CollectionConfig {
   474  			hash := util2.ComputeSHA256([]byte("rws-pre-image"))
   475  			element := &gossip2.PvtDataElement{
   476  				Digest: &gossip2.PvtDataDigest{
   477  					TxId:       digest.TxId,
   478  					BlockSeq:   digest.BlockSeq,
   479  					Collection: digest.Collection,
   480  					Namespace:  digest.Namespace,
   481  					SeqInBlock: digest.SeqInBlock,
   482  				},
   483  				Payload: [][]byte{hash},
   484  			}
   485  			result.AvailableElements = append(result.AvailableElements, element)
   486  		}
   487  	}).Return(result, nil)
   488  
   489  	committer.On("CommitPvtDataOfOldBlocks", mock.Anything, mock.Anything).Return(nil, errors.New("failed to commit"))
   490  
   491  	r := &Reconciler{
   492  		channel:                "mychannel",
   493  		logger:                 logger.With("channel", "mychannel"),
   494  		metrics:                metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics,
   495  		ReconcileSleepInterval: time.Minute,
   496  		ReconcileBatchSize:     1,
   497  		ReconciliationFetcher:  fetcher, Committer: committer,
   498  	}
   499  	err := r.reconcile()
   500  
   501  	assert.Error(t, err)
   502  	assert.Contains(t, err.Error(), "failed to commit")
   503  }
   504  
   505  func TestFailuresWhileReconcilingMissingPvtData(t *testing.T) {
   506  	metrics := metrics.NewGossipMetrics(&disabled.Provider{}).PrivdataMetrics
   507  	committer := &mocks.Committer{}
   508  	fetcher := &mocks.ReconciliationFetcher{}
   509  	committer.On("GetMissingPvtDataTracker").Return(nil, errors.New("failed to obtain missing pvt data tracker"))
   510  
   511  	r := NewReconciler(
   512  		"",
   513  		metrics,
   514  		committer,
   515  		fetcher,
   516  		&PrivdataConfig{
   517  			ReconcileSleepInterval: time.Millisecond * 100,
   518  			ReconcileBatchSize:     1,
   519  			ReconciliationEnabled:  true,
   520  		})
   521  	err := r.reconcile()
   522  	assert.Error(t, err)
   523  	assert.Contains(t, "failed to obtain missing pvt data tracker", err.Error())
   524  
   525  	committer.Mock = mock.Mock{}
   526  	committer.On("GetMissingPvtDataTracker").Return(nil, nil)
   527  	r = NewReconciler("", metrics, committer, fetcher,
   528  		&PrivdataConfig{ReconcileSleepInterval: time.Millisecond * 100, ReconcileBatchSize: 1, ReconciliationEnabled: true})
   529  	err = r.reconcile()
   530  	assert.Error(t, err)
   531  	assert.Contains(t, "got nil as MissingPvtDataTracker, exiting...", err.Error())
   532  
   533  	missingPvtDataTracker := &mocks.MissingPvtDataTracker{}
   534  	missingPvtDataTracker.On("GetMissingPvtDataInfoForMostRecentBlocks", mock.Anything).Return(nil, errors.New("failed get missing pvt data for recent blocks"))
   535  
   536  	committer.Mock = mock.Mock{}
   537  	committer.On("GetMissingPvtDataTracker").Return(missingPvtDataTracker, nil)
   538  	r = NewReconciler("", metrics, committer, fetcher,
   539  		&PrivdataConfig{ReconcileSleepInterval: time.Millisecond * 100, ReconcileBatchSize: 1, ReconciliationEnabled: true})
   540  	err = r.reconcile()
   541  	assert.Error(t, err)
   542  	assert.Contains(t, "failed get missing pvt data for recent blocks", err.Error())
   543  }
   544  
   545  func TestConstructUnreconciledMissingData(t *testing.T) {
   546  	requestedMissingData := privdatacommon.Dig2CollectionConfig{
   547  		privdatacommon.DigKey{
   548  			TxId:       "tx1",
   549  			Namespace:  "ns1",
   550  			Collection: "coll1",
   551  			BlockSeq:   1,
   552  			SeqInBlock: 1,
   553  		}: nil,
   554  		privdatacommon.DigKey{
   555  			TxId:       "tx1",
   556  			Namespace:  "ns2",
   557  			Collection: "coll2",
   558  			BlockSeq:   1,
   559  			SeqInBlock: 1,
   560  		}: nil,
   561  		privdatacommon.DigKey{
   562  			TxId:       "tx1",
   563  			Namespace:  "ns3",
   564  			Collection: "coll3",
   565  			BlockSeq:   1,
   566  			SeqInBlock: 3,
   567  		}: nil,
   568  		privdatacommon.DigKey{
   569  			TxId:       "tx2",
   570  			Namespace:  "ns4",
   571  			Collection: "coll4",
   572  			BlockSeq:   4,
   573  			SeqInBlock: 4,
   574  		}: nil,
   575  	}
   576  
   577  	testCases := []struct {
   578  		description                     string
   579  		fetchedData                     []*gossip2.PvtDataElement
   580  		expectedUnreconciledMissingData ledger.MissingPvtDataInfo
   581  	}{
   582  		{
   583  			description: "none-reconciled",
   584  			fetchedData: nil,
   585  			expectedUnreconciledMissingData: ledger.MissingPvtDataInfo{
   586  				1: ledger.MissingBlockPvtdataInfo{
   587  					1: []*ledger.MissingCollectionPvtDataInfo{
   588  						{
   589  							Namespace:  "ns1",
   590  							Collection: "coll1",
   591  						},
   592  						{
   593  							Namespace:  "ns2",
   594  							Collection: "coll2",
   595  						},
   596  					},
   597  					3: []*ledger.MissingCollectionPvtDataInfo{
   598  						{
   599  							Namespace:  "ns3",
   600  							Collection: "coll3",
   601  						},
   602  					},
   603  				},
   604  				4: ledger.MissingBlockPvtdataInfo{
   605  					4: []*ledger.MissingCollectionPvtDataInfo{
   606  						{
   607  							Namespace:  "ns4",
   608  							Collection: "coll4",
   609  						},
   610  					},
   611  				},
   612  			},
   613  		},
   614  		{
   615  			description: "all-reconciled",
   616  			fetchedData: []*gossip2.PvtDataElement{
   617  				{
   618  					Digest: &gossip2.PvtDataDigest{
   619  						TxId:       "tx1",
   620  						Namespace:  "ns1",
   621  						Collection: "coll1",
   622  						BlockSeq:   1,
   623  						SeqInBlock: 1,
   624  					},
   625  				},
   626  				{
   627  					Digest: &gossip2.PvtDataDigest{
   628  						TxId:       "tx1",
   629  						Namespace:  "ns2",
   630  						Collection: "coll2",
   631  						BlockSeq:   1,
   632  						SeqInBlock: 1,
   633  					},
   634  				},
   635  				{
   636  					Digest: &gossip2.PvtDataDigest{
   637  						TxId:       "tx1",
   638  						Namespace:  "ns3",
   639  						Collection: "coll3",
   640  						BlockSeq:   1,
   641  						SeqInBlock: 3,
   642  					},
   643  				},
   644  				{
   645  					Digest: &gossip2.PvtDataDigest{
   646  						TxId:       "tx2",
   647  						Namespace:  "ns4",
   648  						Collection: "coll4",
   649  						BlockSeq:   4,
   650  						SeqInBlock: 4,
   651  					},
   652  				},
   653  			},
   654  			expectedUnreconciledMissingData: nil,
   655  		},
   656  		{
   657  			description: "some-unreconciled",
   658  			fetchedData: []*gossip2.PvtDataElement{
   659  				{
   660  					Digest: &gossip2.PvtDataDigest{
   661  						TxId:       "tx1",
   662  						Namespace:  "ns1",
   663  						Collection: "coll1",
   664  						BlockSeq:   1,
   665  						SeqInBlock: 1,
   666  					},
   667  				},
   668  				{
   669  					Digest: &gossip2.PvtDataDigest{
   670  						TxId:       "tx1",
   671  						Namespace:  "ns3",
   672  						Collection: "coll3",
   673  						BlockSeq:   1,
   674  						SeqInBlock: 3,
   675  					},
   676  				},
   677  				{
   678  					Digest: &gossip2.PvtDataDigest{
   679  						TxId:       "tx2",
   680  						Namespace:  "ns4",
   681  						Collection: "coll4",
   682  						BlockSeq:   4,
   683  						SeqInBlock: 4,
   684  					},
   685  				},
   686  			},
   687  			expectedUnreconciledMissingData: ledger.MissingPvtDataInfo{
   688  				1: ledger.MissingBlockPvtdataInfo{
   689  					1: []*ledger.MissingCollectionPvtDataInfo{
   690  						{
   691  							Namespace:  "ns2",
   692  							Collection: "coll2",
   693  						},
   694  					},
   695  				},
   696  			},
   697  		},
   698  	}
   699  
   700  	for _, testCase := range testCases {
   701  		t.Run(testCase.description, func(t *testing.T) {
   702  			unreconciledData := constructUnreconciledMissingData(requestedMissingData, testCase.fetchedData)
   703  			require.Equal(t, len(testCase.expectedUnreconciledMissingData), len(unreconciledData))
   704  			for blkNum, txsMissingData := range testCase.expectedUnreconciledMissingData {
   705  				for txNum, expectedUnreconciledData := range txsMissingData {
   706  					require.ElementsMatch(t, expectedUnreconciledData, unreconciledData[blkNum][txNum])
   707  				}
   708  			}
   709  		})
   710  	}
   711  }