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  }