github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/service/consumer_integration_test.go (about) 1 // +build integration 2 3 package service_test 4 5 import ( 6 "fmt" 7 "math/rand" 8 "path" 9 "runtime" 10 "strconv" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/hyperledger/burrow/vent/chain/ethereum" 16 17 "github.com/hyperledger/burrow/crypto" 18 "github.com/hyperledger/burrow/execution/exec" 19 "github.com/hyperledger/burrow/logging/logconfig" 20 "github.com/hyperledger/burrow/rpc/rpctransact" 21 "github.com/hyperledger/burrow/vent/config" 22 "github.com/hyperledger/burrow/vent/service" 23 "github.com/hyperledger/burrow/vent/sqldb" 24 "github.com/hyperledger/burrow/vent/sqlsol" 25 "github.com/hyperledger/burrow/vent/test" 26 "github.com/hyperledger/burrow/vent/types" 27 "github.com/stretchr/testify/assert" 28 29 "github.com/stretchr/testify/require" 30 ) 31 32 const ( 33 testViewSpec = "sqlsol_view.json" 34 testLogSpec = "sqlsol_log.json" 35 ) 36 37 var tables = types.DefaultSQLTableNames 38 39 // Tweak logger for debug purposes here 40 var logger = logconfig.Sink().Terminal().FilterScope(ethereum.Scope).LoggingConfig().WithTrace().MustLogger() 41 42 func testConsumer(t *testing.T, chainID string, cfg *config.VentConfig, tcli test.TransactClient, 43 inputAddress crypto.Address) { 44 45 create := test.CreateContract(t, tcli, inputAddress) 46 eventTestTableName := "EventTest" 47 require.True(t, create.Receipt.CreatesContract) 48 cfg.WatchAddresses = []crypto.Address{create.Receipt.ContractAddress} 49 50 // TODO: strengthen this test to count events in and out 51 t.Run("view mode", func(t *testing.T) { 52 // create test db 53 db, closeDB := test.NewTestDB(t, cfg) 54 defer closeDB() 55 resolveSpec(cfg, testViewSpec) 56 57 // generate events 58 name := "TestEvent1" 59 description := "Description of TestEvent1" 60 txeA := test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 61 62 name = "TestEvent2" 63 description = "Description of TestEvent2" 64 test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 65 66 name = "TestEvent3" 67 description = "Description of TestEvent3" 68 test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 69 70 name = "TestEvent4" 71 description = "Description of TestEvent4" 72 txeB := test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 73 74 // Run the consumer 75 runConsumer(t, cfg) 76 77 // test data stored in database for two different block heights 78 ensureEvents(t, db, chainID, eventTestTableName, txeA.Height, 1) 79 eventData := ensureEvents(t, db, chainID, eventTestTableName, txeB.Height, 1) 80 81 // block & tx raw data also persisted 82 if cfg.SpecOpt&sqlsol.Block > 0 { 83 tblData := eventData.Tables[tables.Block] 84 require.Equal(t, 1, len(tblData)) 85 86 } 87 if cfg.SpecOpt&sqlsol.Tx > 0 { 88 tblData := eventData.Tables[tables.Tx] 89 require.Equal(t, 1, len(tblData)) 90 require.Equal(t, txeB.TxHash.String(), tblData[0].RowData["_txhash"].(string)) 91 } 92 93 // Restore 94 err := db.RestoreDB(time.Time{}, "RESTORED") 95 require.NoError(t, err) 96 }) 97 98 t.Run("log mode", func(t *testing.T) { 99 db, closeDB := test.NewTestDB(t, cfg) 100 defer closeDB() 101 resolveSpec(cfg, testLogSpec) 102 103 name := "TestEvent5" 104 description := "Description of TestEvent5" 105 txeC := test.CallAddEvents(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 106 runConsumer(t, cfg) 107 ensureEvents(t, db, chainID, eventTestTableName, txeC.Height, 2) 108 }) 109 110 t.Run("continuity", func(t *testing.T) { 111 batches := 20 112 batchSize := 5 113 totalTx := batches * batchSize 114 db, closeDB := test.NewTestDB(t, cfg) 115 defer closeDB() 116 resolveSpec(cfg, testLogSpec) 117 118 receipts := make(chan *exec.TxExecution, totalTx) 119 wg := new(sync.WaitGroup) 120 wg.Add(totalTx) 121 122 for i := 0; i < batches; i++ { 123 for j := 0; j < batchSize; j++ { 124 name := fmt.Sprintf("Continuity_%d_%d", i, j) 125 go func() { 126 receipts <- test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, "Blah") 127 wg.Done() 128 }() 129 } 130 } 131 132 wg.Wait() 133 close(receipts) 134 runConsumer(t, cfg) 135 136 txeByHeight := make(map[uint64][]*exec.TxExecution) 137 for txe := range receipts { 138 txeByHeight[txe.Height] = append(txeByHeight[txe.Height], txe) 139 } 140 141 for height, txes := range txeByHeight { 142 ensureEvents(t, db, chainID, eventTestTableName, height, uint64(len(txes))) 143 } 144 145 }) 146 147 } 148 149 func testDeleteEvent(t *testing.T, chainID string, cfg *config.VentConfig, tcli rpctransact.TransactClient, inputAddress crypto.Address) { 150 create := test.CreateContract(t, tcli, inputAddress) 151 152 eventColumnName := "EventTest" 153 name := "TestEventForDeletion" 154 description := "to be deleted" 155 156 // test data stored in database for two different block ids 157 158 // create test db 159 db, closeDB := test.NewTestDB(t, cfg) 160 defer closeDB() 161 resolveSpec(cfg, testViewSpec) 162 163 // Add a test event 164 txeAdd := test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 165 166 // Spin the consumer 167 runConsumer(t, cfg) 168 169 // Expect block table, tx table, and EventTest table 170 ensureEvents(t, db, chainID, eventColumnName, txeAdd.Height, 1) 171 172 // Now emit a deletion event for that table 173 test.CallRemoveEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name) 174 runConsumer(t, cfg) 175 ensureEvents(t, db, chainID, eventColumnName, txeAdd.Height, 0) 176 177 // delete not allowed on log mode 178 } 179 180 func ensureEvents(t *testing.T, db *sqldb.SQLDB, chainID, table string, height, numEvents uint64) types.EventData { 181 eventData, err := db.GetBlock(chainID, height) 182 require.NoError(t, err) 183 require.Equal(t, height, eventData.BlockHeight) 184 require.Equal(t, 3, len(eventData.Tables)) 185 186 // Check the number of rows 187 tblData := eventData.Tables[table] 188 if !assert.Equal(t, numEvents, uint64(len(tblData))) { 189 t.Fatal(logconfig.JSONString(tblData)) 190 } 191 192 if numEvents > 0 && len(tblData) > 0 { 193 // Expect data in the EventTest table 194 require.Equal(t, "LogEvent", tblData[0].RowData["_eventtype"].(string)) 195 require.Equal(t, "UpdateTestEvents", tblData[0].RowData["_eventname"].(string)) 196 lastTxIndex := "" 197 var eventIndex uint64 198 for _, datum := range tblData { 199 txIndex := datum.RowData["_txindex"].(string) 200 if lastTxIndex != txIndex { 201 eventIndex = 0 202 lastTxIndex = txIndex 203 } 204 205 good := rowEqual(t, datum.RowData, "_height", height) && 206 rowEqual(t, datum.RowData, "_eventindex", eventIndex) 207 if !good { 208 t.Fatal(logconfig.JSONString(tblData)) 209 } 210 211 assert.Equal(t, fmt.Sprintf("%d", height), datum.RowData["_height"]) 212 eventIndex++ 213 } 214 } else if numEvents > 0 && len(tblData) == 0 { 215 require.Failf(t, "no events found", "expected %d", numEvents) 216 } 217 218 return eventData 219 } 220 221 func rowEqual(t *testing.T, row map[string]interface{}, key string, expectedIndex uint64) bool { 222 return assert.Equal(t, strconv.FormatUint(expectedIndex, 10), row[key].(string)) 223 } 224 225 func testResume(t *testing.T, cfg *config.VentConfig) { 226 _, closeDB := test.NewTestDB(t, cfg) 227 defer closeDB() 228 resolveSpec(cfg, testViewSpec) 229 230 numRestarts := 6 231 // Add some pseudo-random timings 232 rnd := rand.New(rand.NewSource(4634653)) 233 time.Sleep(time.Second) 234 for i := 0; i < numRestarts; i++ { 235 // wait up to a second 236 time.Sleep(time.Millisecond * time.Duration(rnd.Int63n(1000))) 237 for ed := range runConsumer(t, cfg) { 238 t.Logf("testResume, got block: %d", ed.BlockHeight) 239 } 240 } 241 } 242 243 func testInvalidUTF8(t *testing.T, cfg *config.VentConfig, tcli rpctransact.TransactClient, inputAddress crypto.Address) { 244 create := test.CreateContract(t, tcli, inputAddress) 245 246 // The code point for ó is less than 255 but needs two unicode bytes - it's value expressed as a single byte 247 // is in the private use area so is invalid. 248 goodString := "Cliente - Doc. identificación" 249 250 // generate events 251 name := service.BadStringToHexFunction(goodString) 252 description := "Description of TestEvent1" 253 test.CallAddEvent(t, tcli, inputAddress, create.Receipt.ContractAddress, name, description) 254 255 // create test db 256 _, closeDB := test.NewTestDB(t, cfg) 257 defer closeDB() 258 resolveSpec(cfg, testViewSpec) 259 260 // Run the consumer with this event - this used to create an error on UPSERT 261 runConsumer(t, cfg) 262 263 // Error we used to get before fixing this test case: 264 //require.Error(t, err) 265 //require.Contains(t, err.Error(), "pq: invalid byte sequence for encoding \"UTF8\": 0xf3 0x6e") 266 } 267 268 func resolveSpec(cfg *config.VentConfig, specFile string) { 269 // Resolve relative path to test dir 270 _, testFile, _, _ := runtime.Caller(0) 271 testDir := path.Join(path.Dir(testFile), "..", "test") 272 273 cfg.SpecFileOrDirs = []string{path.Join(testDir, specFile)} 274 cfg.AbiFileOrDirs = []string{path.Join(testDir, "EventsTest.abi")} 275 cfg.SpecOpt = sqlsol.BlockTx 276 } 277 278 // Run consumer to listen to events 279 func runConsumer(t *testing.T, cfg *config.VentConfig) chan types.EventData { 280 ch := make(chan types.EventData, 100) 281 consumer := service.NewConsumer(cfg, logger, ch) 282 283 projection, err := sqlsol.SpecLoader(cfg.SpecFileOrDirs, cfg.SpecOpt) 284 require.NoError(t, err) 285 286 err = consumer.Run(projection, false) 287 require.NoError(t, err) 288 return consumer.EventsChannel 289 }