github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/snapshot_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package kvledger
     8  
     9  import (
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"os"
    15  	"path/filepath"
    16  	"testing"
    17  
    18  	"github.com/hyperledger/fabric-protos-go/peer"
    19  	"github.com/osdi23p228/fabric/bccsp/sw"
    20  	"github.com/osdi23p228/fabric/common/ledger/testutil"
    21  	"github.com/osdi23p228/fabric/common/metrics/disabled"
    22  	"github.com/osdi23p228/fabric/common/util"
    23  	"github.com/osdi23p228/fabric/core/ledger"
    24  	lgr "github.com/osdi23p228/fabric/core/ledger"
    25  	"github.com/osdi23p228/fabric/core/ledger/mock"
    26  	"github.com/osdi23p228/fabric/protoutil"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func TestGenerateSnapshot(t *testing.T) {
    31  	conf, cleanup := testConfig(t)
    32  	defer cleanup()
    33  	snapshotRootDir := conf.SnapshotsConfig.RootDir
    34  	nsCollBtlConfs := []*nsCollBtlConfig{
    35  		{
    36  			namespace: "ns",
    37  			btlConfig: map[string]uint64{"coll": 0},
    38  		},
    39  	}
    40  	provider := testutilNewProviderWithCollectionConfig(
    41  		t,
    42  		nsCollBtlConfs,
    43  		conf,
    44  	)
    45  	defer provider.Close()
    46  
    47  	// add the genesis block and generate the snapshot
    48  	blkGenerator, genesisBlk := testutil.NewBlockGenerator(t, "testLedgerid", false)
    49  	lgr, err := provider.Create(genesisBlk)
    50  	require.NoError(t, err)
    51  	defer lgr.Close()
    52  	kvlgr := lgr.(*kvLedger)
    53  	require.NoError(t, kvlgr.generateSnapshot())
    54  	verifySnapshotOutput(t,
    55  		snapshotRootDir,
    56  		kvlgr.ledgerID,
    57  		1,
    58  		protoutil.BlockHeaderHash(genesisBlk.Header),
    59  		kvlgr.commitHash,
    60  		"txids.data", "txids.metadata",
    61  	)
    62  
    63  	// add block-1 only with public state data and generate the snapshot
    64  	blockAndPvtdata1 := prepareNextBlockForTest(t, kvlgr, blkGenerator, "SimulateForBlk1",
    65  		map[string]string{"key1": "value1.1", "key2": "value2.1", "key3": "value3.1"},
    66  		nil,
    67  	)
    68  	require.NoError(t, kvlgr.CommitLegacy(blockAndPvtdata1, &ledger.CommitOptions{}))
    69  	require.NoError(t, kvlgr.generateSnapshot())
    70  	verifySnapshotOutput(t,
    71  		snapshotRootDir,
    72  		kvlgr.ledgerID,
    73  		2,
    74  		protoutil.BlockHeaderHash(blockAndPvtdata1.Block.Header),
    75  		kvlgr.commitHash,
    76  		"txids.data", "txids.metadata",
    77  		"public_state.data", "public_state.metadata",
    78  	)
    79  
    80  	// add block-2 only with public and private data and generate the snapshot
    81  	blockAndPvtdata2 := prepareNextBlockForTest(t, kvlgr, blkGenerator, "SimulateForBlk2",
    82  		map[string]string{"key1": "value1.2", "key2": "value2.2", "key3": "value3.2"},
    83  		map[string]string{"key1": "pvtValue1.2", "key2": "pvtValue2.2", "key3": "pvtValue3.2"},
    84  	)
    85  	require.NoError(t, kvlgr.CommitLegacy(blockAndPvtdata2, &ledger.CommitOptions{}))
    86  	require.NoError(t, kvlgr.generateSnapshot())
    87  	verifySnapshotOutput(t,
    88  		snapshotRootDir,
    89  		kvlgr.ledgerID,
    90  		3,
    91  		protoutil.BlockHeaderHash(blockAndPvtdata2.Block.Header),
    92  		kvlgr.commitHash,
    93  		"txids.data", "txids.metadata",
    94  		"public_state.data", "public_state.metadata",
    95  		"private_state_hashes.data", "private_state_hashes.metadata",
    96  	)
    97  
    98  	// add dummy entry in collection config history and commit block-3 and generate the snapshot
    99  	addDummyEntryInCollectionConfigHistory(t, provider, kvlgr.ledgerID)
   100  	blockAndPvtdata3 := prepareNextBlockForTest(t, kvlgr, blkGenerator, "SimulateForBlk3",
   101  		map[string]string{"key1": "value1.3", "key2": "value2.3", "key3": "value3.3"},
   102  		nil,
   103  	)
   104  	require.NoError(t, kvlgr.CommitLegacy(blockAndPvtdata3, &ledger.CommitOptions{}))
   105  	require.NoError(t, kvlgr.generateSnapshot())
   106  	verifySnapshotOutput(t,
   107  		snapshotRootDir,
   108  		kvlgr.ledgerID,
   109  		4,
   110  		protoutil.BlockHeaderHash(blockAndPvtdata3.Block.Header),
   111  		kvlgr.commitHash,
   112  		"txids.data", "txids.metadata",
   113  		"public_state.data", "public_state.metadata",
   114  		"private_state_hashes.data", "private_state_hashes.metadata",
   115  		"confighistory.data", "confighistory.metadata",
   116  	)
   117  }
   118  
   119  func TestSnapshotDirPaths(t *testing.T) {
   120  	require.Equal(t, "/peerFSPath/snapshotRootDir/underConstruction", InProgressSnapshotsPath("/peerFSPath/snapshotRootDir"))
   121  	require.Equal(t, "/peerFSPath/snapshotRootDir/completed", CompletedSnapshotsPath("/peerFSPath/snapshotRootDir"))
   122  	require.Equal(t, "/peerFSPath/snapshotRootDir/completed/myLedger", SnapshotsDirForLedger("/peerFSPath/snapshotRootDir", "myLedger"))
   123  	require.Equal(t, "/peerFSPath/snapshotRootDir/completed/myLedger/2000", SnapshotDirForLedgerHeight("/peerFSPath/snapshotRootDir", "myLedger", 2000))
   124  }
   125  
   126  func TestSnapshotDirPathsCreation(t *testing.T) {
   127  	conf, cleanup := testConfig(t)
   128  	defer cleanup()
   129  	provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{})
   130  	defer func() {
   131  		provider.Close()
   132  	}()
   133  
   134  	inProgressSnapshotsPath := InProgressSnapshotsPath(conf.SnapshotsConfig.RootDir)
   135  	completedSnapshotsPath := CompletedSnapshotsPath(conf.SnapshotsConfig.RootDir)
   136  
   137  	// verify that upon first time start, kvledgerProvider creates an empty temp dir and an empty final dir for the snapshots
   138  	for _, dir := range [2]string{inProgressSnapshotsPath, completedSnapshotsPath} {
   139  		f, err := ioutil.ReadDir(dir)
   140  		require.NoError(t, err)
   141  		require.Len(t, f, 0)
   142  	}
   143  
   144  	// add a file in each of the above folders
   145  	for _, dir := range [2]string{inProgressSnapshotsPath, completedSnapshotsPath} {
   146  		ioutil.WriteFile(filepath.Join(dir, "testFile"), []byte("some junk data"), 0644)
   147  		f, err := ioutil.ReadDir(dir)
   148  		require.NoError(t, err)
   149  		require.Len(t, f, 1)
   150  	}
   151  
   152  	// verify that upon subsequent opening, kvledgerProvider removes any under-processing snapshots,
   153  	// potentially from a previous crash, from the temp dir but it does not remove any files from the final dir
   154  	provider.Close()
   155  	provider = testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{})
   156  	f, err := ioutil.ReadDir(inProgressSnapshotsPath)
   157  	require.NoError(t, err)
   158  	require.Len(t, f, 0)
   159  	f, err = ioutil.ReadDir(completedSnapshotsPath)
   160  	require.NoError(t, err)
   161  	require.Len(t, f, 1)
   162  }
   163  
   164  func TestSnapshotsDirInitializingErrors(t *testing.T) {
   165  	initKVLedgerProvider := func(conf *ledger.Config) error {
   166  		cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   167  		require.NoError(t, err)
   168  		_, err = NewProvider(
   169  			&lgr.Initializer{
   170  				DeployedChaincodeInfoProvider: &mock.DeployedChaincodeInfoProvider{},
   171  				MetricsProvider:               &disabled.Provider{},
   172  				Config:                        conf,
   173  				HashProvider:                  cryptoProvider,
   174  			},
   175  		)
   176  		return err
   177  	}
   178  
   179  	t.Run("invalid-path", func(t *testing.T) {
   180  		conf, cleanup := testConfig(t)
   181  		defer cleanup()
   182  		conf.SnapshotsConfig.RootDir = "./a-relative-path"
   183  		err := initKVLedgerProvider(conf)
   184  		require.EqualError(t, err, "invalid path: ./a-relative-path. The path for the snapshot dir is expected to be an absolute path")
   185  	})
   186  
   187  	t.Run("snapshots final dir creation returns error", func(t *testing.T) {
   188  		conf, cleanup := testConfig(t)
   189  		defer cleanup()
   190  
   191  		completedSnapshotsPath := CompletedSnapshotsPath(conf.SnapshotsConfig.RootDir)
   192  		require.NoError(t, os.MkdirAll(filepath.Dir(completedSnapshotsPath), 0755))
   193  		require.NoError(t, ioutil.WriteFile(completedSnapshotsPath, []byte("some data"), 0644))
   194  		err := initKVLedgerProvider(conf)
   195  		require.Error(t, err)
   196  		require.Contains(t, err.Error(), "while creating the dir: "+completedSnapshotsPath)
   197  	})
   198  }
   199  
   200  func TestGenerateSnapshotErrors(t *testing.T) {
   201  	conf, cleanup := testConfig(t)
   202  	defer cleanup()
   203  	provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{})
   204  	defer func() {
   205  		provider.Close()
   206  	}()
   207  
   208  	// create a ledger
   209  	_, genesisBlk := testutil.NewBlockGenerator(t, "testLedgerid", false)
   210  	lgr, err := provider.Create(genesisBlk)
   211  	require.NoError(t, err)
   212  	kvlgr := lgr.(*kvLedger)
   213  
   214  	closeAndReopenLedgerProvider := func() {
   215  		provider.Close()
   216  		provider = testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{})
   217  		lgr, err = provider.Open("testLedgerid")
   218  		require.NoError(t, err)
   219  		kvlgr = lgr.(*kvLedger)
   220  	}
   221  
   222  	t.Run("snapshot tmp dir creation returns error", func(t *testing.T) {
   223  		closeAndReopenLedgerProvider()
   224  		require.NoError(t, os.RemoveAll( // remove the base tempdir so that the snapshot tempdir creation fails
   225  			InProgressSnapshotsPath(conf.SnapshotsConfig.RootDir),
   226  		))
   227  		err := kvlgr.generateSnapshot()
   228  		require.Error(t, err)
   229  		require.Contains(t, err.Error(), "error while creating temp dir")
   230  	})
   231  
   232  	t.Run("block store returns error", func(t *testing.T) {
   233  		closeAndReopenLedgerProvider()
   234  		provider.blkStoreProvider.Close() // close the blockstore provider to trigger the error
   235  		err := kvlgr.generateSnapshot()
   236  		require.Error(t, err)
   237  		errStackTrace := fmt.Sprintf("%+v", err)
   238  		require.Contains(t, errStackTrace, "internal leveldb error while obtaining db iterator")
   239  		require.Contains(t, errStackTrace, "fabric/common/ledger/blkstorage/blockindex.go")
   240  	})
   241  
   242  	t.Run("config history mgr returns error", func(t *testing.T) {
   243  		closeAndReopenLedgerProvider()
   244  		provider.configHistoryMgr.Close() // close the configHistoryMgr to trigger the error
   245  		err := kvlgr.generateSnapshot()
   246  		require.Error(t, err)
   247  		errStackTrace := fmt.Sprintf("%+v", err)
   248  		require.Contains(t, errStackTrace, "internal leveldb error while obtaining db iterator")
   249  		require.Contains(t, errStackTrace, "fabric/core/ledger/confighistory/mgr.go")
   250  	})
   251  
   252  	t.Run("statedb returns error", func(t *testing.T) {
   253  		closeAndReopenLedgerProvider()
   254  		provider.dbProvider.Close() // close the dbProvider to trigger the error
   255  		err := kvlgr.generateSnapshot()
   256  		require.Error(t, err)
   257  		errStackTrace := fmt.Sprintf("%+v", err)
   258  		require.Contains(t, errStackTrace, "internal leveldb error while obtaining db iterator")
   259  		require.Contains(t, errStackTrace, "statedb/stateleveldb/stateleveldb.go")
   260  	})
   261  
   262  	t.Run("renaming to the final snapshot dir returns error", func(t *testing.T) {
   263  		closeAndReopenLedgerProvider()
   264  		snapshotFinalDir := SnapshotDirForLedgerHeight(conf.SnapshotsConfig.RootDir, "testLedgerid", 1)
   265  		require.NoError(t, os.MkdirAll(snapshotFinalDir, 0744))
   266  		defer os.RemoveAll(snapshotFinalDir)
   267  		require.NoError(t, ioutil.WriteFile( // make a non-empty snapshotFinalDir to trigger failure on rename
   268  			filepath.Join(snapshotFinalDir, "dummyFile"),
   269  			[]byte("dummy file"), 0444),
   270  		)
   271  		err := kvlgr.generateSnapshot()
   272  		require.Contains(t, err.Error(), "error while renaming dir")
   273  	})
   274  }
   275  
   276  func TestFileUtilFunctionsErrors(t *testing.T) {
   277  	t.Run("syncDir-openfile", func(t *testing.T) {
   278  		err := syncDir("non-existent-dir")
   279  		require.Error(t, err)
   280  		require.Contains(t, err.Error(), "error while opening dir:non-existent-dir")
   281  	})
   282  
   283  	t.Run("createAndSyncFile-openfile", func(t *testing.T) {
   284  		path, err := ioutil.TempDir("", "kvledger")
   285  		err = createAndSyncFile(path, []byte("dummy content"))
   286  		require.Error(t, err)
   287  		require.Contains(t, err.Error(), "error while creating file:"+path)
   288  	})
   289  }
   290  
   291  func verifySnapshotOutput(
   292  	t *testing.T,
   293  	snapshotRootDir string,
   294  	ledgerID string,
   295  	ledgerHeight uint64,
   296  	lastBlockHash []byte,
   297  	lastCommitHash []byte,
   298  	expectedBinaryFiles ...string,
   299  ) {
   300  	inProgressSnapshotsPath := InProgressSnapshotsPath(snapshotRootDir)
   301  	f, err := ioutil.ReadDir(inProgressSnapshotsPath)
   302  	require.NoError(t, err)
   303  	require.Len(t, f, 0)
   304  
   305  	snapshotDir := SnapshotDirForLedgerHeight(snapshotRootDir, ledgerID, ledgerHeight)
   306  	files, err := ioutil.ReadDir(snapshotDir)
   307  	require.NoError(t, err)
   308  	require.Len(t, files, len(expectedBinaryFiles)+2) // + 2 JSON files
   309  
   310  	filesAndHashes := map[string]string{}
   311  	for _, f := range expectedBinaryFiles {
   312  		c, err := ioutil.ReadFile(filepath.Join(snapshotDir, f))
   313  		require.NoError(t, err)
   314  		filesAndHashes[f] = hex.EncodeToString(util.ComputeSHA256(c))
   315  	}
   316  
   317  	// verify the contents of the file snapshot_metadata.json
   318  	m := &snapshotSignableMetadata{}
   319  	mJSON, err := ioutil.ReadFile(filepath.Join(snapshotDir, snapshotMetadataFileName))
   320  	require.NoError(t, err)
   321  	require.NoError(t, json.Unmarshal(mJSON, m))
   322  	require.Equal(t,
   323  		&snapshotSignableMetadata{
   324  			ChannelName:        ledgerID,
   325  			ChannelHeight:      ledgerHeight,
   326  			LastBlockHashInHex: hex.EncodeToString(lastBlockHash),
   327  			FilesAndHashes:     filesAndHashes,
   328  		},
   329  		m,
   330  	)
   331  
   332  	// verify the contents of the file snapshot_metadata_hash.json
   333  	mh := &snapshotAdditionalInfo{}
   334  	mhJSON, err := ioutil.ReadFile(filepath.Join(snapshotDir, snapshotMetadataHashFileName))
   335  	require.NoError(t, err)
   336  	require.NoError(t, json.Unmarshal(mhJSON, mh))
   337  	require.Equal(t,
   338  		&snapshotAdditionalInfo{
   339  			SnapshotHashInHex:        hex.EncodeToString(util.ComputeSHA256(mJSON)),
   340  			LastBlockCommitHashInHex: hex.EncodeToString(lastCommitHash),
   341  		},
   342  		mh,
   343  	)
   344  }
   345  
   346  func addDummyEntryInCollectionConfigHistory(t *testing.T, provider *Provider, ledgerID string) {
   347  	// configure mock to cause data entry in collection config history
   348  	ccInfoProviderMock := provider.initializer.DeployedChaincodeInfoProvider.(*mock.DeployedChaincodeInfoProvider)
   349  	ccInfoProviderMock.UpdatedChaincodesReturns(
   350  		[]*ledger.ChaincodeLifecycleInfo{
   351  			{
   352  				Name: "ns",
   353  			},
   354  		},
   355  		nil,
   356  	)
   357  
   358  	ccInfoProviderMock.ChaincodeInfoReturns(
   359  		&ledger.DeployedChaincodeInfo{
   360  			Name: "ns",
   361  			ExplicitCollectionConfigPkg: &peer.CollectionConfigPackage{
   362  				Config: []*peer.CollectionConfig{
   363  					{
   364  						Payload: &peer.CollectionConfig_StaticCollectionConfig{
   365  							StaticCollectionConfig: &peer.StaticCollectionConfig{
   366  								Name: "coll1",
   367  							},
   368  						},
   369  					},
   370  				},
   371  			},
   372  		},
   373  		nil,
   374  	)
   375  	provider.configHistoryMgr.HandleStateUpdates(
   376  		&ledger.StateUpdateTrigger{
   377  			LedgerID: ledgerID,
   378  			StateUpdates: map[string]*ledger.KVStateUpdates{
   379  				"ns": {},
   380  			},
   381  		},
   382  	)
   383  }