diff --git a/cmd/dummy/main.go b/cmd/dummy/main.go new file mode 100644 index 0000000..c4cb75f --- /dev/null +++ b/cmd/dummy/main.go @@ -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 +} diff --git a/test/dummy.go b/test/dummy.go index bac37a4..93be076 100644 --- a/test/dummy.go +++ b/test/dummy.go @@ -2,6 +2,8 @@ package test import ( "context" + "crypto/sha512" + "fmt" "time" "github.com/rollkit/go-execution/types" @@ -9,38 +11,60 @@ import ( // 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) } diff --git a/test/suite.go b/test/suite.go index c0536fb..28574db 100644 --- a/test/suite.go +++ b/test/suite.go @@ -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. @@ -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) }