920 lines
24 KiB
Go
920 lines
24 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package compare
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"hash"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hyperledger/fabric/common/ledger/util"
|
|
"github.com/hyperledger/fabric/core/ledger/kvledger"
|
|
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/privacyenabledstate"
|
|
"github.com/hyperledger/fabric/internal/fileutil"
|
|
"github.com/hyperledger/fabric/internal/ledgerutil/jsonrw"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var testNewHashFunc = func() (hash.Hash, error) {
|
|
return sha256.New(), nil
|
|
}
|
|
|
|
type testRecord struct {
|
|
namespace string
|
|
key string
|
|
value string
|
|
blockNum uint64
|
|
txNum uint64
|
|
metadata string
|
|
}
|
|
|
|
func TestCompare(t *testing.T) {
|
|
// Each list of testRecords represents the records of a single snapshot
|
|
sampleRecords1 := []*testRecord{
|
|
{
|
|
namespace: "ns1", key: "k1", value: "v1",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
{
|
|
namespace: "ns1", key: "k2", value: "v2",
|
|
blockNum: 1, txNum: 1, metadata: "md2",
|
|
},
|
|
{
|
|
namespace: "ns2", key: "k1", value: "v3",
|
|
blockNum: 1, txNum: 2, metadata: "md3",
|
|
},
|
|
{
|
|
namespace: "ns3", key: "k1", value: "v4",
|
|
blockNum: 2, txNum: 0, metadata: "md4",
|
|
},
|
|
}
|
|
|
|
sampleRecords2 := []*testRecord{
|
|
{
|
|
namespace: "ns1", key: "k1", value: "v1",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
{
|
|
namespace: "ns1", key: "k2", value: "v2",
|
|
blockNum: 1, txNum: 1, metadata: "md2",
|
|
},
|
|
{
|
|
namespace: "ns2", key: "k1", value: "v4",
|
|
blockNum: 1, txNum: 2, metadata: "md3",
|
|
},
|
|
{
|
|
namespace: "ns3", key: "k1", value: "v4",
|
|
blockNum: 2, txNum: 0, metadata: "md4",
|
|
},
|
|
}
|
|
|
|
sampleRecords3 := []*testRecord{
|
|
{
|
|
namespace: "ns1", key: "k1", value: "v1",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
{
|
|
namespace: "ns2", key: "k1", value: "v3",
|
|
blockNum: 1, txNum: 2, metadata: "md3",
|
|
},
|
|
{
|
|
namespace: "ns3", key: "k1", value: "v4",
|
|
blockNum: 2, txNum: 0, metadata: "md4",
|
|
},
|
|
}
|
|
|
|
sampleRecords4 := []*testRecord{
|
|
{
|
|
namespace: "ns1", key: "k1", value: "v1",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
{
|
|
namespace: "ns1", key: "k2", value: "v2",
|
|
blockNum: 1, txNum: 1, metadata: "md2",
|
|
},
|
|
}
|
|
|
|
sampleRecords5 := []*testRecord{
|
|
{
|
|
namespace: "ns1", key: "k1", value: "v1",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
{
|
|
namespace: "ns1", key: "k2", value: "v3",
|
|
blockNum: 1, txNum: 1, metadata: "md2",
|
|
},
|
|
{
|
|
namespace: "ns3", key: "k1", value: "v4",
|
|
blockNum: 2, txNum: 1, metadata: "md4",
|
|
},
|
|
{
|
|
namespace: "ns3", key: "k2", value: "v5",
|
|
blockNum: 1, txNum: 3, metadata: "md5",
|
|
},
|
|
}
|
|
|
|
samplePvtRecords1 := []*testRecord{
|
|
{
|
|
namespace: "_lifecycle$$h_implicit_org_Org1MSP", key: "sk1", value: "#!",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
}
|
|
|
|
samplePvtRecords2 := []*testRecord{
|
|
{
|
|
namespace: "_lifecycle$$h_implicit_org_Org1MSP", key: "sk1", value: "$^",
|
|
blockNum: 1, txNum: 1, metadata: "md1",
|
|
},
|
|
}
|
|
|
|
// Signable metadata samples for snapshots
|
|
sampleSignableMetadata1 := &kvledger.SnapshotSignableMetadata{
|
|
ChannelName: "testchannel",
|
|
LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum,
|
|
LastBlockHashInHex: "last_block_hash",
|
|
PreviousBlockHashInHex: "previous_block_hash",
|
|
FilesAndHashes: map[string]string{
|
|
"private_state_hashes.data": "private_state_hash1",
|
|
"private_state_hashes.metadata": "private_state_hash1",
|
|
"public_state.data": "public_state_hash1",
|
|
"public_state.metadata": "public_state_hash1",
|
|
"txids.data": "txids_hash1",
|
|
"txids.metadata": "txids_hash1",
|
|
},
|
|
StateDBType: "testdatabase",
|
|
}
|
|
|
|
sampleSignableMetadata2 := &kvledger.SnapshotSignableMetadata{
|
|
ChannelName: "testchannel",
|
|
LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum,
|
|
LastBlockHashInHex: "last_block_hash",
|
|
PreviousBlockHashInHex: "previous_block_hash",
|
|
FilesAndHashes: map[string]string{
|
|
"private_state_hashes.data": "private_state_hash1",
|
|
"private_state_hashes.metadata": "private_state_hash1",
|
|
"public_state.data": "public_state_hash2",
|
|
"public_state.metadata": "public_state_hash2",
|
|
"txids.data": "txids_hash2",
|
|
"txids.metadata": "txids_hash2",
|
|
},
|
|
StateDBType: "testdatabase",
|
|
}
|
|
|
|
sampleSignableMetadata3 := &kvledger.SnapshotSignableMetadata{
|
|
ChannelName: "testchannel",
|
|
LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum,
|
|
LastBlockHashInHex: "last_block_hash",
|
|
PreviousBlockHashInHex: "previous_block_hash",
|
|
FilesAndHashes: map[string]string{
|
|
"private_state_hashes.data": "private_state_hash2",
|
|
"private_state_hashes.metadata": "private_state_hash2",
|
|
"public_state.data": "public_state_hash2",
|
|
"public_state.metadata": "public_state_hash2",
|
|
"txids.data": "txids_hash2",
|
|
"txids.metadata": "txids_hash2",
|
|
},
|
|
StateDBType: "testdatabase2",
|
|
}
|
|
|
|
sampleSignableMetadata4 := &kvledger.SnapshotSignableMetadata{
|
|
ChannelName: "testchannel",
|
|
LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum,
|
|
LastBlockHashInHex: "last_block_hash",
|
|
PreviousBlockHashInHex: "previous_block_hash",
|
|
FilesAndHashes: map[string]string{
|
|
"private_state_hashes.data": "private_state_hash2",
|
|
"private_state_hashes.metadata": "private_state_hash2",
|
|
"public_state.data": "public_state_hash2",
|
|
"public_state.metadata": "public_state_hash2",
|
|
"txids.data": "txids_hash2",
|
|
"txids.metadata": "txids_hash2",
|
|
},
|
|
StateDBType: "testdatabase",
|
|
}
|
|
|
|
sampleSignableMetadata5 := &kvledger.SnapshotSignableMetadata{
|
|
ChannelName: "testchannel",
|
|
LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum,
|
|
LastBlockHashInHex: "last_block_hash",
|
|
PreviousBlockHashInHex: "previous_block_hash",
|
|
FilesAndHashes: map[string]string{
|
|
"private_state_hashes.data": "private_state_hash2",
|
|
"private_state_hashes.metadata": "private_state_hash2",
|
|
"public_state.data": "public_state_hash1",
|
|
"public_state.metadata": "public_state_hash1",
|
|
"txids.data": "txids_hash2",
|
|
"txids.metadata": "txids_hash2",
|
|
},
|
|
StateDBType: "testdatabase",
|
|
}
|
|
|
|
// Expected outputs
|
|
expectedDifferenceResult := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns2",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "v4",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedMissingResult1 := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns1",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v2",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
},
|
|
"snapshotrecord2" : null
|
|
}
|
|
]
|
|
}`
|
|
expectedMissingResult2 := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns1",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "v2",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedMissingTailResult1 := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns2",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
},
|
|
"snapshotrecord2" : null
|
|
},
|
|
{
|
|
"namespace" : "ns3",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v4",
|
|
"blockNum" : 2,
|
|
"txNum" : 0
|
|
},
|
|
"snapshotrecord2" : null
|
|
}
|
|
]
|
|
}`
|
|
expectedMissingTailResult2 := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns2",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
}
|
|
},
|
|
{
|
|
"namespace" : "ns3",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "v4",
|
|
"blockNum" : 2,
|
|
"txNum" : 0
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedDiffDatabaseError := "the supplied snapshots appear to be non-comparable. State db types do not match." +
|
|
"\nSnapshot1 state db type: testdatabase\nSnapshot2 state db type: testdatabase2"
|
|
expectedFirstDiffs := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns1",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v2",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
}
|
|
},
|
|
{
|
|
"namespace" : "ns2",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
},
|
|
"snapshotrecord2" : null
|
|
},
|
|
{
|
|
"namespace" : "ns3",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "v5",
|
|
"blockNum" : 1,
|
|
"txNum" : 3
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedAllPubDiffs := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "ns1",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v2",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
}
|
|
},
|
|
{
|
|
"namespace" : "ns2",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v3",
|
|
"blockNum" : 1,
|
|
"txNum" : 2
|
|
},
|
|
"snapshotrecord2" : null
|
|
},
|
|
{
|
|
"namespace" : "ns3",
|
|
"key" : "k1",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "v4",
|
|
"blockNum" : 2,
|
|
"txNum" : 0
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "v4",
|
|
"blockNum" : 2,
|
|
"txNum" : 1
|
|
}
|
|
},
|
|
{
|
|
"namespace" : "ns3",
|
|
"key" : "k2",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "v5",
|
|
"blockNum" : 1,
|
|
"txNum" : 3
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedPvtDiffResult := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "_lifecycle$$h_implicit_org_Org1MSP",
|
|
"key" : "736b31",
|
|
"hashed" : true,
|
|
"snapshotrecord1" : {
|
|
"value" : "2321",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "245e",
|
|
"blockNum" : 1,
|
|
"txNum" : 1
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
expectedEmpty := "null"
|
|
|
|
testCases := map[string]struct {
|
|
inputTestRecords1 []*testRecord
|
|
inputTestPvtRecords1 []*testRecord
|
|
inputSignableMetadata1 *kvledger.SnapshotSignableMetadata
|
|
inputTestRecords2 []*testRecord
|
|
inputTestPvtRecords2 []*testRecord
|
|
inputSignableMetadata2 *kvledger.SnapshotSignableMetadata
|
|
expectedOutputType string
|
|
expectedPubOutput string
|
|
expectedPvtOutput string
|
|
expectedFirOutput string
|
|
expectedError string
|
|
firstN int
|
|
expectedDiffCount int
|
|
}{
|
|
// Snapshots have a single difference in record
|
|
"single-difference": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords2,
|
|
inputSignableMetadata2: sampleSignableMetadata2,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedDifferenceResult,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 1,
|
|
},
|
|
// Second snapshot is missing a record
|
|
"second-missing": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords3,
|
|
inputSignableMetadata2: sampleSignableMetadata2,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedMissingResult1,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 1,
|
|
},
|
|
// First snapshot is missing a record
|
|
"first-missing": {
|
|
inputTestRecords1: sampleRecords3,
|
|
inputSignableMetadata1: sampleSignableMetadata2,
|
|
inputTestRecords2: sampleRecords1,
|
|
inputSignableMetadata2: sampleSignableMetadata1,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedMissingResult2,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 1,
|
|
},
|
|
// Second snapshot is missing tailing records
|
|
"second-missing-tail": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords4,
|
|
inputSignableMetadata2: sampleSignableMetadata2,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedMissingTailResult1,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 2,
|
|
},
|
|
// First snapshot is missing tailing records
|
|
"first-missing-tail": {
|
|
inputTestRecords1: sampleRecords4,
|
|
inputSignableMetadata1: sampleSignableMetadata2,
|
|
inputTestRecords2: sampleRecords1,
|
|
inputSignableMetadata2: sampleSignableMetadata1,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedMissingTailResult2,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 2,
|
|
},
|
|
// Snapshots contain the same public state hashes
|
|
"same-hash": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords1,
|
|
inputSignableMetadata2: sampleSignableMetadata1,
|
|
expectedOutputType: "same-hash",
|
|
expectedDiffCount: -1,
|
|
},
|
|
// Snapshots contain different metadata (different databases in this case) that makes them non-comparable
|
|
"different-database": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords2,
|
|
inputSignableMetadata2: sampleSignableMetadata3,
|
|
expectedOutputType: "error",
|
|
expectedError: expectedDiffDatabaseError,
|
|
expectedDiffCount: 0,
|
|
},
|
|
// Output directory file already exists
|
|
"output-dir-exists": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords2,
|
|
inputSignableMetadata2: sampleSignableMetadata2,
|
|
expectedOutputType: "exists-error",
|
|
expectedDiffCount: 0,
|
|
},
|
|
// User requested first 3 differences
|
|
"first-3-diffs": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords5,
|
|
inputSignableMetadata2: sampleSignableMetadata2,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedAllPubDiffs,
|
|
expectedPvtOutput: expectedEmpty,
|
|
expectedFirOutput: expectedFirstDiffs,
|
|
firstN: 3,
|
|
expectedDiffCount: 4,
|
|
},
|
|
// Snapshots have a single difference public difference and a single private difference
|
|
"pub-and-pvt-difference": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputTestPvtRecords1: samplePvtRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords2,
|
|
inputTestPvtRecords2: samplePvtRecords2,
|
|
inputSignableMetadata2: sampleSignableMetadata4,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedDifferenceResult,
|
|
expectedPvtOutput: expectedPvtDiffResult,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 2,
|
|
},
|
|
// Snapshots have a single private difference only
|
|
"pvt-difference-only": {
|
|
inputTestRecords1: sampleRecords1,
|
|
inputTestPvtRecords1: samplePvtRecords1,
|
|
inputSignableMetadata1: sampleSignableMetadata1,
|
|
inputTestRecords2: sampleRecords1,
|
|
inputTestPvtRecords2: samplePvtRecords2,
|
|
inputSignableMetadata2: sampleSignableMetadata5,
|
|
expectedOutputType: "json",
|
|
expectedPubOutput: expectedEmpty,
|
|
expectedPvtOutput: expectedPvtDiffResult,
|
|
expectedFirOutput: expectedEmpty,
|
|
expectedDiffCount: 1,
|
|
},
|
|
}
|
|
|
|
// Run test cases individually
|
|
for testName, testCase := range testCases {
|
|
t.Run(testName, func(t *testing.T) {
|
|
// Create temporary directories for the sample snapshots and comparison results
|
|
snapshotDir1 := t.TempDir()
|
|
snapshotDir2 := t.TempDir()
|
|
resultsDir := t.TempDir()
|
|
|
|
// Populate temporary directories with sample snapshot data
|
|
err := createSnapshot(snapshotDir1, testCase.inputTestRecords1, testCase.inputTestPvtRecords1, testCase.inputSignableMetadata1)
|
|
require.NoError(t, err)
|
|
err = createSnapshot(snapshotDir2, testCase.inputTestRecords2, testCase.inputTestPvtRecords2, testCase.inputSignableMetadata2)
|
|
require.NoError(t, err)
|
|
|
|
// Compare snapshots and check the output
|
|
count, pubOut, pvtOut, firOut, err := compareSnapshots(snapshotDir1, snapshotDir2, resultsDir, testCase.firstN)
|
|
switch testCase.expectedOutputType {
|
|
case "error":
|
|
require.Equal(t, testCase.expectedDiffCount, count)
|
|
require.ErrorContains(t, err, testCase.expectedError)
|
|
case "exists-error":
|
|
require.NoError(t, err)
|
|
count, _, _, _, err = compareSnapshots(snapshotDir1, snapshotDir2, resultsDir, testCase.firstN)
|
|
require.Equal(t, testCase.expectedDiffCount, count)
|
|
require.ErrorContains(t, err, "testchannel_2_comparison already exists in "+resultsDir+". Choose a different location or remove the existing results. Aborting compare")
|
|
case "json":
|
|
require.Equal(t, testCase.expectedDiffCount, count)
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, testCase.expectedPubOutput, pubOut)
|
|
require.JSONEq(t, testCase.expectedPvtOutput, pvtOut)
|
|
require.JSONEq(t, testCase.expectedFirOutput, firOut)
|
|
case "same-hash":
|
|
require.Equal(t, testCase.expectedDiffCount, count)
|
|
require.NoError(t, err)
|
|
default:
|
|
panic("unexpected code path: bug")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// createSnapshot generates a sample snapshot based on the passed in records and metadata
|
|
func createSnapshot(dir string, pubStateRecords []*testRecord, pvtStateRecords []*testRecord,
|
|
signableMetadata *kvledger.SnapshotSignableMetadata) error {
|
|
// Generate public state of sample snapshot
|
|
pubStateWriter, err := privacyenabledstate.NewSnapshotWriter(
|
|
dir,
|
|
privacyenabledstate.PubStateDataFileName,
|
|
privacyenabledstate.PubStateMetadataFileName,
|
|
testNewHashFunc,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer pubStateWriter.Close()
|
|
|
|
// Add sample records to sample snapshot
|
|
for _, sample := range pubStateRecords {
|
|
err = pubStateWriter.AddData(sample.namespace, &privacyenabledstate.SnapshotRecord{
|
|
Key: []byte(sample.key),
|
|
Value: []byte(sample.value),
|
|
Metadata: []byte(sample.metadata),
|
|
Version: toBytes(sample.blockNum, sample.txNum),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, _, err = pubStateWriter.Done()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate private state of sample snapshot
|
|
pvtStateWriter, err := privacyenabledstate.NewSnapshotWriter(
|
|
dir,
|
|
privacyenabledstate.PvtStateHashesFileName,
|
|
privacyenabledstate.PvtStateHashesMetadataFileName,
|
|
testNewHashFunc,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer pvtStateWriter.Close()
|
|
|
|
// Add sample records to sample snapshot
|
|
for _, sample := range pvtStateRecords {
|
|
err = pvtStateWriter.AddData(sample.namespace, &privacyenabledstate.SnapshotRecord{
|
|
Key: []byte(sample.key),
|
|
Value: []byte(sample.value),
|
|
Metadata: []byte(sample.metadata),
|
|
Version: toBytes(sample.blockNum, sample.txNum),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, _, err = pvtStateWriter.Done()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate the signable metadata files for sample snapshot
|
|
signableMetadataBytes, err := signableMetadata.ToJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Populate temporary directory with signable metadata files
|
|
err = fileutil.CreateAndSyncFile(filepath.Join(dir, kvledger.SnapshotSignableMetadataFileName), signableMetadataBytes, 0o444)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// compareSnapshots calls the Compare tool and extracts the result json
|
|
func compareSnapshots(ss1 string, ss2 string, res string, firstN int) (int, string, string, string, error) {
|
|
// Run compare tool on snapshots
|
|
count, opath, err := Compare(ss1, ss2, res, firstN)
|
|
if err != nil || count == -1 {
|
|
return count, "", "", "", err
|
|
}
|
|
|
|
// Get json as a string from generated output files
|
|
pubStr, err := jsonrw.OutputFileToString(AllPubDiffsByKey, opath)
|
|
if err != nil {
|
|
return 0, "", "", "", err
|
|
}
|
|
pvtStr, err := jsonrw.OutputFileToString(AllPvtDiffsByKey, opath)
|
|
if err != nil {
|
|
return 0, "", "", "", err
|
|
}
|
|
firStr, err := jsonrw.OutputFileToString(FirstDiffsByHeight, opath)
|
|
if err != nil {
|
|
return 0, "", "", "", err
|
|
}
|
|
|
|
return count, pubStr, pvtStr, firStr, nil
|
|
}
|
|
|
|
// toBytes serializes the Height
|
|
func toBytes(blockNum uint64, txNum uint64) []byte {
|
|
blockNumBytes := util.EncodeOrderPreservingVarUint64(blockNum)
|
|
txNumBytes := util.EncodeOrderPreservingVarUint64(txNum)
|
|
return append(blockNumBytes, txNumBytes...)
|
|
}
|
|
|
|
func TestJSONArrayFileWriter(t *testing.T) {
|
|
sampleDiffRecords := []diffRecord{
|
|
{
|
|
Namespace: "abc",
|
|
Key: "key-52",
|
|
Hashed: false,
|
|
Record1: &snapshotRecord{
|
|
Value: "red",
|
|
BlockNum: 254,
|
|
TxNum: 21,
|
|
},
|
|
Record2: &snapshotRecord{
|
|
Value: "blue",
|
|
BlockNum: 254,
|
|
TxNum: 21,
|
|
},
|
|
},
|
|
{
|
|
Namespace: "abc",
|
|
Key: "key-73",
|
|
Hashed: false,
|
|
Record1: &snapshotRecord{
|
|
Value: "green",
|
|
BlockNum: 472,
|
|
TxNum: 61,
|
|
},
|
|
Record2: nil,
|
|
},
|
|
{
|
|
Namespace: "xyz",
|
|
Key: "key-44",
|
|
Hashed: false,
|
|
Record1: nil,
|
|
Record2: &snapshotRecord{
|
|
Value: "purple",
|
|
BlockNum: 566,
|
|
TxNum: 3,
|
|
},
|
|
},
|
|
}
|
|
|
|
expectedResult := `{
|
|
"ledgerid" : "testchannel",
|
|
"diffRecords" : [
|
|
{
|
|
"namespace" : "abc",
|
|
"key" : "key-52",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "red",
|
|
"blockNum" : 254,
|
|
"txNum" : 21
|
|
},
|
|
"snapshotrecord2" : {
|
|
"value" : "blue",
|
|
"blockNum" : 254,
|
|
"txNum" : 21
|
|
}
|
|
},
|
|
{
|
|
"namespace" : "abc",
|
|
"key" : "key-73",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : {
|
|
"value" : "green",
|
|
"blockNum" : 472,
|
|
"txNum" : 61
|
|
},
|
|
"snapshotrecord2" : null
|
|
},
|
|
{
|
|
"namespace" : "xyz",
|
|
"key" : "key-44",
|
|
"hashed" : false,
|
|
"snapshotrecord1" : null,
|
|
"snapshotrecord2" : {
|
|
"value" : "purple",
|
|
"blockNum" : 566,
|
|
"txNum" : 3
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
|
|
// Create temporary directory for output
|
|
resultDir := t.TempDir()
|
|
// Create the output file
|
|
jsonResultFile, err := newJSONFileWriter(filepath.Join(resultDir, "result.json"), "testchannel")
|
|
require.NoError(t, err)
|
|
// Write each sample diffRecord to the output file and close
|
|
for _, diffRecord := range sampleDiffRecords {
|
|
err = jsonResultFile.addRecord(diffRecord)
|
|
require.NoError(t, err)
|
|
}
|
|
err = jsonResultFile.close()
|
|
require.NoError(t, err)
|
|
|
|
// Read results of output and compare
|
|
resBytes, err := ioutil.ReadFile(filepath.Join(resultDir, "result.json"))
|
|
require.NoError(t, err)
|
|
res, err := ioutil.ReadAll(bytes.NewReader(resBytes))
|
|
require.NoError(t, err)
|
|
|
|
require.JSONEq(t, expectedResult, string(res))
|
|
}
|
|
|
|
func TestNSKeyCompare(t *testing.T) {
|
|
testCases := []struct {
|
|
nsKey1 nsKey
|
|
nsKey2 nsKey
|
|
expected int
|
|
}{
|
|
{
|
|
nsKey1: nsKey{
|
|
namespace: "xyz",
|
|
key: []byte("key-1"),
|
|
},
|
|
nsKey2: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-2"),
|
|
},
|
|
expected: 1,
|
|
},
|
|
{
|
|
nsKey1: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-1"),
|
|
},
|
|
nsKey2: nsKey{
|
|
namespace: "xyz",
|
|
key: []byte("key-2"),
|
|
},
|
|
expected: -1,
|
|
},
|
|
{
|
|
nsKey1: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-1"),
|
|
},
|
|
nsKey2: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-2"),
|
|
},
|
|
expected: -1,
|
|
},
|
|
{
|
|
nsKey1: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-2"),
|
|
},
|
|
nsKey2: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-1"),
|
|
},
|
|
expected: 1,
|
|
},
|
|
{
|
|
nsKey1: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-1"),
|
|
},
|
|
nsKey2: nsKey{
|
|
namespace: "abc",
|
|
key: []byte("key-1"),
|
|
},
|
|
expected: 0,
|
|
},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
t.Run(fmt.Sprint(i+1), func(t *testing.T) {
|
|
require.Equal(t, nsKeyCompare(&testCase.nsKey1, &testCase.nsKey2), testCase.expected)
|
|
})
|
|
}
|
|
}
|