github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/integration/rpcevents/execution_events_server_test.go (about)

     1  // +build integration
     2  
     3  // Space above here matters
     4  // Copyright Monax Industries Limited
     5  // SPDX-License-Identifier: Apache-2.0
     6  
     7  package rpcevents
     8  
     9  import (
    10  	"context"
    11  	"io"
    12  	"strconv"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/hyperledger/burrow/core"
    18  	"github.com/hyperledger/burrow/crypto"
    19  	"github.com/hyperledger/burrow/event"
    20  	"github.com/hyperledger/burrow/event/query"
    21  	"github.com/hyperledger/burrow/execution/errors"
    22  	"github.com/hyperledger/burrow/execution/evm/abi"
    23  	"github.com/hyperledger/burrow/execution/exec"
    24  	"github.com/hyperledger/burrow/execution/solidity"
    25  	"github.com/hyperledger/burrow/integration"
    26  	"github.com/hyperledger/burrow/integration/rpctest"
    27  	"github.com/hyperledger/burrow/rpc/rpcevents"
    28  	"github.com/hyperledger/burrow/rpc/rpctransact"
    29  	"github.com/hyperledger/burrow/txs/payload"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestExecutionEventsTest(t *testing.T) {
    35  	kern, shutdown := integration.RunNode(t, rpctest.GenesisDoc, rpctest.PrivateAccounts)
    36  	defer shutdown()
    37  	tcli := rpctest.NewTransactClient(t, kern.GRPCListenAddress().String())
    38  	ecli := rpctest.NewExecutionEventsClient(t, kern.GRPCListenAddress().String())
    39  	inputAddress0 := rpctest.PrivateAccounts[0].GetAddress()
    40  	inputAddress1 := rpctest.PrivateAccounts[1].GetAddress()
    41  
    42  	t.Run("Group", func(t *testing.T) {
    43  		t.Run("StreamDB", func(t *testing.T) {
    44  			numSends := 4
    45  			request := &rpcevents.BlocksRequest{
    46  				BlockRange: doSends(t, numSends, tcli, kern, inputAddress1, 2004),
    47  			}
    48  			var blocks []*exec.BlockExecution
    49  
    50  			stream, err := ecli.Stream(context.Background(), request)
    51  			require.NoError(t, err)
    52  
    53  			err = rpcevents.ConsumeBlockExecutions(stream, func(be *exec.BlockExecution) error {
    54  				blocks = append(blocks, be)
    55  				return nil
    56  			})
    57  			require.Equal(t, io.EOF, err)
    58  
    59  			require.True(t, len(blocks) > 0, "should see at least one block")
    60  			var height uint64
    61  			for _, b := range blocks {
    62  				if height > 0 {
    63  					assert.Equal(t, height+1, b.Height)
    64  				}
    65  				for range b.TxExecutions {
    66  					numSends--
    67  				}
    68  				height = b.Height
    69  			}
    70  			require.Equal(t, 0, numSends, "all transactions should be observed")
    71  			require.NoError(t, stream.CloseSend())
    72  		})
    73  
    74  		t.Run("Stream_streaming", func(t *testing.T) {
    75  			request := &rpcevents.BlocksRequest{
    76  				BlockRange: rpcevents.NewBlockRange(rpcevents.AbsoluteBound(0), rpcevents.StreamBound()),
    77  			}
    78  			stream, err := ecli.Stream(context.Background(), request)
    79  			require.NoError(t, err)
    80  			batches := 3
    81  			sendsPerBatch := 4
    82  			total := batches * sendsPerBatch
    83  			doneCh := make(chan []struct{})
    84  			go func() {
    85  				for i := 0; i < batches; i++ {
    86  					doSends(t, sendsPerBatch, tcli, kern, inputAddress0, 2004)
    87  				}
    88  				close(doneCh)
    89  			}()
    90  
    91  			err = rpcevents.ConsumeBlockExecutions(stream, func(be *exec.BlockExecution) error {
    92  				for range be.TxExecutions {
    93  					total--
    94  				}
    95  				if total == 0 {
    96  					return io.EOF
    97  				}
    98  				return nil
    99  			})
   100  			require.Equal(t, io.EOF, err)
   101  			assert.Equal(t, 0, total)
   102  			require.NoError(t, stream.CloseSend())
   103  			<-doneCh
   104  		})
   105  
   106  		t.Run("StreamContains2", func(t *testing.T) {
   107  			request := &rpcevents.BlocksRequest{
   108  				BlockRange: rpcevents.AbsoluteRange(0, 12),
   109  				Query:      "Height CONTAINS '2'",
   110  			}
   111  			stream, err := ecli.Stream(context.Background(), request)
   112  			require.NoError(t, err)
   113  			numSends := 4
   114  			var blocks []*exec.BlockExecution
   115  			require.NoError(t, err)
   116  			doSends(t, numSends, tcli, kern, inputAddress1, 1992)
   117  			require.NoError(t, err)
   118  			err = rpcevents.ConsumeBlockExecutions(stream, func(be *exec.BlockExecution) error {
   119  				blocks = append(blocks, be)
   120  				assert.Contains(t, strconv.FormatUint(be.Height, 10), "2")
   121  				return nil
   122  			}, exec.NonConsecutiveBlocks|exec.NonConsecutiveTxs)
   123  			require.Equal(t, io.EOF, err)
   124  			require.Len(t, blocks, 2, "should record blocks 2 and 12")
   125  			assert.Equal(t, uint64(2), blocks[0].Height)
   126  			assert.Equal(t, uint64(12), blocks[1].Height)
   127  
   128  			require.NoError(t, stream.CloseSend())
   129  		})
   130  
   131  		t.Run("GetEventsSend", func(t *testing.T) {
   132  			numSends := 1100
   133  			request := &rpcevents.BlocksRequest{BlockRange: doSends(t, numSends, tcli, kern, inputAddress0, 2004)}
   134  			responses, err := getEvents(t, request, ecli)
   135  			require.NoError(t, err)
   136  			assert.Equal(t, numSends*2, countEventsAndCheckConsecutive(t, responses),
   137  				"should receive 1 input, 1 output per send")
   138  		})
   139  
   140  		t.Run("GetEventsSendContainsAA", func(t *testing.T) {
   141  			numSends := 1100
   142  			request := &rpcevents.BlocksRequest{
   143  				BlockRange: doSends(t, numSends, tcli, kern, inputAddress1, 2004),
   144  				Query:      "TxHash CONTAINS 'AA'",
   145  			}
   146  			responses, err := getEvents(t, request, ecli)
   147  			require.NoError(t, err)
   148  			for _, response := range responses {
   149  				for _, ev := range response.Events {
   150  					require.Contains(t, ev.Header.TxHash.String(), "AA")
   151  				}
   152  			}
   153  		})
   154  
   155  		t.Run("GetEventsSendFiltered", func(t *testing.T) {
   156  			numSends := 500
   157  			request := &rpcevents.BlocksRequest{
   158  				BlockRange: doSends(t, numSends, tcli, kern, inputAddress0, 999),
   159  				Query: query.NewBuilder().AndEquals("Input.Address", inputAddress0.String()).
   160  					AndEquals(event.EventTypeKey, exec.TypeAccountInput.String()).String(),
   161  			}
   162  			responses, err := getEvents(t, request, ecli)
   163  			require.NoError(t, err)
   164  			assert.Equal(t, numSends, countEventsAndCheckConsecutive(t, responses), "should receive every single input event per send")
   165  		})
   166  
   167  		t.Run("Revert", func(t *testing.T) {
   168  			txe, err := rpctest.CreateEVMContract(tcli, inputAddress0, solidity.Bytecode_Revert, nil)
   169  			require.NoError(t, err)
   170  			spec, err := abi.ReadSpec(solidity.Abi_Revert)
   171  			require.NoError(t, err)
   172  			data, _, err := spec.Pack("RevertAt", 4)
   173  			require.NoError(t, err)
   174  			contractAddress := txe.Receipt.ContractAddress
   175  			txe, err = rpctest.CallContract(tcli, inputAddress0, contractAddress, data)
   176  			require.NoError(t, err)
   177  			assert.Equal(t, errors.Codes.ExecutionReverted, errors.GetCode(txe.Exception))
   178  			assert.Contains(t, txe.Exception.Error(), "I have reverted")
   179  
   180  			request := &rpcevents.BlocksRequest{
   181  				BlockRange: rpcevents.NewBlockRange(rpcevents.AbsoluteBound(0), rpcevents.LatestBound()),
   182  				Query: query.Must(query.NewBuilder().AndEquals(event.EventIDKey, exec.EventStringLogEvent(contractAddress)).
   183  					AndEquals(event.TxHashKey, txe.TxHash).Query()).String(),
   184  			}
   185  			evs, err := getEvents(t, request, ecli)
   186  			require.NoError(t, err)
   187  			n := countEventsAndCheckConsecutive(t, evs)
   188  			assert.Equal(t, 0, n, "should not see reverted events")
   189  		})
   190  
   191  		// This test triggered a bug when using 'latest' as the end bound and where the latest block is an empty block
   192  		// leading to streaming until another block is emitted causing clients to hang around much longer than they
   193  		// should
   194  		t.Run("GetEventsWithLatestBlockEmpty", func(t *testing.T) {
   195  			numSends := 5
   196  			blockRange := doSends(t, numSends, tcli, kern, inputAddress0, 999)
   197  			request := &rpcevents.BlocksRequest{
   198  				BlockRange: &rpcevents.BlockRange{
   199  					Start: blockRange.Start,
   200  					End:   rpcevents.LatestBound(),
   201  				},
   202  			}
   203  
   204  			n := 400
   205  			wait := time.Millisecond
   206  			before := time.Now()
   207  			for i := 0; i < n; i++ {
   208  				time.Sleep(wait)
   209  				responses, err := getEvents(t, request, ecli)
   210  				require.NoError(t, err)
   211  				assert.Equal(t, 2*numSends, countEventsAndCheckConsecutive(t, responses), "should receive every single input event per send")
   212  			}
   213  			elapsed := time.Now().Sub(before)
   214  			// This should complete very quickly unless it accidentally starts streaming in which case it will keep
   215  			// waiting for an empty block at each iteration after the bug is triggered
   216  			require.Less(t, elapsed, time.Duration(n)*wait*10)
   217  		})
   218  	})
   219  }
   220  
   221  func getEvents(t *testing.T, request *rpcevents.BlocksRequest, ecli rpcevents.ExecutionEventsClient) ([]*rpcevents.EventsResponse, error) {
   222  	evs, err := ecli.Events(context.Background(), request)
   223  	require.NoError(t, err)
   224  	var responses []*rpcevents.EventsResponse
   225  	for {
   226  		resp, err := evs.Recv()
   227  		if err != nil {
   228  			if err == io.EOF {
   229  				break
   230  			}
   231  			return nil, err
   232  		}
   233  		responses = append(responses, resp)
   234  	}
   235  	return responses, nil
   236  }
   237  
   238  func doSends(t *testing.T, numSends int, cli rpctransact.TransactClient, kern *core.Kernel, inputAddress crypto.Address,
   239  	amt uint64) *rpcevents.BlockRange {
   240  	expecter := rpctest.ExpectTxs(kern.Emitter, "doSends")
   241  	wg := new(sync.WaitGroup)
   242  	for i := 0; i < numSends; i++ {
   243  		wg.Add(1)
   244  		// Slow us down a bit to ensure spread across blocks
   245  		time.Sleep(time.Millisecond)
   246  		receipt, err := cli.SendTxAsync(context.Background(),
   247  			&payload.SendTx{
   248  				Inputs: []*payload.TxInput{{
   249  					Address: inputAddress,
   250  					Amount:  amt,
   251  				}},
   252  				Outputs: []*payload.TxOutput{{
   253  					Address: rpctest.PrivateAccounts[4].GetAddress(),
   254  					Amount:  amt,
   255  				}},
   256  			})
   257  		require.NoError(t, err)
   258  		expecter.Expect(receipt.TxHash)
   259  		assert.False(t, receipt.CreatesContract)
   260  		wg.Done()
   261  	}
   262  	wg.Wait()
   263  	return expecter.AssertCommitted(t)
   264  }
   265  
   266  func countEventsAndCheckConsecutive(t *testing.T, responses []*rpcevents.EventsResponse) int {
   267  	i := 0
   268  	var height uint64
   269  	var index uint64
   270  	txHash := ""
   271  	for _, resp := range responses {
   272  		require.True(t, resp.Height > height, "must not receive multiple GetEventsResponses for the same block")
   273  		height := resp.Height
   274  		for _, ev := range resp.Events {
   275  			require.Equal(t, ev.Header.Height, height, "height of event headers much match height of GetEventsResponse")
   276  			if txHash != ev.Header.TxHash.String() {
   277  				txHash = ev.Header.TxHash.String()
   278  				index = 0
   279  			}
   280  			if ev.Header.Index > index {
   281  				require.Equal(t, index+1, ev.Header.Index)
   282  				index++
   283  			}
   284  			i++
   285  		}
   286  	}
   287  	return i
   288  }