Skip to content

Commit

Permalink
test: enhance DummyExecutor (#39)
Browse files Browse the repository at this point in the history
* test: enhance DummyExecutor

* chore: fix linter errors

* feat: handle signals, use channel instead of WaitGroup

* refactor: improve flag parsing and logs

* fix: remove block from pending after finalization

This prevents from memory leaking and makes it impossible to `SetFinal`
more than once.
  • Loading branch information
tzdybal authored Dec 3, 2024
1 parent 0418e6f commit 55adaf5
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 12 deletions.
78 changes: 78 additions & 0 deletions cmd/dummy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"errors"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"syscall"

"google.golang.org/grpc"

grpcproxy "github.com/rollkit/go-execution/proxy/grpc"
"github.com/rollkit/go-execution/test"
pb "github.com/rollkit/go-execution/types/pb/execution"
)

func main() {
listenAddress, err := parseListenAddress()
if err != nil {
log.Fatalf("Failed to parse listen address: %v\n", err)
}
listener, err := net.Listen("tcp4", listenAddress)
if err != nil {
log.Fatalf("Failed to listen on %q: %v\n", listenAddress, err)
}
defer func() {
_ = listener.Close()
}()

log.Println("Creating Dummy Executor and gRPC server")
dummy := test.NewDummyExecutor()
server := grpcproxy.NewServer(dummy, grpcproxy.DefaultConfig())
s := grpc.NewServer()
pb.RegisterExecutionServiceServer(s, server)

// Setup signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

doneChan := make(chan interface{}, 1)
go func() {
log.Printf("Serving (%s)...\n", listenAddress)
log.Println("Type Ctrl+C to shutdown")
if err := s.Serve(listener); err != nil && !errors.Is(err, grpc.ErrServerStopped) {
log.Fatalf("Server exited with error: %v\n", err)
}
doneChan <- nil
}()

// Handle shutdown signal
go func() {
<-sigChan
log.Println("Received shutdown signal")
s.GracefulStop()
}()

<-doneChan
log.Println("Server stopped")
}

func parseListenAddress() (string, error) {
var listenAddress string
flag.StringVar(&listenAddress, "address", "127.0.0.1:40041", "gRPC server listen address")
flag.Parse()

_, port, err := net.SplitHostPort(listenAddress)
if err != nil {
return "", fmt.Errorf("invalid address format %q: %v", listenAddress, err)
}
if port == "" {
return "", errors.New("port cannot be empty")
}

return listenAddress, nil
}
46 changes: 35 additions & 11 deletions test/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,69 @@ package test

import (
"context"
"crypto/sha512"
"fmt"
"time"

"github.com/rollkit/go-execution/types"
)

// DummyExecutor is a dummy implementation of the DummyExecutor interface for testing
type DummyExecutor struct {
stateRoot types.Hash
maxBytes uint64
txs []types.Tx
stateRoot types.Hash
pendingRoots map[uint64]types.Hash
maxBytes uint64
injectedTxs []types.Tx
}

// NewDummyExecutor creates a new dummy DummyExecutor instance
func NewDummyExecutor() *DummyExecutor {
return &DummyExecutor{
stateRoot: types.Hash{1, 2, 3},
maxBytes: 1000000,
txs: make([]types.Tx, 0),
stateRoot: types.Hash{1, 2, 3},
pendingRoots: make(map[uint64]types.Hash),
maxBytes: 1000000,
}
}

// InitChain initializes the chain state with the given genesis time, initial height, and chain ID.
// It returns the state root hash, the maximum byte size, and an error if the initialization fails.
func (e *DummyExecutor) InitChain(ctx context.Context, genesisTime time.Time, initialHeight uint64, chainID string) (types.Hash, uint64, error) {
hash := sha512.New()
hash.Write(e.stateRoot)
e.stateRoot = hash.Sum(nil)
return e.stateRoot, e.maxBytes, nil
}

// GetTxs returns the list of transactions (types.Tx) within the DummyExecutor instance and an error if any.
func (e *DummyExecutor) GetTxs(context.Context) ([]types.Tx, error) {
return e.txs, nil
txs := e.injectedTxs
e.injectedTxs = nil
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) {
e.injectedTxs = append(e.injectedTxs, tx)
}

// ExecuteTxs simulate execution of transactions.
func (e *DummyExecutor) ExecuteTxs(ctx context.Context, txs []types.Tx, blockHeight uint64, timestamp time.Time, prevStateRoot types.Hash) (types.Hash, uint64, error) {
e.txs = append(e.txs, txs...)
return e.stateRoot, e.maxBytes, nil
hash := sha512.New()
hash.Write(prevStateRoot)
for _, tx := range txs {
hash.Write(tx)
}
pending := hash.Sum(nil)
e.pendingRoots[blockHeight] = pending
return pending, e.maxBytes, nil
}

// SetFinal marks block at given height as finalized. Currently not implemented.
// SetFinal marks block at given height as finalized.
func (e *DummyExecutor) SetFinal(ctx context.Context, blockHeight uint64) error {
return nil
if pending, ok := e.pendingRoots[blockHeight]; ok {
e.stateRoot = pending
delete(e.pendingRoots, blockHeight)
return nil
}
return fmt.Errorf("cannot set finalized block at height %d", blockHeight)
}
8 changes: 7 additions & 1 deletion test/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *ExecutorSuite) TestInitChain() {
func (s *ExecutorSuite) TestGetTxs() {
txs, err := s.Exec.GetTxs(context.TODO())
s.Require().NoError(err)
s.NotNil(txs)
s.Empty(txs)
}

// TestExecuteTxs tests ExecuteTxs method.
Expand All @@ -50,6 +50,12 @@ func (s *ExecutorSuite) TestExecuteTxs() {

// TestSetFinal tests SetFinal method.
func (s *ExecutorSuite) TestSetFinal() {
// finalizing invalid height must return error
err := s.Exec.SetFinal(context.TODO(), 1)
s.Require().Error(err)

_, _, 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)
s.Require().NoError(err)
}

0 comments on commit 55adaf5

Please sign in to comment.