From 7580d9d6fecb66af11a9a28316f25be0f13599e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Tue, 18 Feb 2025 20:26:36 +0100 Subject: [PATCH 01/10] test: redesign tx injector # Conflicts: # test/dummy.go --- test/dummy.go | 17 +++++++++++++++-- test/dummy_test.go | 6 ++---- test/suite.go | 40 +++++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/test/dummy.go b/test/dummy.go index b9b5699..1e99c57 100644 --- a/test/dummy.go +++ b/test/dummy.go @@ -2,6 +2,8 @@ package test import ( "bytes" + "crypto/rand" + "context" "crypto/sha512" "fmt" @@ -52,12 +54,23 @@ func (e *DummyExecutor) GetTxs(context.Context) ([]types.Tx, error) { return txs, nil } -// InjectTx adds a transaction to the internal list of injected transactions in the DummyExecutor instance. -func (e *DummyExecutor) InjectTx(tx types.Tx) { +// InjectRandomTx adds a transaction to the internal list of injected transactions in the DummyExecutor instance. +func (e *DummyExecutor) InjectRandomTx() types.Tx { e.mu.Lock() defer e.mu.Unlock() + tx := types.Tx(mustGetRandomBytes(100)) e.injectedTxs = append(e.injectedTxs, tx) + return tx +} + +func mustGetRandomBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic(fmt.Errorf("failed to generate random bytes: %w", err)) + } + return b } // ExecuteTxs simulate execution of transactions. diff --git a/test/dummy_test.go b/test/dummy_test.go index 4c44618..5254437 100644 --- a/test/dummy_test.go +++ b/test/dummy_test.go @@ -27,11 +27,9 @@ func TestDummySuite(t *testing.T) { func TestTxRemoval(t *testing.T) { exec := NewDummyExecutor() - tx1 := types.Tx([]byte{1, 2, 3}) - tx2 := types.Tx([]byte{3, 2, 1}) - exec.InjectTx(tx1) - exec.InjectTx(tx2) + tx1 := exec.InjectRandomTx() + tx2 := exec.InjectRandomTx() // first execution of GetTxs - nothing special txs, err := exec.GetTxs(context.Background()) diff --git a/test/suite.go b/test/suite.go index 6508763..81d1014 100644 --- a/test/suite.go +++ b/test/suite.go @@ -19,7 +19,7 @@ type ExecutorSuite struct { // TxInjector provides an interface for injecting transactions into a test suite. type TxInjector interface { - InjectTx(tx types.Tx) + InjectRandomTx() types.Tx } // TestInitChain tests InitChain method. @@ -38,11 +38,8 @@ func (s *ExecutorSuite) TestInitChain() { func (s *ExecutorSuite) TestGetTxs() { s.skipIfInjectorNotSet() - tx1 := types.Tx("tx1") - tx2 := types.Tx("tx2") - - s.TxInjector.InjectTx(tx1) - s.TxInjector.InjectTx(tx2) + tx1 := s.TxInjector.InjectRandomTx() + tx2 := s.TxInjector.InjectRandomTx() txs, err := s.Exec.GetTxs(context.TODO()) s.Require().NoError(err) s.Require().Len(txs, 2) @@ -58,14 +55,15 @@ func (s *ExecutorSuite) skipIfInjectorNotSet() { // TestExecuteTxs tests ExecuteTxs method. func (s *ExecutorSuite) TestExecuteTxs() { - txs := []types.Tx{[]byte("tx1"), []byte("tx2")} - blockHeight := uint64(1) - timestamp := time.Now().UTC() - prevStateRoot := types.Hash{1, 2, 3} + s.skipIfInjectorNotSet() + txs := []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()} + + genesisTime, initialHeight, genesisStateRoot, _ := s.initChain(context.TODO()) - stateRoot, maxBytes, err := s.Exec.ExecuteTxs(context.TODO(), txs, blockHeight, timestamp, prevStateRoot) + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(context.TODO(), txs, initialHeight+1, genesisTime.Add(time.Second), genesisStateRoot) s.Require().NoError(err) s.NotEqual(types.Hash{}, stateRoot) + s.NotEqual(genesisStateRoot, stateRoot) s.Greater(maxBytes, uint64(0)) } @@ -75,6 +73,7 @@ func (s *ExecutorSuite) TestSetFinal() { err := s.Exec.SetFinal(context.TODO(), 1) s.Require().Error(err) + _, _, _, _ = s.initChain(context.TODO()) _, _, err = s.Exec.ExecuteTxs(context.TODO(), nil, 2, time.Now(), types.Hash("test state")) s.Require().NoError(err) err = s.Exec.SetFinal(context.TODO(), 2) @@ -83,16 +82,9 @@ func (s *ExecutorSuite) TestSetFinal() { // TestMultipleBlocks is a basic test ensuring that all API methods used together can be used to produce multiple blocks. func (s *ExecutorSuite) TestMultipleBlocks() { - genesisTime := time.Now().UTC() - initialHeight := uint64(1) - chainID := "test-chain" ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - - stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) - s.Require().NoError(err) - s.NotEqual(types.Hash{}, stateRoot) - s.Greater(maxBytes, uint64(0)) + genesisTime, initialHeight, stateRoot, maxBytes := s.initChain(ctx) for i := initialHeight; i <= 10; i++ { txs, err := s.Exec.GetTxs(ctx) @@ -107,3 +99,13 @@ func (s *ExecutorSuite) TestMultipleBlocks() { s.Require().NoError(err) } } + +func (s *ExecutorSuite) initChain(ctx context.Context) (time.Time, uint64, types.Hash, uint64) { + genesisTime := time.Now().UTC() + initialHeight := uint64(1) + chainID := "test-chain" + + stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) + s.Require().NoError(err) + return genesisTime, initialHeight, stateRoot, maxBytes +} From afecab36ad1e72b96f91d2c23b912dbb26db68b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Wed, 19 Feb 2025 10:38:16 +0100 Subject: [PATCH 02/10] fix: refactor handling of hash copy copy logic. Previously, hashes was not copied properly because of nil destination for copy function. Replaced manual slice copying with a dedicated `copyHash` function for cleaner and reusable code. Updated related assertions and method calls to align with the new approach, improving code readability and maintainability. --- proxy/grpc/client.go | 6 ++---- proxy/grpc/client_server_test.go | 8 +++----- proxy/grpc/server.go | 3 +-- proxy/grpc/util.go | 9 +++++++++ test/suite.go | 11 ++++++----- 5 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 proxy/grpc/util.go diff --git a/proxy/grpc/client.go b/proxy/grpc/client.go index 7b14f73..6f29f4b 100644 --- a/proxy/grpc/client.go +++ b/proxy/grpc/client.go @@ -61,8 +61,7 @@ func (c *Client) InitChain(ctx context.Context, genesisTime time.Time, initialHe return types.Hash{}, 0, err } - var stateRoot types.Hash - copy(stateRoot[:], resp.StateRoot) + stateRoot := copyHash(resp.StateRoot) return stateRoot, resp.MaxBytes, nil } @@ -99,8 +98,7 @@ func (c *Client) ExecuteTxs(ctx context.Context, txs []types.Tx, blockHeight uin return types.Hash{}, 0, err } - var updatedStateRoot types.Hash - copy(updatedStateRoot[:], resp.UpdatedStateRoot) + updatedStateRoot := copyHash(resp.UpdatedStateRoot) return updatedStateRoot, resp.MaxBytes, nil } diff --git a/proxy/grpc/client_server_test.go b/proxy/grpc/client_server_test.go index af9bc34..3b8a3dc 100644 --- a/proxy/grpc/client_server_test.go +++ b/proxy/grpc/client_server_test.go @@ -56,10 +56,8 @@ func TestClientServer(t *testing.T) { chainID := "test-chain" // initialize a new Hash with a fixed size - expectedStateRoot := make([]byte, 32) + expectedStateRoot := types.Hash(make([]byte, 32)) copy(expectedStateRoot, []byte{1, 2, 3}) - var stateRootHash types.Hash - copy(stateRootHash[:], expectedStateRoot) expectedMaxBytes := uint64(1000000) @@ -68,12 +66,12 @@ func TestClientServer(t *testing.T) { expectedTime := time.Unix(unixTime, 0).UTC() mockExec.On("InitChain", mock.Anything, expectedTime, initialHeight, chainID). - Return(stateRootHash, expectedMaxBytes, nil).Once() + Return(expectedStateRoot, expectedMaxBytes, nil).Once() stateRoot, maxBytes, err := client.InitChain(context.TODO(), genesisTime, initialHeight, chainID) require.NoError(t, err) - assert.Equal(t, stateRootHash, stateRoot) + assert.Equal(t, expectedStateRoot, stateRoot) assert.Equal(t, expectedMaxBytes, maxBytes) mockExec.AssertExpectations(t) }) diff --git a/proxy/grpc/server.go b/proxy/grpc/server.go index 629360e..f924d19 100644 --- a/proxy/grpc/server.go +++ b/proxy/grpc/server.go @@ -83,8 +83,7 @@ func (s *Server) ExecuteTxs(ctx context.Context, req *pb.ExecuteTxsRequest) (*pb txs[i] = tx } - var prevStateRoot types.Hash - copy(prevStateRoot[:], req.PrevStateRoot) + prevStateRoot := copyHash(req.PrevStateRoot) updatedStateRoot, maxBytes, err := s.exec.ExecuteTxs( ctx, diff --git a/proxy/grpc/util.go b/proxy/grpc/util.go new file mode 100644 index 0000000..2e3cf67 --- /dev/null +++ b/proxy/grpc/util.go @@ -0,0 +1,9 @@ +package grpc + +import "github.com/rollkit/go-execution/types" + +func copyHash(src []byte) types.Hash { + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} diff --git a/test/suite.go b/test/suite.go index 81d1014..52342e4 100644 --- a/test/suite.go +++ b/test/suite.go @@ -62,9 +62,9 @@ func (s *ExecutorSuite) TestExecuteTxs() { stateRoot, maxBytes, err := s.Exec.ExecuteTxs(context.TODO(), txs, initialHeight+1, genesisTime.Add(time.Second), genesisStateRoot) s.Require().NoError(err) - s.NotEqual(types.Hash{}, stateRoot) - s.NotEqual(genesisStateRoot, stateRoot) - s.Greater(maxBytes, uint64(0)) + s.Require().NotEmpty(stateRoot) + s.Require().NotEqualValues(genesisStateRoot, stateRoot) + s.Require().Greater(maxBytes, uint64(0)) } // TestSetFinal tests SetFinal method. @@ -73,8 +73,8 @@ func (s *ExecutorSuite) TestSetFinal() { err := s.Exec.SetFinal(context.TODO(), 1) s.Require().Error(err) - _, _, _, _ = s.initChain(context.TODO()) - _, _, err = s.Exec.ExecuteTxs(context.TODO(), nil, 2, time.Now(), types.Hash("test state")) + _, height, stateRoot, _ := s.initChain(context.TODO()) + _, _, err = s.Exec.ExecuteTxs(context.TODO(), nil, height+1, time.Now(), stateRoot) s.Require().NoError(err) err = s.Exec.SetFinal(context.TODO(), 2) s.Require().NoError(err) @@ -107,5 +107,6 @@ func (s *ExecutorSuite) initChain(ctx context.Context) (time.Time, uint64, types stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) s.Require().NoError(err) + s.Require().NotEmpty(stateRoot) return genesisTime, initialHeight, stateRoot, maxBytes } From 4c257e1602c85fea6b2494333027ab40da177333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 20 Feb 2025 16:32:42 +0100 Subject: [PATCH 03/10] test: add context timeout to tests in ExecutorSuite and gRPC client Introduced a context with a timeout to ensure all tests in `ExecutorSuite` and the gRPC client-server tests properly respect execution time limits. Standardized the use of a `maxTestDuration` constant to improve maintainability and readability across the test suite. --- proxy/grpc/client_server_test.go | 4 ++- test/suite.go | 43 ++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/proxy/grpc/client_server_test.go b/proxy/grpc/client_server_test.go index 3b8a3dc..57e3bda 100644 --- a/proxy/grpc/client_server_test.go +++ b/proxy/grpc/client_server_test.go @@ -68,7 +68,9 @@ func TestClientServer(t *testing.T) { mockExec.On("InitChain", mock.Anything, expectedTime, initialHeight, chainID). Return(expectedStateRoot, expectedMaxBytes, nil).Once() - stateRoot, maxBytes, err := client.InitChain(context.TODO(), genesisTime, initialHeight, chainID) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + stateRoot, maxBytes, err := client.InitChain(ctx, genesisTime, initialHeight, chainID) require.NoError(t, err) assert.Equal(t, expectedStateRoot, stateRoot) diff --git a/test/suite.go b/test/suite.go index 52342e4..22b7fc1 100644 --- a/test/suite.go +++ b/test/suite.go @@ -17,6 +17,8 @@ type ExecutorSuite struct { TxInjector TxInjector } +const maxTestDuration = 3 * time.Second + // TxInjector provides an interface for injecting transactions into a test suite. type TxInjector interface { InjectRandomTx() types.Tx @@ -28,7 +30,10 @@ func (s *ExecutorSuite) TestInitChain() { initialHeight := uint64(1) chainID := "test-chain" - stateRoot, maxBytes, err := s.Exec.InitChain(context.TODO(), genesisTime, initialHeight, chainID) + ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + defer cancel() + + stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) s.Require().NoError(err) s.NotEqual(types.Hash{}, stateRoot) s.Greater(maxBytes, uint64(0)) @@ -38,9 +43,12 @@ func (s *ExecutorSuite) TestInitChain() { func (s *ExecutorSuite) TestGetTxs() { s.skipIfInjectorNotSet() + ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + defer cancel() + tx1 := s.TxInjector.InjectRandomTx() tx2 := s.TxInjector.InjectRandomTx() - txs, err := s.Exec.GetTxs(context.TODO()) + txs, err := s.Exec.GetTxs(ctx) s.Require().NoError(err) s.Require().Len(txs, 2) s.Require().Contains(txs, tx1) @@ -56,11 +64,16 @@ func (s *ExecutorSuite) skipIfInjectorNotSet() { // TestExecuteTxs tests ExecuteTxs method. func (s *ExecutorSuite) TestExecuteTxs() { s.skipIfInjectorNotSet() + txs := []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()} + initialHeight := uint64(1) - genesisTime, initialHeight, genesisStateRoot, _ := s.initChain(context.TODO()) + ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + defer cancel() + + genesisTime, genesisStateRoot, _ := s.initChain(ctx, initialHeight) - stateRoot, maxBytes, err := s.Exec.ExecuteTxs(context.TODO(), txs, initialHeight+1, genesisTime.Add(time.Second), genesisStateRoot) + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, txs, initialHeight, genesisTime.Add(time.Second), genesisStateRoot) s.Require().NoError(err) s.Require().NotEmpty(stateRoot) s.Require().NotEqualValues(genesisStateRoot, stateRoot) @@ -69,22 +82,27 @@ func (s *ExecutorSuite) TestExecuteTxs() { // TestSetFinal tests SetFinal method. func (s *ExecutorSuite) TestSetFinal() { + ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + defer cancel() + // finalizing invalid height must return error - err := s.Exec.SetFinal(context.TODO(), 1) + err := s.Exec.SetFinal(ctx, 7) s.Require().Error(err) - _, height, stateRoot, _ := s.initChain(context.TODO()) - _, _, err = s.Exec.ExecuteTxs(context.TODO(), nil, height+1, time.Now(), stateRoot) + initialHeight := uint64(1) + _, stateRoot, _ := s.initChain(ctx, initialHeight) + _, _, err = s.Exec.ExecuteTxs(ctx, nil, initialHeight, time.Now(), stateRoot) s.Require().NoError(err) - err = s.Exec.SetFinal(context.TODO(), 2) + err = s.Exec.SetFinal(ctx, initialHeight) s.Require().NoError(err) } // TestMultipleBlocks is a basic test ensuring that all API methods used together can be used to produce multiple blocks. func (s *ExecutorSuite) TestMultipleBlocks() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) defer cancel() - genesisTime, initialHeight, stateRoot, maxBytes := s.initChain(ctx) + initialHeight := uint64(1) + genesisTime, stateRoot, maxBytes := s.initChain(ctx, initialHeight) for i := initialHeight; i <= 10; i++ { txs, err := s.Exec.GetTxs(ctx) @@ -100,13 +118,12 @@ func (s *ExecutorSuite) TestMultipleBlocks() { } } -func (s *ExecutorSuite) initChain(ctx context.Context) (time.Time, uint64, types.Hash, uint64) { +func (s *ExecutorSuite) initChain(ctx context.Context, initialHeight uint64) (time.Time, types.Hash, uint64) { genesisTime := time.Now().UTC() - initialHeight := uint64(1) chainID := "test-chain" stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) s.Require().NoError(err) s.Require().NotEmpty(stateRoot) - return genesisTime, initialHeight, stateRoot, maxBytes + return genesisTime, stateRoot, maxBytes } From daa661ce3be49c6b62899c2ea0ecdc1763bfdf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 20 Feb 2025 18:19:43 +0100 Subject: [PATCH 04/10] chore: remove unsed function & obsolete file --- proxy/grpc/util.go | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 proxy/grpc/util.go diff --git a/proxy/grpc/util.go b/proxy/grpc/util.go deleted file mode 100644 index 2e3cf67..0000000 --- a/proxy/grpc/util.go +++ /dev/null @@ -1,9 +0,0 @@ -package grpc - -import "github.com/rollkit/go-execution/types" - -func copyHash(src []byte) types.Hash { - dst := make([]byte, len(src)) - copy(dst, src) - return dst -} From 90e3f7ba01d4fa7aafa107192fd15598eaa26926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 20 Feb 2025 20:02:28 +0100 Subject: [PATCH 05/10] test: add more testing --- test/suite.go | 59 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/test/suite.go b/test/suite.go index 22b7fc1..4506ec7 100644 --- a/test/suite.go +++ b/test/suite.go @@ -17,8 +17,6 @@ type ExecutorSuite struct { TxInjector TxInjector } -const maxTestDuration = 3 * time.Second - // TxInjector provides an interface for injecting transactions into a test suite. type TxInjector interface { InjectRandomTx() types.Tx @@ -30,7 +28,7 @@ func (s *ExecutorSuite) TestInitChain() { initialHeight := uint64(1) chainID := "test-chain" - ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) @@ -43,12 +41,18 @@ func (s *ExecutorSuite) TestInitChain() { func (s *ExecutorSuite) TestGetTxs() { s.skipIfInjectorNotSet() - ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() + // try to get transactions without injecting any + txs, err := s.Exec.GetTxs(ctx) + s.Require().NoError(err) + s.Require().Empty(txs) + + // inject two txs and retrieve them tx1 := s.TxInjector.InjectRandomTx() tx2 := s.TxInjector.InjectRandomTx() - txs, err := s.Exec.GetTxs(ctx) + txs, err = s.Exec.GetTxs(ctx) s.Require().NoError(err) s.Require().Len(txs, 2) s.Require().Contains(txs, tx1) @@ -65,24 +69,45 @@ func (s *ExecutorSuite) skipIfInjectorNotSet() { func (s *ExecutorSuite) TestExecuteTxs() { s.skipIfInjectorNotSet() - txs := []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()} - initialHeight := uint64(1) + cases := []struct { + name string + txs []types.Tx + }{ + { + name: "nil txs", + txs: nil, + }, + { + name: "empty txs", + txs: []types.Tx{}, + }, + { + name: "two txs", + txs: []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()}, + }, + } - ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) - defer cancel() + for _, c := range cases { + s.Run(c.name, func() { + initialHeight := uint64(1) - genesisTime, genesisStateRoot, _ := s.initChain(ctx, initialHeight) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() - stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, txs, initialHeight, genesisTime.Add(time.Second), genesisStateRoot) - s.Require().NoError(err) - s.Require().NotEmpty(stateRoot) - s.Require().NotEqualValues(genesisStateRoot, stateRoot) - s.Require().Greater(maxBytes, uint64(0)) + genesisTime, genesisStateRoot, _ := s.initChain(ctx, initialHeight) + + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, initialHeight, genesisTime.Add(time.Second), genesisStateRoot) + s.Require().NoError(err) + s.Require().NotEmpty(stateRoot) + s.Require().NotEqual(genesisStateRoot, stateRoot) + s.Require().Greater(maxBytes, uint64(0)) + }) + } } // TestSetFinal tests SetFinal method. func (s *ExecutorSuite) TestSetFinal() { - ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // finalizing invalid height must return error @@ -99,7 +124,7 @@ func (s *ExecutorSuite) TestSetFinal() { // TestMultipleBlocks is a basic test ensuring that all API methods used together can be used to produce multiple blocks. func (s *ExecutorSuite) TestMultipleBlocks() { - ctx, cancel := context.WithTimeout(context.Background(), maxTestDuration) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() initialHeight := uint64(1) genesisTime, stateRoot, maxBytes := s.initChain(ctx, initialHeight) From 69bd7705ef27268b35928c9139023590e828898e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Fri, 21 Feb 2025 11:33:50 +0100 Subject: [PATCH 06/10] test: refactor state root handling in test suite Replaced the reuse of "stateRoot" with "prevStateRoot" for clarity and updated state transition logic. Added assertion to ensure state roots differ between iterations, improving test robustness. --- test/suite.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/suite.go b/test/suite.go index 4506ec7..b251178 100644 --- a/test/suite.go +++ b/test/suite.go @@ -127,16 +127,19 @@ func (s *ExecutorSuite) TestMultipleBlocks() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() initialHeight := uint64(1) - genesisTime, stateRoot, maxBytes := s.initChain(ctx, initialHeight) + genesisTime, prevStateRoot, _ := s.initChain(ctx, initialHeight) for i := initialHeight; i <= 10; i++ { txs, err := s.Exec.GetTxs(ctx) s.Require().NoError(err) blockTime := genesisTime.Add(time.Duration(i+1) * time.Second) //nolint:gosec - stateRoot, maxBytes, err = s.Exec.ExecuteTxs(ctx, txs, i, blockTime, stateRoot) + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, txs, i, blockTime, prevStateRoot) s.Require().NoError(err) s.Require().NotZero(maxBytes) + s.Require().NotEqual(prevStateRoot, stateRoot) + + prevStateRoot = stateRoot err = s.Exec.SetFinal(ctx, i) s.Require().NoError(err) From c95406241f7d8636b4a37fa70f7eacb5bb9b23e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Fri, 21 Feb 2025 12:19:40 +0100 Subject: [PATCH 07/10] test: refactor state root checks and handle empty transactions. Add a `stateRootChanged` flag to test cases to explicitly verify state root changes. Update logic in `Exec.ExecuteTxs` to handle state root equality checks properly and return previous state root for empty transactions in `dummy.go`. --- test/dummy.go | 5 +++++ test/suite.go | 24 +++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/test/dummy.go b/test/dummy.go index 1e99c57..886d87a 100644 --- a/test/dummy.go +++ b/test/dummy.go @@ -78,6 +78,11 @@ func (e *DummyExecutor) ExecuteTxs(ctx context.Context, txs []types.Tx, blockHei e.mu.Lock() defer e.mu.Unlock() + if len(txs) == 0 { + e.pendingRoots[blockHeight] = prevStateRoot + return prevStateRoot, e.maxBytes, nil + } + hash := sha512.New() hash.Write(prevStateRoot) for _, tx := range txs { diff --git a/test/suite.go b/test/suite.go index b251178..1d955ff 100644 --- a/test/suite.go +++ b/test/suite.go @@ -1,6 +1,7 @@ package test import ( + "bytes" "context" "time" @@ -70,20 +71,24 @@ func (s *ExecutorSuite) TestExecuteTxs() { s.skipIfInjectorNotSet() cases := []struct { - name string - txs []types.Tx + name string + txs []types.Tx + stateRootChanged bool }{ { - name: "nil txs", - txs: nil, + name: "nil txs", + txs: nil, + stateRootChanged: false, }, { - name: "empty txs", - txs: []types.Tx{}, + name: "empty txs", + txs: []types.Tx{}, + stateRootChanged: false, }, { - name: "two txs", - txs: []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()}, + name: "two txs", + txs: []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()}, + stateRootChanged: true, }, } @@ -99,7 +104,7 @@ func (s *ExecutorSuite) TestExecuteTxs() { stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, initialHeight, genesisTime.Add(time.Second), genesisStateRoot) s.Require().NoError(err) s.Require().NotEmpty(stateRoot) - s.Require().NotEqual(genesisStateRoot, stateRoot) + s.Require().NotEqual(c.stateRootChanged, bytes.Equal(genesisStateRoot, stateRoot)) s.Require().Greater(maxBytes, uint64(0)) }) } @@ -130,6 +135,7 @@ func (s *ExecutorSuite) TestMultipleBlocks() { genesisTime, prevStateRoot, _ := s.initChain(ctx, initialHeight) for i := initialHeight; i <= 10; i++ { + s.TxInjector.InjectRandomTx() txs, err := s.Exec.GetTxs(ctx) s.Require().NoError(err) From d24bbf4c4a7702625e69731bfd30e371b1c3d8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Fri, 21 Feb 2025 12:40:17 +0100 Subject: [PATCH 08/10] refactor TestExecuteTxs --- test/suite.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/suite.go b/test/suite.go index 1d955ff..ceb808d 100644 --- a/test/suite.go +++ b/test/suite.go @@ -3,6 +3,7 @@ package test import ( "bytes" "context" + "fmt" "time" "github.com/stretchr/testify/suite" @@ -92,16 +93,14 @@ func (s *ExecutorSuite) TestExecuteTxs() { }, } - for _, c := range cases { + for i, c := range cases { s.Run(c.name, func() { - initialHeight := uint64(1) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - genesisTime, genesisStateRoot, _ := s.initChain(ctx, initialHeight) + genesisTime, genesisStateRoot, _ := s.initChain(ctx, uint64(i+1)) - stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, initialHeight, genesisTime.Add(time.Second), genesisStateRoot) + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, uint64(1), genesisTime.Add(time.Second), genesisStateRoot) s.Require().NoError(err) s.Require().NotEmpty(stateRoot) s.Require().NotEqual(c.stateRootChanged, bytes.Equal(genesisStateRoot, stateRoot)) @@ -154,7 +153,7 @@ func (s *ExecutorSuite) TestMultipleBlocks() { func (s *ExecutorSuite) initChain(ctx context.Context, initialHeight uint64) (time.Time, types.Hash, uint64) { genesisTime := time.Now().UTC() - chainID := "test-chain" + chainID := fmt.Sprintf("test-chain-%d", initialHeight) stateRoot, maxBytes, err := s.Exec.InitChain(ctx, genesisTime, initialHeight, chainID) s.Require().NoError(err) From a0fa37e38dd7dca7b5707e0c5cfd6883fb544349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 27 Feb 2025 21:09:43 +0100 Subject: [PATCH 09/10] test: refactor transaction injection interface and update tests Refactored `TxInjector` to replace `InjectRandomTx` with `GetRandomTxs` and `InjectTxs` for better flexibility and clarity. Updated related test cases to reflect the new interface, ensuring consistency and enhanced test coverage. --- test/dummy.go | 19 +++++++++++------ test/dummy_test.go | 18 ++++++++++------ test/suite.go | 52 ++++++++++++++++++++++++++++------------------ 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/test/dummy.go b/test/dummy.go index 886d87a..db5399b 100644 --- a/test/dummy.go +++ b/test/dummy.go @@ -54,16 +54,23 @@ func (e *DummyExecutor) GetTxs(context.Context) ([]types.Tx, error) { return txs, nil } -// InjectRandomTx adds a transaction to the internal list of injected transactions in the DummyExecutor instance. -func (e *DummyExecutor) InjectRandomTx() types.Tx { +// GetRandomTxs generates a slice of n random transactions (types.Tx), each containing 100 random bytes. +func (e *DummyExecutor) GetRandomTxs(n int) []types.Tx { + txs := make([]types.Tx, n) + for i := 0; i < n; i++ { + txs[i] = mustGetRandomBytes(100) + } + return txs +} + +// InjectTxs adds a slice of transactions to the internal list of injected transactions in a thread-safe manner. +func (e *DummyExecutor) InjectTxs(txs []types.Tx) error { e.mu.Lock() defer e.mu.Unlock() - tx := types.Tx(mustGetRandomBytes(100)) - e.injectedTxs = append(e.injectedTxs, tx) - return tx + e.injectedTxs = append(e.injectedTxs, txs...) + return nil } - func mustGetRandomBytes(n int) []byte { b := make([]byte, n) _, err := rand.Read(b) diff --git a/test/dummy_test.go b/test/dummy_test.go index 5254437..70f146f 100644 --- a/test/dummy_test.go +++ b/test/dummy_test.go @@ -28,16 +28,22 @@ func TestDummySuite(t *testing.T) { func TestTxRemoval(t *testing.T) { exec := NewDummyExecutor() - tx1 := exec.InjectRandomTx() - tx2 := exec.InjectRandomTx() + // Generate random transactions using GetRandomTxs + randomTxs := exec.GetRandomTxs(2) - // first execution of GetTxs - nothing special + // Inject the random transactions into the executor + err := exec.InjectTxs(randomTxs) + require.NoError(t, err) + + tx1 := randomTxs[0] + tx2 := randomTxs[1] + + // Retrieve transactions and verify txs, err := exec.GetTxs(context.Background()) require.NoError(t, err) require.Len(t, txs, 2) - require.Contains(t, txs, tx1) - require.Contains(t, txs, tx2) - + require.Contains(t, txs, randomTxs[0]) + require.Contains(t, txs, randomTxs[1]) // ExecuteTxs was not called, so 2 txs should still be returned txs, err = exec.GetTxs(context.Background()) require.NoError(t, err) diff --git a/test/suite.go b/test/suite.go index ceb808d..9893a4d 100644 --- a/test/suite.go +++ b/test/suite.go @@ -21,7 +21,8 @@ type ExecutorSuite struct { // TxInjector provides an interface for injecting transactions into a test suite. type TxInjector interface { - InjectRandomTx() types.Tx + GetRandomTxs(n int) []types.Tx + InjectTxs(tx []types.Tx) error } // TestInitChain tests InitChain method. @@ -52,13 +53,16 @@ func (s *ExecutorSuite) TestGetTxs() { s.Require().Empty(txs) // inject two txs and retrieve them - tx1 := s.TxInjector.InjectRandomTx() - tx2 := s.TxInjector.InjectRandomTx() - txs, err = s.Exec.GetTxs(ctx) + // inject two txs and retrieve them using GetRandomTxs and InjectTxs + randomTxs := s.TxInjector.GetRandomTxs(2) // Retrieve 2 random transactions + err = s.TxInjector.InjectTxs(randomTxs) // Inject the transactions into the state + s.Require().NoError(err) + + txs, err = s.Exec.GetTxs(ctx) // Retrieve transactions from the executor s.Require().NoError(err) s.Require().Len(txs, 2) - s.Require().Contains(txs, tx1) - s.Require().Contains(txs, tx2) + s.Require().Contains(txs, randomTxs[0]) + s.Require().Contains(txs, randomTxs[1]) } func (s *ExecutorSuite) skipIfInjectorNotSet() { @@ -77,34 +81,41 @@ func (s *ExecutorSuite) TestExecuteTxs() { stateRootChanged bool }{ { - name: "nil txs", - txs: nil, + name: "empty txs", + txs: []types.Tx{}, stateRootChanged: false, }, { - name: "empty txs", - txs: []types.Tx{}, + name: "nil txs", + txs: nil, stateRootChanged: false, }, { name: "two txs", - txs: []types.Tx{s.TxInjector.InjectRandomTx(), s.TxInjector.InjectRandomTx()}, + txs: s.TxInjector.GetRandomTxs(2), stateRootChanged: true, }, } - for i, c := range cases { - s.Run(c.name, func() { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() - genesisTime, genesisStateRoot, _ := s.initChain(ctx, uint64(i+1)) + genesisTime, lastStateRoot, _ := s.initChain(ctx, uint64(1)) - stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, uint64(1), genesisTime.Add(time.Second), genesisStateRoot) + for i, c := range cases { + s.Run(c.name, func() { + err := s.TxInjector.InjectTxs(c.txs) + s.Require().NoError(err) + txs, _ := s.Exec.GetTxs(ctx) + fmt.Println(len(txs)) + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, c.txs, uint64(i+1), genesisTime.Add(time.Duration(i)*time.Second), lastStateRoot) s.Require().NoError(err) s.Require().NotEmpty(stateRoot) - s.Require().NotEqual(c.stateRootChanged, bytes.Equal(genesisStateRoot, stateRoot)) + s.Assert().Equal(c.stateRootChanged, !bytes.Equal(lastStateRoot, stateRoot)) s.Require().Greater(maxBytes, uint64(0)) + lastStateRoot = stateRoot + s.Exec.SetFinal(ctx, uint64(i+1)) + time.Sleep(3 * time.Second) }) } } @@ -134,7 +145,8 @@ func (s *ExecutorSuite) TestMultipleBlocks() { genesisTime, prevStateRoot, _ := s.initChain(ctx, initialHeight) for i := initialHeight; i <= 10; i++ { - s.TxInjector.InjectRandomTx() + err := s.TxInjector.InjectTxs(s.TxInjector.GetRandomTxs(2)) + s.Require().NoError(err) txs, err := s.Exec.GetTxs(ctx) s.Require().NoError(err) @@ -142,7 +154,7 @@ func (s *ExecutorSuite) TestMultipleBlocks() { stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, txs, i, blockTime, prevStateRoot) s.Require().NoError(err) s.Require().NotZero(maxBytes) - s.Require().NotEqual(prevStateRoot, stateRoot) + s.Assert().NotEqual(prevStateRoot, stateRoot) prevStateRoot = stateRoot From dc52ad420afce11f5bf108773a97e0020cca404e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Mon, 3 Mar 2025 17:59:46 +0100 Subject: [PATCH 10/10] test: Enhance test coverage and fix transaction injection logic Added a new test `TestSyncScenario` to validate block sync without mempool transactions and refined `TestMultipleBlocks` for clarity. Updated dummy logic to handle empty transaction cases and improved transaction injection error handling in tests. --- test/dummy.go | 5 +++++ test/dummy_test.go | 2 +- test/suite.go | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test/dummy.go b/test/dummy.go index 88441ab..badc4f1 100644 --- a/test/dummy.go +++ b/test/dummy.go @@ -126,6 +126,11 @@ func (e *DummyExecutor) ExecuteTxs(ctx context.Context, txs []types.Tx, blockHei } } + if len(txs) == 0 { + e.pendingRoots[blockHeight] = prevStateRoot + return prevStateRoot, e.maxBytes, nil + } + hash := sha512.New() hash.Write(prevStateRoot) for _, tx := range txs { diff --git a/test/dummy_test.go b/test/dummy_test.go index 67fc249..159ab86 100644 --- a/test/dummy_test.go +++ b/test/dummy_test.go @@ -214,7 +214,7 @@ func (s *DummyTestSuite) TestGetTxsWithConcurrency() { defer wg.Done() for j := 0; j < txsPerGoroutine; j++ { tx := types.Tx([]byte(fmt.Sprintf("tx-%d-%d", id, j))) - s.TxInjector.InjectTx(tx) + s.Require().NoError(s.TxInjector.InjectTxs([]types.Tx{tx})) } }(i) } diff --git a/test/suite.go b/test/suite.go index 9893a4d..5204e58 100644 --- a/test/suite.go +++ b/test/suite.go @@ -137,7 +137,7 @@ func (s *ExecutorSuite) TestSetFinal() { s.Require().NoError(err) } -// TestMultipleBlocks is a basic test ensuring that all API methods used together can be used to produce multiple blocks. +// TestMultipleBlocks is a basic test ensuring that all API methods used together can be used to produce multiple fresh blocks. func (s *ExecutorSuite) TestMultipleBlocks() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -163,6 +163,29 @@ func (s *ExecutorSuite) TestMultipleBlocks() { } } +// TestSyncScenario is a basic test ensuring that all API methods used together can be used to sync multiple blocks without injecting transactions to mempool. +func (s *ExecutorSuite) TestSyncScenario() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + initialHeight := uint64(1) + genesisTime, prevStateRoot, _ := s.initChain(ctx, initialHeight) + + for i := initialHeight; i <= 10; i++ { + txs := s.TxInjector.GetRandomTxs(2) + + blockTime := genesisTime.Add(time.Duration(i+1) * time.Second) //nolint:gosec + stateRoot, maxBytes, err := s.Exec.ExecuteTxs(ctx, txs, i, blockTime, prevStateRoot) + s.Require().NoError(err) + s.Require().NotZero(maxBytes) + s.Assert().NotEqual(prevStateRoot, stateRoot) + + prevStateRoot = stateRoot + + err = s.Exec.SetFinal(ctx, i) + s.Require().NoError(err) + } +} + func (s *ExecutorSuite) initChain(ctx context.Context, initialHeight uint64) (time.Time, types.Hash, uint64) { genesisTime := time.Now().UTC() chainID := fmt.Sprintf("test-chain-%d", initialHeight)