Repository: onflow/flow-cli (go)

Issue:
Fetch multiple events for large block ranges using goroutines/channels
## Instructions

I have some code in go-with-the-flow https://github.com/bjartek/go-with-the-flow/blob/master/gwtf/event.go#L247 that I am pondering changing and submitting to this cli project. The idea is that it should be possible to specify several events and a large block size and partition the rangeQueries and run them in paralell. 


### Issue To Be Solved

Fetching two or more events for a large block size is currently not possible. 


### (Optional): Suggest A Solution

Include the go-with-the-flow channel/go-routine code into flow cli and exapand on the `flow events get` methods or possible introduct a `flow events get-many` command



Pull request:
Fetch many events
Closes #316 

This is not done, it is just a copy of some code and cleaning it up a little. I do not want to invest more time in this if this is something that you feel is not needed here. 

Personally I think this gives a lot of value.

______

For contributor use:

- [x] Targeted PR against `master` branch
- [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [x] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels


Patch:
diff --git a/go.mod b/go.mod
index c1dd19bc..d32893bb 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ require (
 	github.com/spf13/cobra v1.1.3
 	github.com/stretchr/testify v1.7.0
 	github.com/thoas/go-funk v0.7.0
+	golang.org/x/tools v0.1.4 // indirect
 	gonum.org/v1/gonum v0.6.1
 	google.golang.org/grpc v1.37.0
 )
diff --git a/go.sum b/go.sum
index 0eeb6fde..45090ad3 100644
--- a/go.sum
+++ b/go.sum
@@ -1086,6 +1086,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@@ -1190,6 +1191,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1236,6 +1239,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
 golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1325,8 +1329,10 @@ golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -1404,6 +1410,8 @@ golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20201020161133-226fd2f889ca/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/events/get.go b/internal/events/get.go
index 68e9adf5..872fc0ec 100644
--- a/internal/events/get.go
+++ b/internal/events/get.go
@@ -19,6 +19,8 @@
 package events
 
 import (
+	"fmt"
+
 	"github.com/spf13/cobra"
 
 	"github.com/onflow/flow-cli/internal/command"
@@ -26,18 +28,35 @@ import (
 	"github.com/onflow/flow-cli/pkg/flowkit/services"
 )
 
-type flagsGenerate struct{}
+type flagsEvents struct {
+	Start   uint64 `flag:"start" info:"Start block height"`
+	End     uint64 `flag:"end" info:"End block height"`
+	Last    uint64 `default:"10" flag:"last" info:"Fetch number of blocks relative to the last block. Ignored if the start flag is set. Used as a default if no flags are provided"`
+	Workers int    `default:"10" flag:"workers" info:"Number of workers to use when fetching events in parallel"`
+	Batch   uint64 `default:"250" flag:"batch" info:"Number of blocks each worker will fetch"`
+}
 
-var generateFlag = flagsGenerate{}
+var eventsFlags = flagsEvents{}
 
 var GetCommand = &command.Command{
 	Cmd: &cobra.Command{
-		Use:     "get <event_name> <block_height_range_start> <optional:block_height_range_end|latest>",
-		Short:   "Get events in a block range",
-		Args:    cobra.RangeArgs(2, 3),
-		Example: "flow events get A.1654653399040a61.FlowToken.TokensDeposited 11559500 11559600",
+		Use:   "get <event_name>",
+		Short: "Get events in a block range",
+		Args:  cobra.MinimumNArgs(1),
+		Example: `#fetch events from the latest 10 blocks is the default behavior
+flow events get A.1654653399040a61.FlowToken.TokensDeposited
+
+#specify manual start and stop blocks
+flow events get A.1654653399040a61.FlowToken.TokensDeposited --start 11559500 --end 11559600
+
+#in order to get and event from the 20 latest blocks on a network run
+flow events get A.1654653399040a61.FlowToken.TokensDeposited --last 20 --network mainnet
+
+#if you want to fetch multiple event types that is done by sending in more events. Even fetching will be done in parallel.
+flow events get A.1654653399040a61.FlowToken.TokensDeposited A.1654653399040a61.FlowToken.TokensWithdrawn
+	`,
 	},
-	Flags: &generateFlag,
+	Flags: &eventsFlags,
 	Run:   get,
 }
 
@@ -47,15 +66,26 @@ func get(
 	_ command.GlobalFlags,
 	services *services.Services,
 ) (command.Result, error) {
-	name := args[0]
-	start := args[1] // block height range start
-	end := ""        // block height range end
 
-	if len(args) == 3 {
-		end = args[2]
+	var err error
+	start := eventsFlags.Start
+	end := eventsFlags.End
+	last := eventsFlags.Last
+
+	// handle if not passing start and end
+	if start == 0 && end == 0 {
+		//cannot use := here as it will not overrwrite end in the outer scope if you do
+		end, err = services.Blocks.GetLatestBlockHeight()
+		if err != nil {
+			return nil, err
+		}
+
+		start = end - last
+	} else if start == 0 || end == 0 {
+		return nil, fmt.Errorf("please provide either both start and end for range or only last flag")
 	}
 
-	events, err := services.Events.Get(name, start, end)
+	events, err := services.Events.Get(args, start, end, eventsFlags.Batch, eventsFlags.Workers)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/flowkit/services/blocks.go b/pkg/flowkit/services/blocks.go
index dcc70cac..f745dc7c 100644
--- a/pkg/flowkit/services/blocks.go
+++ b/pkg/flowkit/services/blocks.go
@@ -111,3 +111,12 @@ func (e *Blocks) GetBlock(
 
 	return block, events, collections, err
 }
+
+// GetLatestBlockHeight returns the latest block height
+func (e *Blocks) GetLatestBlockHeight() (uint64, error) {
+	block, err := e.gateway.GetLatestBlock()
+	if err != nil {
+		return 0, err
+	}
+	return block.Height, nil
+}
diff --git a/pkg/flowkit/services/events.go b/pkg/flowkit/services/events.go
index c6e1986a..4d28494d 100644
--- a/pkg/flowkit/services/events.go
+++ b/pkg/flowkit/services/events.go
@@ -20,7 +20,7 @@ package services
 
 import (
 	"fmt"
-	"strconv"
+	"sync"
 
 	"github.com/onflow/flow-cli/pkg/flowkit"
 
@@ -50,46 +50,88 @@ func NewEvents(
 	}
 }
 
-// Get queries for an event by name and block range.
-func (e *Events) Get(name string, start string, end string) ([]client.BlockEvents, error) {
-	if name == "" {
-		return nil, fmt.Errorf("cannot use empty string as event name")
+func makeEventQueries(events []string, startHeight uint64, endHeight uint64, blockCount uint64) []client.EventRangeQuery {
+	var queries []client.EventRangeQuery
+	for startHeight <= endHeight {
+		suggestedEndHeight := startHeight + blockCount - 1 //since we are inclusive
+		end := endHeight
+		if suggestedEndHeight < endHeight {
+			end = suggestedEndHeight
+		}
+		for _, event := range events {
+			queries = append(queries, client.EventRangeQuery{
+				Type:        event,
+				StartHeight: startHeight,
+				EndHeight:   end,
+			})
+		}
+		startHeight = suggestedEndHeight + 1
 	}
+	return queries
 
-	startHeight, err := strconv.ParseUint(start, 10, 64)
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse start height of block range: %v", start)
+}
+func (e *Events) Get(events []string, startHeight uint64, endHeight uint64, blockCount uint64, workerCount int) ([]client.BlockEvents, error) {
+	if endHeight < startHeight {
+		return nil, fmt.Errorf("cannot have end height (%d) of block range less that start height (%d)", endHeight, startHeight)
 	}
 
-	e.logger.StartProgress("Fetching Events...")
+	e.logger.StartProgress("Fetching events...")
 	defer e.logger.StopProgress()
 
-	var endHeight uint64
-	if end == "" {
-		endHeight = startHeight
-	} else if end == "latest" {
-		latestBlock, err := e.gateway.GetLatestBlock()
-		if err != nil {
-			return nil, err
+	queries := makeEventQueries(events, startHeight, endHeight, blockCount)
+
+	jobChan := make(chan client.EventRangeQuery, workerCount)
+	results := make(chan EventWorkerResult)
+
+	var wg sync.WaitGroup
+
+	for i := 0; i < workerCount; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			e.eventWorker(jobChan, results)
+		}()
+	}
+
+	// wait on the workers to finish and close the result channel
+	// to signal downstream that all work is done
+	go func() {
+		defer close(results)
+		wg.Wait()
+	}()
+
+	go func() {
+		defer close(jobChan)
+		for _, query := range queries {
+			jobChan <- query
 		}
+	}()
 
-		endHeight = latestBlock.Height
-	} else {
-		endHeight, err = strconv.ParseUint(end, 10, 64)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse end height of block range: %s", end)
+	var resultEvents []client.BlockEvents
+	for eventResult := range results {
+		if eventResult.Error != nil {
+			return nil, eventResult.Error
 		}
-	}
 
-	if endHeight < startHeight {
-		return nil, fmt.Errorf("cannot have end height (%d) of block range less that start height (%d)", endHeight, startHeight)
+		resultEvents = append(resultEvents, eventResult.Events...)
 	}
 
-	events, err := e.gateway.GetEvents(name, startHeight, endHeight)
-	if err != nil {
-		return nil, err
+	return resultEvents, nil
+
+}
+
+func (e *Events) eventWorker(jobChan <-chan client.EventRangeQuery, results chan<- EventWorkerResult) {
+	for q := range jobChan {
+		//e.logger.Debug(fmt.Sprintf("Fetching events %v", q))
+		blockEvents, err := e.gateway.GetEvents(q.Type, q.StartHeight, q.EndHeight)
+		if err != nil {
+			results <- EventWorkerResult{nil, err}
+		}
+		results <- EventWorkerResult{blockEvents, nil}
 	}
+}
 
-	e.logger.StopProgress()
-	return events, nil
+type EventWorkerResult struct {
+	Events []client.BlockEvents
+	Error  error
 }


Test patch:
diff --git a/pkg/flowkit/services/blocks_test.go b/pkg/flowkit/services/blocks_test.go
index 0c3742b9..e84b6c4a 100644
--- a/pkg/flowkit/services/blocks_test.go
+++ b/pkg/flowkit/services/blocks_test.go
@@ -45,6 +45,16 @@ func TestBlocks(t *testing.T) {
 		assert.NoError(t, err)
 	})
 
+	t.Run("Get latest block height", func(t *testing.T) {
+		t.Parallel()
+		_, s, gw := setup()
+		height, err := s.Blocks.GetLatestBlockHeight()
+		gw.Mock.AssertCalled(t, tests.GetLatestBlockFunc)
+		assert.NoError(t, err)
+		assert.Equal(t, height, uint64(1))
+
+	})
+
 	t.Run("Get Block by Height", func(t *testing.T) {
 		t.Parallel()
 
diff --git a/pkg/flowkit/services/events_test.go b/pkg/flowkit/services/events_test.go
index 10d363db..0730d295 100644
--- a/pkg/flowkit/services/events_test.go
+++ b/pkg/flowkit/services/events_test.go
@@ -19,11 +19,14 @@
 package services
 
 import (
+	"errors"
 	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/onflow/flow-go-sdk/client"
+
 	"github.com/onflow/flow-cli/tests"
 )
 
@@ -34,74 +37,101 @@ func TestEvents(t *testing.T) {
 		t.Parallel()
 
 		_, s, gw := setup()
-		_, err := s.Events.Get("flow.CreateAccount", "0", "1")
+		_, err := s.Events.Get([]string{"flow.CreateAccount"}, 0, 0, 250, 1)
 
 		assert.NoError(t, err)
-		gw.Mock.AssertCalled(t, tests.GetEventsFunc, "flow.CreateAccount", uint64(0), uint64(1))
+		gw.Mock.AssertCalled(t, tests.GetEventsFunc, "flow.CreateAccount", uint64(0), uint64(0))
 	})
 
-	t.Run("Get Events Latest", func(t *testing.T) {
+	t.Run("Should have larger endHeight then startHeight", func(t *testing.T) {
 		t.Parallel()
 
-		_, s, gw := setup()
-		_, err := s.Events.Get("flow.CreateAccount", "0", "latest")
+		_, s, _ := setup()
+		_, err := s.Events.Get([]string{"flow.CreateAccount"}, 10, 0, 250, 1)
+		assert.EqualError(t, err, "cannot have end height (0) of block range less that start height (10)")
+	})
 
-		assert.NoError(t, err)
-		gw.Mock.AssertCalled(t, tests.GetLatestBlockFunc)
-		gw.Mock.AssertCalled(t, tests.GetEventsFunc, "flow.CreateAccount", uint64(0), uint64(1))
+	t.Run("Test create queries", func(t *testing.T) {
+
+		names := []string{"first", "second"}
+		queries := makeEventQueries(names, 0, 400, 250)
+		expected := []client.EventRangeQuery{
+			{Type: "first", StartHeight: 0, EndHeight: 249},
+			{Type: "second", StartHeight: 0, EndHeight: 249},
+			{Type: "first", StartHeight: 250, EndHeight: 400},
+			{Type: "second", StartHeight: 250, EndHeight: 400},
+		}
+		assert.Equal(t, expected, queries)
 	})
 
-	t.Run("Fails to get events without name", func(t *testing.T) {
+	t.Run("Should handle error from get events in goroutine", func(t *testing.T) {
 		t.Parallel()
 
-		_, s, _ := setup()
-		inputs := [][]string{
-			{"", "0", "1"},
-			{"test", "-1", "1"},
-			{"test", "1", "-1"},
-			{"test", "10", "5"},
-		}
+		_, s, gw := setup()
 
-		outputs := []string{
-			"cannot use empty string as event name",
-			"failed to parse start height of block range: -1",
-			"failed to parse end height of block range: -1",
-			"cannot have end height (5) of block range less that start height (10)",
-		}
+		gw.GetEvents.Return([]client.BlockEvents{}, errors.New("failed getting event"))
 
-		for i, in := range inputs {
-			_, err := s.Events.Get(in[0], in[1], in[2])
-			assert.Equal(t, err.Error(), outputs[i])
-		}
+		_, err := s.Events.Get([]string{"flow.CreateAccount"}, 0, 1, 250, 1)
+
+		assert.EqualError(t, err, "failed getting event")
 	})
+
 }
 
 func TestEvents_Integration(t *testing.T) {
 	t.Parallel()
 
-	t.Run("Get Events", func(t *testing.T) {
+	t.Run("Get Events for non existent event", func(t *testing.T) {
 		t.Parallel()
 
-		state, s := setupIntegration()
-		srvAcc, _ := state.EmulatorServiceAccount()
+		_, s := setupIntegration()
 
-		events, err := s.Events.Get("nonexisting", "0", "latest")
+		events, err := s.Events.Get([]string{"nonexisting"}, 0, 0, 250, 1)
 		assert.NoError(t, err)
 		assert.Len(t, events, 1)
 		assert.Len(t, events[0].Events, 0)
+	})
+
+	t.Run("Get Events while adding contracts", func(t *testing.T) {
+		t.Parallel()
+
+		state, s := setupIntegration()
+		srvAcc, _ := state.EmulatorServiceAccount()
 
 		// create events
-		_, err = s.Accounts.AddContract(srvAcc, tests.ContractEvents.Name, tests.ContractEvents.Source, false)
+		_, err := s.Accounts.AddContract(srvAcc, tests.ContractEvents.Name, tests.ContractEvents.Source, false)
+		assert.NoError(t, err)
 		assert.NoError(t, err)
-
 		for x := 'A'; x <= 'J'; x++ { // test contract emits events named from A to J
 			eName := fmt.Sprintf("A.%s.ContractEvents.Event%c", srvAcc.Address().String(), x)
-			events, err = s.Events.Get(eName, "0", "latest")
-
+			events, err := s.Events.Get([]string{eName}, 0, 1, 250, 1)
 			assert.NoError(t, err)
 			assert.Len(t, events, 2)
 			assert.Len(t, events[1].Events, 1)
-			assert.Equal(t, events[1].Events[0].Type, eName)
+
 		}
 	})
+
+	t.Run("Get Events while adding contracts in parallel", func(t *testing.T) {
+		t.Parallel()
+
+		state, s := setupIntegration()
+		srvAcc, _ := state.EmulatorServiceAccount()
+
+		// create events
+		_, err := s.Accounts.AddContract(srvAcc, tests.ContractEvents.Name, tests.ContractEvents.Source, false)
+		assert.NoError(t, err)
+
+		assert.NoError(t, err)
+		var eventNames []string
+		for x := 'A'; x <= 'J'; x++ { // test contract emits events named from A to J
+			eName := fmt.Sprintf("A.%s.ContractEvents.Event%c", srvAcc.Address().String(), x)
+			eventNames = append(eventNames, eName)
+		}
+
+		events, err := s.Events.Get(eventNames, 0, 1, 250, 5)
+		assert.NoError(t, err)
+		assert.Len(t, events, 20)
+		assert.Len(t, events[1].Events, 1)
+	})
 }