github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/statedb/statecouchdb/redolog_test.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package statecouchdb 8 9 import ( 10 "fmt" 11 "io/ioutil" 12 "os" 13 "testing" 14 15 "github.com/davecgh/go-spew/spew" 16 "github.com/hechain20/hechain/core/ledger/internal/version" 17 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestRedoLogger(t *testing.T) { 22 provider, cleanup := redologTestSetup(t) 23 defer cleanup() 24 25 loggers := []*redoLogger{} 26 records := []*redoRecord{} 27 28 verifyLogRecords := func() { 29 for i := 0; i < len(loggers); i++ { 30 retrievedRec, err := loggers[i].load() 31 require.NoError(t, err) 32 require.Equal(t, records[i], retrievedRec) 33 } 34 } 35 36 // write log records for multiple channels 37 for i := 0; i < 10; i++ { 38 logger := provider.newRedoLogger(fmt.Sprintf("channel-%d", i)) 39 rec, err := logger.load() 40 require.NoError(t, err) 41 require.Nil(t, rec) 42 loggers = append(loggers, logger) 43 batch := statedb.NewUpdateBatch() 44 blkNum := uint64(i) 45 batch.Put("ns1", "key1", []byte("value1"), version.NewHeight(blkNum, 1)) 46 batch.Put("ns2", string([]byte{0x00, 0xff}), []byte("value3"), version.NewHeight(blkNum, 3)) 47 batch.PutValAndMetadata("ns2", string([]byte{0x00, 0xff}), []byte("value3"), []byte("metadata"), version.NewHeight(blkNum, 4)) 48 batch.Delete("ns2", string([]byte{0xff, 0xff}), version.NewHeight(blkNum, 5)) 49 rec = &redoRecord{ 50 UpdateBatch: batch, 51 Version: version.NewHeight(blkNum, 10), 52 } 53 records = append(records, rec) 54 require.NoError(t, logger.persist(rec)) 55 } 56 57 verifyLogRecords() 58 // overwrite logrecord for one channel 59 records[5].UpdateBatch = statedb.NewUpdateBatch() 60 records[5].Version = version.NewHeight(5, 5) 61 require.NoError(t, loggers[5].persist(records[5])) 62 verifyLogRecords() 63 } 64 65 func TestCouchdbRedoLogger(t *testing.T) { 66 vdbEnv.init(t, nil) 67 defer vdbEnv.cleanup() 68 69 // commitToRedologAndRestart - a helper function that commits directly to redologs and restart the statedb 70 commitToRedologAndRestart := func(newVal string, version *version.Height) { 71 batch := statedb.NewUpdateBatch() 72 batch.Put("ns1", "key1", []byte(newVal), version) 73 db, err := vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 74 require.NoError(t, err) 75 vdb := db.(*VersionedDB) 76 require.NoError(t, 77 vdb.redoLogger.persist( 78 &redoRecord{ 79 UpdateBatch: batch, 80 Version: version, 81 }, 82 ), 83 ) 84 vdbEnv.closeAndReopen() 85 } 86 // verifyExpectedVal - a helper function that verifies the statedb contents 87 verifyExpectedVal := func(expectedVal string, expectedSavepoint *version.Height) { 88 db, err := vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 89 require.NoError(t, err) 90 vdb := db.(*VersionedDB) 91 vv, err := vdb.GetState("ns1", "key1") 92 require.NoError(t, err) 93 require.Equal(t, expectedVal, string(vv.Value)) 94 savepoint, err := vdb.GetLatestSavePoint() 95 require.NoError(t, err) 96 require.Equal(t, expectedSavepoint, savepoint) 97 } 98 99 // initialize statedb with initial set of writes 100 db, err := vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 101 if err != nil { 102 t.Fatalf("Failed to get database handle: %s", err) 103 } 104 vdb := db.(*VersionedDB) 105 batch1 := statedb.NewUpdateBatch() 106 batch1.Put("ns1", "key1", []byte("value1"), version.NewHeight(1, 1)) 107 require.NoError(t, vdb.ApplyUpdates(batch1, version.NewHeight(1, 1))) 108 109 // make redolog one block ahead than statedb - upon restart the redolog should get applied 110 commitToRedologAndRestart("value2", version.NewHeight(2, 1)) 111 verifyExpectedVal("value2", version.NewHeight(2, 1)) 112 113 // make redolog two blocks ahead than statedb - upon restart the redolog should be ignored 114 commitToRedologAndRestart("value3", version.NewHeight(4, 1)) 115 verifyExpectedVal("value2", version.NewHeight(2, 1)) 116 117 // make redolog one block behind than statedb - upon restart the redolog should be ignored 118 commitToRedologAndRestart("value3", version.NewHeight(1, 5)) 119 verifyExpectedVal("value2", version.NewHeight(2, 1)) 120 121 // A nil height should cause skipping the writing of redo-record 122 db, _ = vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 123 vdb = db.(*VersionedDB) 124 require.NoError(t, vdb.ApplyUpdates(batch1, nil)) 125 record, err := vdb.redoLogger.load() 126 require.NoError(t, err) 127 require.Equal(t, version.NewHeight(1, 5), record.Version) 128 require.Equal(t, []byte("value3"), record.UpdateBatch.Get("ns1", "key1").Value) 129 130 // A batch that does not contain PostOrderWrites should cause skipping the writing of redo-record 131 db, _ = vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 132 vdb = db.(*VersionedDB) 133 batchWithNoGeneratedWrites := batch1 134 batchWithNoGeneratedWrites.ContainsPostOrderWrites = false 135 require.NoError(t, vdb.ApplyUpdates(batchWithNoGeneratedWrites, version.NewHeight(2, 5))) 136 record, err = vdb.redoLogger.load() 137 require.NoError(t, err) 138 require.Equal(t, version.NewHeight(1, 5), record.Version) 139 require.Equal(t, []byte("value3"), record.UpdateBatch.Get("ns1", "key1").Value) 140 141 // A batch that contains PostOrderWrites should cause writing of redo-record 142 db, _ = vdbEnv.DBProvider.GetDBHandle("testcouchdbredologger", nil) 143 vdb = db.(*VersionedDB) 144 batchWithGeneratedWrites := batch1 145 batchWithGeneratedWrites.ContainsPostOrderWrites = true 146 require.NoError(t, vdb.ApplyUpdates(batchWithNoGeneratedWrites, version.NewHeight(3, 4))) 147 record, err = vdb.redoLogger.load() 148 require.NoError(t, err) 149 require.Equal(t, version.NewHeight(3, 4), record.Version) 150 require.Equal(t, []byte("value1"), record.UpdateBatch.Get("ns1", "key1").Value) 151 } 152 153 func redologTestSetup(t *testing.T) (p *redoLoggerProvider, cleanup func()) { 154 dbPath, err := ioutil.TempDir("", "redolog") 155 if err != nil { 156 t.Fatalf("Failed to create redo log directory: %s", err) 157 } 158 p, err = newRedoLoggerProvider(dbPath) 159 require.NoError(t, err) 160 cleanup = func() { 161 p.close() 162 require.NoError(t, os.RemoveAll(dbPath)) 163 } 164 return 165 } 166 167 func TestReadExistingRedoRecord(t *testing.T) { 168 b, err := ioutil.ReadFile("testdata/persisted_redo_record") 169 require.NoError(t, err) 170 rec, err := decodeRedologVal(b) 171 require.NoError(t, err) 172 t.Logf("rec = %s", spew.Sdump(rec)) 173 require.Equal(t, constructSampleRedoRecord(), rec) 174 } 175 176 func constructSampleRedoRecord() *redoRecord { 177 batch := statedb.NewUpdateBatch() 178 batch.Put("ns1", "key1", []byte("value1"), version.NewHeight(1, 1)) 179 batch.Put("ns2", string([]byte{0x00, 0xff}), []byte("value3"), version.NewHeight(3, 3)) 180 batch.PutValAndMetadata("ns2", string([]byte{0x00, 0xff}), []byte("value3"), []byte("metadata"), version.NewHeight(4, 4)) 181 batch.Delete("ns2", string([]byte{0xff, 0xff}), version.NewHeight(5, 5)) 182 return &redoRecord{ 183 UpdateBatch: batch, 184 Version: version.NewHeight(10, 10), 185 } 186 }