diff --git a/cmd/cartesi-rollups-advancer/root/root.go b/cmd/cartesi-rollups-advancer/root/root.go index fa405e2d4..56051f5af 100644 --- a/cmd/cartesi-rollups-advancer/root/root.go +++ b/cmd/cartesi-rollups-advancer/root/root.go @@ -4,6 +4,8 @@ package root import ( + "time" + "github.com/cartesi/rollups-node/internal/advancer" "github.com/cartesi/rollups-node/pkg/service" "github.com/spf13/cobra" @@ -21,6 +23,7 @@ var ( TelemetryAddress: ":10001", Impl: &advancerService, }, + MaxStartupTime: 10 * time.Second, } ) @@ -36,6 +39,12 @@ func init() { Cmd.Flags().Var(&createInfo.LogLevel, "log-level", "log level: debug, info, warn or error") + Cmd.Flags().BoolVar(&createInfo.LogPretty, + "log-color", createInfo.LogPretty, + "tint the logs (colored output)") + Cmd.Flags().DurationVar(&createInfo.MaxStartupTime, + "max-startup-time", createInfo.MaxStartupTime, + "maximum startup time in seconds") } func run(cmd *cobra.Command, args []string) { diff --git a/cmd/cartesi-rollups-claimer/root/root.go b/cmd/cartesi-rollups-claimer/root/root.go index af357f090..0d38f0987 100644 --- a/cmd/cartesi-rollups-claimer/root/root.go +++ b/cmd/cartesi-rollups-claimer/root/root.go @@ -4,6 +4,8 @@ package root import ( + "time" + "github.com/cartesi/rollups-node/internal/claimer" "github.com/cartesi/rollups-node/pkg/service" "github.com/spf13/cobra" @@ -24,6 +26,7 @@ var ( Impl: &claimerService, }, EnableSubmission: true, + MaxStartupTime: 10 * time.Second, } ) @@ -48,13 +51,19 @@ func init() { Cmd.Flags().Var(&createInfo.LogLevel, "log-level", "log level: debug, info, warn or error") + Cmd.Flags().BoolVar(&createInfo.LogPretty, + "log-color", createInfo.LogPretty, + "tint the logs (colored output)") + Cmd.Flags().DurationVar(&createInfo.MaxStartupTime, + "max-startup-time", createInfo.MaxStartupTime, + "maximum startup time in seconds") Cmd.Flags().BoolVar(&createInfo.EnableSubmission, "claim-submission", createInfo.EnableSubmission, "enable or disable claim submission (reader mode)") } func run(cmd *cobra.Command, args []string) { - cobra.CheckErr(claimer.Create(createInfo, &claimerService)) + cobra.CheckErr(claimer.Create(&createInfo, &claimerService)) claimerService.CreateDefaultHandlers("/" + claimerService.Name) cobra.CheckErr(claimerService.Serve()) } diff --git a/cmd/cartesi-rollups-cli/root/common/common.go b/cmd/cartesi-rollups-cli/root/common/common.go index c1c1ddaa5..92553cb68 100644 --- a/cmd/cartesi-rollups-cli/root/common/common.go +++ b/cmd/cartesi-rollups-cli/root/common/common.go @@ -4,6 +4,8 @@ package common import ( + "log/slog" + "github.com/cartesi/rollups-node/internal/repository" "github.com/spf13/cobra" ) @@ -19,6 +21,6 @@ func Setup(cmd *cobra.Command, args []string) { ctx := cmd.Context() var err error - Database, err = repository.Connect(ctx, PostgresEndpoint) + Database, err = repository.Connect(ctx, PostgresEndpoint, slog.Default()) cobra.CheckErr(err) } diff --git a/cmd/cartesi-rollups-evm-reader/root/root.go b/cmd/cartesi-rollups-evm-reader/root/root.go index c4b81edad..d4b1a63ae 100644 --- a/cmd/cartesi-rollups-evm-reader/root/root.go +++ b/cmd/cartesi-rollups-evm-reader/root/root.go @@ -4,8 +4,13 @@ package root import ( + "time" + + "github.com/cartesi/rollups-node/internal/config" "github.com/cartesi/rollups-node/internal/evmreader" + "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/service" + "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" ) @@ -24,8 +29,13 @@ var ( TelemetryAddress: ":10000", Impl: &readerService, }, - DefaultBlockString: "safe", + EvmReaderPersistentConfig: model.EvmReaderPersistentConfig{ + DefaultBlock: model.DefaultBlockStatusSafe, + }, + MaxStartupTime: 10 * time.Second, } + inputBoxAddress service.EthAddress + DefaultBlockString = "safe" ) var Cmd = &cobra.Command{ @@ -38,8 +48,8 @@ var Cmd = &cobra.Command{ func init() { createInfo.LoadEnv() - Cmd.Flags().StringVarP(&createInfo.DefaultBlockString, - "default-block", "d", createInfo.DefaultBlockString, + Cmd.Flags().StringVarP(&DefaultBlockString, + "default-block", "d", DefaultBlockString, `Default block to be used when fetching new blocks. One of 'latest', 'safe', 'pending', 'finalized'`) @@ -61,11 +71,9 @@ func init() { createInfo.BlockchainWsEndpoint.Value, "Blockchain WS Endpoint") -// Cmd.Flags().StringVarP(&inputBoxAddress, -// "inputbox-address", -// "i", -// "", -// "Input Box contract address") + Cmd.Flags().Var(&inputBoxAddress, + "inputbox-address", + "Input Box contract address") Cmd.Flags().Uint64VarP(&createInfo.InputBoxDeploymentBlock, "inputbox-block-number", @@ -75,11 +83,25 @@ func init() { Cmd.Flags().Var(&createInfo.LogLevel, "log-level", "log level: debug, info, warn or error") + Cmd.Flags().BoolVar(&createInfo.LogPretty, + "log-color", createInfo.LogPretty, + "tint the logs (colored output)") + Cmd.Flags().DurationVar(&createInfo.MaxStartupTime, + "max-startup-time", createInfo.MaxStartupTime, + "maximum startup time in seconds") } func run(cmd *cobra.Command, args []string) { - ready := make(chan struct{}, 1) + if cmd.Flags().Changed("default-block") { + var err error + createInfo.DefaultBlock, err = config.ToDefaultBlockFromString(DefaultBlockString) + cobra.CheckErr(err) + } + if cmd.Flags().Changed("inputbox-address") { + createInfo.InputBoxAddress = common.Address(inputBoxAddress) + } + cobra.CheckErr(evmreader.Create(&createInfo, &readerService)) readerService.CreateDefaultHandlers("/" + readerService.Name) - cobra.CheckErr(readerService.Start(nil, ready)) + cobra.CheckErr(readerService.Serve()) } diff --git a/cmd/cartesi-rollups-node/root/root.go b/cmd/cartesi-rollups-node/root/root.go index 3191fb93e..fb8e5feff 100644 --- a/cmd/cartesi-rollups-node/root/root.go +++ b/cmd/cartesi-rollups-node/root/root.go @@ -4,84 +4,56 @@ package root import ( - "context" - "log/slog" - "os" - "os/signal" - "syscall" "time" - "github.com/cartesi/rollups-node/internal/config" "github.com/cartesi/rollups-node/internal/node" - "github.com/cartesi/rollups-node/internal/repository" + "github.com/cartesi/rollups-node/pkg/service" "github.com/spf13/cobra" ) -const CMD_NAME = "node" - var ( // Should be overridden during the final release build with ldflags // to contain the actual version number buildVersion = "devel" - Cmd = &cobra.Command{ - Use: CMD_NAME, - Short: "Runs the Cartesi Rollups Node", - Long: "Runs the Cartesi Rollups Node as a single process", - RunE: run, + nodeService = node.Service{} + createInfo = node.CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "cartesi-rollups-node", + ProcOwner: true, + EnableSignalHandling: true, + TelemetryCreate: true, + TelemetryAddress: ":10001", + Impl: &nodeService, + }, + MaxStartupTime: 10 * time.Second, } - enableClaimSubmission bool ) +var Cmd = &cobra.Command{ + Use: createInfo.Name, + Short: "Runs " + createInfo.Name, + Long: "Runs " + createInfo.Name + " as a single process", + Run: run, +} + func init() { - Cmd.Flags().BoolVar(&enableClaimSubmission, - "claim-submission", true, + createInfo.LoadEnv() + Cmd.Flags().BoolVar(&createInfo.EnableClaimSubmission, + "claim-submission", createInfo.EnableClaimSubmission, "enable or disable claim submission (reader mode)") + Cmd.Flags().Var(&createInfo.LogLevel, + "log-level", + "log level: debug, info, warn or error") + Cmd.Flags().BoolVar(&createInfo.LogPretty, + "log-color", createInfo.LogPretty, + "tint the logs (colored output)") + Cmd.Flags().DurationVar(&createInfo.MaxStartupTime, + "max-startup-time", createInfo.MaxStartupTime, + "maximum startup time in seconds") } -func run(cmd *cobra.Command, args []string) error { - startTime := time.Now() - - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() - - cfg := config.FromEnv() - if cmd.Flags().Lookup("claim-submission").Changed { - cfg.FeatureClaimSubmissionEnabled = enableClaimSubmission - if enableClaimSubmission && cfg.Auth == nil { - cfg.Auth = config.AuthFromEnv() - } - } - - database, err := repository.Connect(ctx, cfg.PostgresEndpoint.Value) - if err != nil { - slog.Error("Node couldn't connect to the database", "error", err) - os.Exit(1) - } - defer database.Close() - - // create the node supervisor - supervisor, err := node.Setup(ctx, cfg, database) - if err != nil { - slog.Error("Node exited with an error", "error", err) - os.Exit(1) - } - - // logs startup time - ready := make(chan struct{}, 1) - go func() { - select { - case <-ready: - duration := time.Since(startTime) - slog.Info("Node is ready", "after", duration) - case <-ctx.Done(): - } - }() - - // start supervisor - if err := supervisor.Start(ctx, ready); err != nil { - slog.Error("Node exited with an error", "error", err) - os.Exit(1) - } - - return err +func run(cmd *cobra.Command, args []string) { + cobra.CheckErr(node.Create(&createInfo, &nodeService)) + nodeService.CreateDefaultHandlers("") + cobra.CheckErr(nodeService.Serve()) } diff --git a/cmd/cartesi-rollups-validator/root/root.go b/cmd/cartesi-rollups-validator/root/root.go index adc5d7ca0..68b5b117b 100644 --- a/cmd/cartesi-rollups-validator/root/root.go +++ b/cmd/cartesi-rollups-validator/root/root.go @@ -44,13 +44,16 @@ func init() { Cmd.Flags().Var(&createInfo.LogLevel, "log-level", "log level: debug, info, warn or error") + Cmd.Flags().BoolVar(&createInfo.LogPretty, + "log-color", createInfo.LogPretty, + "tint the logs (colored output)") Cmd.Flags().StringVar(&createInfo.PostgresEndpoint.Value, "postgres-endpoint", createInfo.PostgresEndpoint.Value, "Postgres endpoint") } func run(cmd *cobra.Command, args []string) { - cobra.CheckErr(validator.Create(createInfo, &validatorService)) + cobra.CheckErr(validator.Create(&createInfo, &validatorService)) validatorService.CreateDefaultHandlers("/" + validatorService.Name) cobra.CheckErr(validatorService.Serve()) } diff --git a/go.mod b/go.mod index 41d9fa2ef..b6ca83288 100644 --- a/go.mod +++ b/go.mod @@ -16,14 +16,10 @@ require ( github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/service/kms v1.37.2 - github.com/davecgh/go-spew v1.1.1 github.com/deepmap/oapi-codegen/v2 v2.1.0 github.com/golang-migrate/migrate/v4 v4.18.1 - github.com/jackc/pgconn v1.14.3 - github.com/jackc/pgx v3.6.2+incompatible github.com/jackc/pgx/v5 v5.7.1 github.com/lmittmann/tint v1.0.5 - github.com/mattn/go-isatty v0.0.20 github.com/oapi-codegen/runtime v1.1.1 golang.org/x/sync v0.8.0 golang.org/x/text v0.19.0 @@ -46,12 +42,12 @@ require ( github.com/aws/smithy-go v1.22.0 // indirect github.com/bits-and-blooms/bitset v1.14.3 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect - github.com/cockroachdb/apd v1.1.0 // indirect github.com/consensys/bavard v0.1.22 // indirect github.com/consensys/gnark-crypto v0.14.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.3 // indirect @@ -61,7 +57,6 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -69,11 +64,7 @@ require ( github.com/holiman/uint256 v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -86,7 +77,6 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 30d3c2e78..4fd667e66 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= @@ -137,8 +135,6 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -179,25 +175,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= -github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -282,8 +263,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -296,7 +275,6 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -349,7 +327,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -366,7 +343,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/advancer/advancer.go b/internal/advancer/advancer.go index 9e5dea38f..5b28b965a 100644 --- a/internal/advancer/advancer.go +++ b/internal/advancer/advancer.go @@ -13,6 +13,7 @@ import ( "github.com/cartesi/rollups-node/internal/advancer/machines" "github.com/cartesi/rollups-node/internal/config" + "github.com/cartesi/rollups-node/internal/services" "github.com/cartesi/rollups-node/internal/inspect" . "github.com/cartesi/rollups-node/internal/model" @@ -43,25 +44,11 @@ type IAdvancerMachines interface { Apps() []Address } -type Advancer struct { - repository IAdvancerRepository - machines IAdvancerMachines -} - type Service struct { service.Service - Advancer - inspector *inspect.Inspector -} - -func New(machines IAdvancerMachines, repository IAdvancerRepository) (*Advancer, error) { - if machines == nil { - return nil, ErrInvalidMachines - } - if repository == nil { - return nil, ErrInvalidRepository - } - return &Advancer{machines: machines, repository: repository}, nil + repository IAdvancerRepository + machines IAdvancerMachines + inspector inspect.Inspector } type CreateInfo struct { @@ -74,6 +61,7 @@ type CreateInfo struct { HttpPort int MachineServerVerbosity config.Redacted[cartesimachine.ServerVerbosity] Machines *machines.Machines + MaxStartupTime time.Duration } func (c *CreateInfo) LoadEnv() { @@ -84,6 +72,8 @@ func (c *CreateInfo) LoadEnv() { c.MachineServerVerbosity.Value = cartesimachine.ServerVerbosity(config.GetMachineServerVerbosity()) c.LogLevel = service.LogLevel(config.GetLogLevel()) + c.LogPretty = config.GetLogPrettyEnabled() + c.MaxStartupTime = config.GetMaxStartupTime() } func Create(c *CreateInfo, s *Service) error { @@ -92,31 +82,50 @@ func Create(c *CreateInfo, s *Service) error { return err } - if c.Repository == nil { - c.Repository, err = repository.Connect(s.Context, c.PostgresEndpoint.Value) - if err != nil { - return err + return service.WithTimeout(c.MaxStartupTime, func() error { + if s.repository == nil { + if c.Repository == nil { + c.Repository, err = repository.Connect(s.Context, c.PostgresEndpoint.Value, s.Logger) + if err != nil { + return err + } + } + s.repository = c.Repository } - } - s.repository = c.Repository - if c.Machines == nil { - c.Machines, err = machines.Load(s.Context, c.Repository, c.MachineServerVerbosity.Value) - if err != nil { - return err + if s.machines == nil { + if c.Machines == nil { + c.Machines, err = machines.Load(s.Context, + c.Repository, c.MachineServerVerbosity.Value, s.Logger) + if err != nil { + return err + } + } + s.machines = c.Machines } - } - s.machines = c.Machines - if s.Service.ServeMux == nil { - if c.CreateInfo.ServeMux == nil { - c.ServeMux = http.NewServeMux() + // allow partial construction for testing + if c.Machines != nil { + logger := service.NewLogger(slog.Level(c.LogLevel), c.LogPretty) + logger = logger.With("service", "inspect") + s.inspector = inspect.Inspector{ + IInspectMachines: c.Machines, + Logger: logger, + } + if s.Service.ServeMux == nil { + if c.CreateInfo.ServeMux == nil { + c.ServeMux = http.NewServeMux() + } + s.ServeMux = c.ServeMux + } + + s.ServeMux.Handle("/inspect/{dapp}", + services.CorsMiddleware(http.Handler(&s.inspector))) + s.ServeMux.Handle("/inspect/{dapp}/{payload}", + services.CorsMiddleware(http.Handler(&s.inspector))) } - } - s.Service.ServeMux.Handle("/inspect/{dapp}", http.Handler(s.inspector)) - s.Service.ServeMux.Handle("/inspect/{dapp}/{payload}", http.Handler(s.inspector)) - - return nil + return nil + }) } func (s *Service) Alive() bool { return true } @@ -144,7 +153,7 @@ func (v *Service) String() string { // It gets unprocessed inputs from the repository, // runs them through the cartesi machine, // and updates the repository with the outputs. -func (advancer *Advancer) Step(ctx context.Context) error { +func (advancer *Service) Step(ctx context.Context) error { // Dynamically updates the list of machines err := advancer.machines.UpdateMachines(ctx) if err != nil { @@ -154,7 +163,7 @@ func (advancer *Advancer) Step(ctx context.Context) error { apps := advancer.machines.Apps() // Gets the unprocessed inputs (of all apps) from the repository. - slog.Debug("advancer: querying for unprocessed inputs") + advancer.Logger.Debug("querying for unprocessed inputs") inputs, err := advancer.repository.GetUnprocessedInputs(ctx, apps) if err != nil { return err @@ -162,7 +171,7 @@ func (advancer *Advancer) Step(ctx context.Context) error { // Processes each set of inputs. for app, inputs := range inputs { - slog.Debug(fmt.Sprintf("advancer: processing %d input(s) from %v", len(inputs), app)) + advancer.Logger.Debug(fmt.Sprintf("processing %d input(s) from %v", len(inputs), app)) err := advancer.process(ctx, app, inputs) if err != nil { return err @@ -181,7 +190,7 @@ func (advancer *Advancer) Step(ctx context.Context) error { } // process sequentially processes inputs from the the application. -func (advancer *Advancer) process(ctx context.Context, app Address, inputs []*Input) error { +func (advancer *Service) process(ctx context.Context, app Address, inputs []*Input) error { // Asserts that the app has an associated machine. machine, exists := advancer.machines.GetAdvanceMachine(app) if !exists { @@ -195,7 +204,7 @@ func (advancer *Advancer) process(ctx context.Context, app Address, inputs []*In // FIXME if theres a change in epoch id call update epochs for _, input := range inputs { - slog.Info("advancer: Processing input", "app", app, "id", input.Id, "index", input.Index) + advancer.Logger.Info("Processing input", "app", app, "id", input.Id, "index", input.Index) // Sends the input to the cartesi machine. res, err := machine.Advance(ctx, input.RawData, input.Index) @@ -212,4 +221,3 @@ func (advancer *Advancer) process(ctx context.Context, app Address, inputs []*In return nil } - diff --git a/internal/advancer/advancer_test.go b/internal/advancer/advancer_test.go index 5a863da84..7f12cbc94 100644 --- a/internal/advancer/advancer_test.go +++ b/internal/advancer/advancer_test.go @@ -11,10 +11,12 @@ import ( "fmt" mrand "math/rand" "testing" + "time" "github.com/cartesi/rollups-node/internal/advancer/machines" . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/internal/nodemachine" + "github.com/cartesi/rollups-node/pkg/service" "github.com/stretchr/testify/suite" ) @@ -25,41 +27,18 @@ func TestAdvancer(t *testing.T) { type AdvancerSuite struct{ suite.Suite } -func (s *AdvancerSuite) TestNew() { - s.Run("Ok", func() { - require := s.Require() - machines := newMockMachines() - machines.Map[randomAddress()] = &MockMachine{} - var repository IAdvancerRepository = &MockRepository{} - advancer, err := New(machines, repository) - require.NotNil(advancer) - require.Nil(err) - }) - - s.Run("InvalidMachines", func() { - require := s.Require() - var machines IAdvancerMachines = nil - var repository IAdvancerRepository = &MockRepository{} - advancer, err := New(machines, repository) - require.Nil(advancer) - require.Error(err) - require.Equal(ErrInvalidMachines, err) - }) - - s.Run("InvalidRepository", func() { - require := s.Require() - machines := newMockMachines() - machines.Map[randomAddress()] = &MockMachine{} - var repository IAdvancerRepository = nil - advancer, err := New(machines, repository) - require.Nil(advancer) - require.Error(err) - require.Equal(ErrInvalidRepository, err) - }) -} - -func (s *AdvancerSuite) TestPoller() { - s.T().Skip("TODO") +func newMock(m IAdvancerMachines, r IAdvancerRepository) (*Service, error) { + s := &Service{ + machines: m, + repository: r, + } + return s, Create(&CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "advancer", + Impl: s, + }, + MaxStartupTime: 1 * time.Second, + }, s) } func (s *AdvancerSuite) TestRun() { @@ -87,7 +66,7 @@ func (s *AdvancerSuite) TestRun() { }, } - advancer, err := New(machines, repository) + advancer, err := newMock(machines, repository) require.NotNil(advancer) require.Nil(err) @@ -105,15 +84,15 @@ func (s *AdvancerSuite) TestRun() { } func (s *AdvancerSuite) TestProcess() { - setup := func() (IAdvancerMachines, *MockRepository, *Advancer, Address) { + setup := func() (IAdvancerMachines, *MockRepository, *Service, Address) { + require := s.Require() + app := randomAddress() machines := newMockMachines() machines.Map[app] = &MockMachine{} repository := &MockRepository{} - advancer := &Advancer{ - machines: machines, - repository: repository, - } + advancer, err := newMock(machines, repository) + require.Nil(err) return machines, repository, advancer, app } diff --git a/internal/advancer/machines/machines.go b/internal/advancer/machines/machines.go index 97eafedb5..6befc2c00 100644 --- a/internal/advancer/machines/machines.go +++ b/internal/advancer/machines/machines.go @@ -45,6 +45,7 @@ type Machines struct { machines map[Address]*nm.NodeMachine repository Repository verbosity cm.ServerVerbosity + Logger *slog.Logger } // Load initializes the cartesi machines. @@ -52,7 +53,12 @@ type Machines struct { // // Load does not fail when one of those machines fail to initialize. // It stores the error to be returned later and continues to initialize the other machines. -func Load(ctx context.Context, repo Repository, verbosity cm.ServerVerbosity) (*Machines, error) { +func Load( + ctx context.Context, + repo Repository, + verbosity cm.ServerVerbosity, + logger *slog.Logger, +) (*Machines, error) { configs, err := repo.GetMachineConfigurations(ctx) if err != nil { return nil, err @@ -63,7 +69,7 @@ func Load(ctx context.Context, repo Repository, verbosity cm.ServerVerbosity) (* for _, config := range configs { // Creates the machine. - machine, err := createMachine(ctx, verbosity, config) + machine, err := createMachine(ctx, verbosity, config, logger) if err != nil { err = fmt.Errorf("failed to create machine from snapshot (%v): %w", config, err) errs = errors.Join(errs, err) @@ -71,7 +77,7 @@ func Load(ctx context.Context, repo Repository, verbosity cm.ServerVerbosity) (* } // Advances the machine until it catches up with the state of the database (if necessary). - err = catchUp(ctx, repo, config.AppAddress, machine, config.ProcessedInputs) + err = catchUp(ctx, repo, config.AppAddress, machine, config.ProcessedInputs, logger) if err != nil { err = fmt.Errorf("failed to advance cartesi machine (%v): %w", config, err) errs = errors.Join(errs, err, machine.Close()) @@ -81,7 +87,12 @@ func Load(ctx context.Context, repo Repository, verbosity cm.ServerVerbosity) (* machines[config.AppAddress] = machine } - return &Machines{machines: machines, repository: repo, verbosity: verbosity}, errs + return &Machines{ + machines: machines, + repository: repo, + verbosity: verbosity, + Logger: logger, + }, errs } func (m *Machines) UpdateMachines(ctx context.Context) error { @@ -95,15 +106,15 @@ func (m *Machines) UpdateMachines(ctx context.Context) error { continue } - machine, err := createMachine(ctx, m.verbosity, config) + machine, err := createMachine(ctx, m.verbosity, config, m.Logger) if err != nil { - slog.Error("advancer: Failed to create machine", "app", config.AppAddress, "error", err) + m.Logger.Error("Failed to create machine", "app", config.AppAddress, "error", err) continue } - err = catchUp(ctx, m.repository, config.AppAddress, machine, config.ProcessedInputs) + err = catchUp(ctx, m.repository, config.AppAddress, machine, config.ProcessedInputs, m.Logger) if err != nil { - slog.Error("Failed to sync the machine", "app", config.AppAddress, "error", err) + m.Logger.Error("Failed to sync the machine", "app", config.AppAddress, "error", err) machine.Close() continue } @@ -158,7 +169,7 @@ func (m *Machines) RemoveAbsent(configs []*MachineConfig) { } for address, machine := range m.machines { if !configMap[address] { - slog.Info("advancer: Application was disabled, shutting down machine", "application", address) + m.Logger.Info("Application was disabled, shutting down machine", "application", address) machine.Close() delete(m.machines, address) } @@ -200,7 +211,7 @@ func (m *Machines) Close() error { err := closeMachines(m.machines) if err != nil { - slog.Error(fmt.Sprintf("failed to close some machines: %v", err)) + m.Logger.Error(fmt.Sprintf("failed to close some machines: %v", err)) } return err } @@ -227,26 +238,27 @@ func closeMachines(machines map[Address]*nm.NodeMachine) (err error) { func createMachine(ctx context.Context, verbosity cm.ServerVerbosity, config *MachineConfig, + logger *slog.Logger, ) (*nm.NodeMachine, error) { - slog.Info("advancer: creating machine", "application", config.AppAddress, + logger.Info("creating machine", "application", config.AppAddress, "template-path", config.SnapshotPath) - slog.Debug("advancer: instantiating remote machine server", "application", config.AppAddress) + logger.Debug("instantiating remote machine server", "application", config.AppAddress) // Starts the server. - address, err := cm.StartServer(verbosity, 0, os.Stdout, os.Stderr) + address, err := cm.StartServer(logger, verbosity, 0, os.Stdout, os.Stderr) if err != nil { return nil, err } - slog.Info("advancer: loading machine on server", "application", config.AppAddress, + logger.Info("loading machine on server", "application", config.AppAddress, "remote-machine", address, "template-path", config.SnapshotPath) // Creates a CartesiMachine from the snapshot. runtimeConfig := &emulator.MachineRuntimeConfig{} cartesiMachine, err := cm.Load(ctx, config.SnapshotPath, address, runtimeConfig) if err != nil { - return nil, errors.Join(err, cm.StopServer(address)) + return nil, errors.Join(err, cm.StopServer(address, logger)) } - slog.Debug("advancer: machine loaded on server", "application", config.AppAddress, + logger.Debug("machine loaded on server", "application", config.AppAddress, "remote-machine", address, "template-path", config.SnapshotPath) // Creates a RollupsMachine from the CartesiMachine. @@ -276,9 +288,10 @@ func catchUp(ctx context.Context, app Address, machine *nm.NodeMachine, processedInputs uint64, + logger *slog.Logger, ) error { - slog.Info("advancer: catching up unprocessed inputs", "app", app) + logger.Info("catching up unprocessed inputs", "app", app) inputs, err := repo.GetProcessedInputs(ctx, app, processedInputs) if err != nil { @@ -287,7 +300,7 @@ func catchUp(ctx context.Context, for _, input := range inputs { // FIXME epoch id to epoch index - slog.Info("advancer: advancing", "app", app, "epochId", input.EpochId, + logger.Info("advancing", "app", app, "epochId", input.EpochId, "input_index", input.Index) _, err := machine.Advance(ctx, input.RawData, input.Index) if err != nil { diff --git a/internal/advancer/machines/machines_test.go b/internal/advancer/machines/machines_test.go index bb35c1c56..f44979fc3 100644 --- a/internal/advancer/machines/machines_test.go +++ b/internal/advancer/machines/machines_test.go @@ -8,4 +8,3 @@ type machinesMock struct { mock.Mock Machines } - diff --git a/internal/claimer/claimer.go b/internal/claimer/claimer.go index e1f785136..cfe3d6bf4 100644 --- a/internal/claimer/claimer.go +++ b/internal/claimer/claimer.go @@ -40,6 +40,7 @@ package claimer import ( "context" "fmt" + "time" "github.com/cartesi/rollups-node/internal/config" "github.com/cartesi/rollups-node/internal/repository" @@ -54,7 +55,7 @@ import ( var ( ErrClaimMismatch = fmt.Errorf("claim and antecessor mismatch") ErrEventMismatch = fmt.Errorf("Computed Claim mismatches ClaimSubmission event") - ErrMissingEvent = fmt.Errorf("accepted claim has no matching blockchain event") + ErrMissingEvent = fmt.Errorf("accepted claim has no matching blockchain event") ) type address = common.Address @@ -69,18 +70,17 @@ type CreateInfo struct { BlockchainHttpEndpoint config.Redacted[string] EthConn *ethclient.Client - - PostgresEndpoint config.Redacted[string] - DBConn *repository.Database - - EnableSubmission bool + PostgresEndpoint config.Redacted[string] + Repository *repository.Database + EnableSubmission bool + MaxStartupTime time.Duration } type Service struct { service.Service submissionEnabled bool - DBConn *repository.Database + Repository *repository.Database EthConn *ethclient.Client TxOpts *bind.TransactOpts claimsInFlight map[address]hash // -> txHash @@ -93,49 +93,53 @@ func (c *CreateInfo) LoadEnv() { } c.BlockchainHttpEndpoint.Value = config.GetBlockchainHttpEndpoint() c.PostgresEndpoint.Value = config.GetPostgresEndpoint() + c.PollInterval = config.GetClaimerPollingInterval() + c.LogLevel = service.LogLevel(config.GetLogLevel()) + c.MaxStartupTime = config.GetMaxStartupTime() } -func Create(ci CreateInfo, s *Service) error { +func Create(c *CreateInfo, s *Service) error { var err error - err = service.Create(&ci.CreateInfo, &s.Service) + err = service.Create(&c.CreateInfo, &s.Service) if err != nil { return err } - s.submissionEnabled = ci.EnableSubmission - if s.EthConn == nil { - if ci.EthConn == nil { - ci.EthConn, err = ethclient.Dial(ci.BlockchainHttpEndpoint.Value) - if err != nil { - return err + return service.WithTimeout(c.MaxStartupTime, func() error { + s.submissionEnabled = c.EnableSubmission + if s.EthConn == nil { + if c.EthConn == nil { + c.EthConn, err = ethclient.Dial(c.BlockchainHttpEndpoint.Value) + if err != nil { + return err + } } + s.EthConn = c.EthConn } - s.EthConn = ci.EthConn - } - if s.DBConn == nil { - if ci.DBConn == nil { - ci.DBConn, err = repository.Connect(s.Context, ci.PostgresEndpoint.Value) - if err != nil { - return err + if s.Repository == nil { + if c.Repository == nil { + c.Repository, err = repository.Connect(s.Context, c.PostgresEndpoint.Value, s.Logger) + if err != nil { + return err + } } + s.Repository = c.Repository } - s.DBConn = ci.DBConn - } - if s.claimsInFlight == nil { - s.claimsInFlight = map[address]hash{} - } - - if s.submissionEnabled && s.TxOpts == nil { - s.TxOpts, err = CreateTxOptsFromAuth(ci.Auth, s.Context, s.EthConn) - if err != nil { - return err + if s.claimsInFlight == nil { + s.claimsInFlight = map[address]hash{} } - } - return err + if s.submissionEnabled && s.TxOpts == nil { + s.TxOpts, err = CreateTxOptsFromAuth(c.Auth, s.Context, s.EthConn) + if err != nil { + return err + } + } + return nil + }) } func (s *Service) Alive() bool { @@ -182,14 +186,14 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { errs = append(errs, err) return errs } - s.Logger.Info("claimer: Claim submitted", + s.Logger.Info("Claim submitted", "app", claim.AppContractAddress, "claim", claim.EpochHash, "last_block", claim.EpochLastBlock, "tx", txHash) delete(currClaims, key) } else { - s.Logger.Warn("claimer: expected claim in flight to be in currClaims.", + s.Logger.Warn("expected claim in flight to be in currClaims.", "tx", receipt.TxHash) } delete(s.claimsInFlight, key) @@ -208,7 +212,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { if prevClaimRow, ok := prevClaims[key]; ok { err := checkClaimsConstraint(&prevClaimRow, &currClaimRow) if err != nil { - s.Logger.Error("claimer: database mismatch", + s.Logger.Error("database mismatch", "prevClaim", prevClaimRow, "currClaim", currClaimRow, "err", err, @@ -227,7 +231,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { goto nextApp } if prevEvent == nil { - s.Logger.Error("claimer: missing event", + s.Logger.Error("missing event", "claim", prevClaimRow, "err", ErrMissingEvent, ) @@ -236,7 +240,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { goto nextApp } if !claimMatchesEvent(&prevClaimRow, prevEvent) { - s.Logger.Error("claimer: event mismatch", + s.Logger.Error("event mismatch", "claim", prevClaimRow, "event", prevEvent, "err", ErrEventMismatch, @@ -258,7 +262,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { if currEvent != nil { if !claimMatchesEvent(&currClaimRow, currEvent) { - s.Logger.Error("claimer: event mismatch", + s.Logger.Error("event mismatch", "claim", currClaimRow, "event", currEvent, "err", ErrEventMismatch, @@ -282,14 +286,14 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { errs = append(errs, err) goto nextApp } - s.Logger.Info("claimer: Submitting claim to blockchain", - "app", currClaimRow.AppContractAddress, - "claim", currClaimRow.EpochHash, + s.Logger.Info("Submitting claim to blockchain", + "app", currClaimRow.AppContractAddress, + "claim", currClaimRow.EpochHash, "last_block", currClaimRow.EpochLastBlock, ) s.claimsInFlight[currClaimRow.AppContractAddress] = txHash } - nextApp: + nextApp: } return errs } @@ -335,7 +339,7 @@ func checkClaimsConstraint(p *claimRow, c *claimRow) error { } func claimMatchesEvent(c *claimRow, e *claimSubmissionEvent) bool { - return c.AppContractAddress == e.AppContract && + return c.AppContractAddress == e.AppContract && c.EpochLastBlock == e.LastProcessedBlockNumber.Uint64() } diff --git a/internal/claimer/claimer_test.go b/internal/claimer/claimer_test.go index 0591b288f..f64d114af 100644 --- a/internal/claimer/claimer_test.go +++ b/internal/claimer/claimer_test.go @@ -112,11 +112,11 @@ func TestSubmitFirstClaim(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } var prevEvent *claimSubmissionEvent = nil @@ -148,18 +148,18 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } prevClaims := map[address]claimRow{ @@ -197,11 +197,11 @@ func TestSkipSubmitFirstClaim(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } var prevEvent *claimSubmissionEvent = nil @@ -234,18 +234,18 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } prevClaims := map[address]claimRow{ @@ -283,11 +283,11 @@ func TestInFlightCompleted(t *testing.T) { reqHash := common.HexToHash("0x10") txHash := common.HexToHash("0x100") currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } prevClaims := map[address]claimRow{} currClaims := map[address]claimRow{ @@ -300,7 +300,7 @@ func TestInFlightCompleted(t *testing.T) { m.On("pollTransaction", reqHash). Return(true, &types.Receipt{ ContractAddress: appContractAddress, - TxHash: txHash, + TxHash: txHash, }, nil) m.On("updateEpochWithSubmittedClaim", &currClaim, txHash). Return(nil) @@ -319,11 +319,11 @@ func TestUpdateFirstClaim(t *testing.T) { m := newServiceMock() appContractAddress := common.HexToAddress("0x01") currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } var nilEvent *claimSubmissionEvent = nil @@ -356,18 +356,18 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { m := newServiceMock() appContractAddress := common.HexToAddress("0x01") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } prevEvent := claimSubmissionEvent{ @@ -411,18 +411,18 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } prevClaims := map[address]claimRow{ @@ -454,18 +454,18 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { m := newServiceMock() appContractAddress := common.HexToAddress("0x01") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 3, - EpochFirstBlock: 30, - EpochLastBlock: 39, + EpochIndex: 3, + EpochFirstBlock: 30, + EpochLastBlock: 39, } prevEvent := claimSubmissionEvent{ @@ -500,18 +500,18 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") prevClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 2, - EpochFirstBlock: 20, - EpochLastBlock: 29, + EpochIndex: 2, + EpochFirstBlock: 20, + EpochLastBlock: 29, } currClaim := claimRow{ - AppContractAddress: appContractAddress, + AppContractAddress: appContractAddress, AppIConsensusAddress: appContractAddress, - EpochIndex: 1, - EpochFirstBlock: 10, - EpochLastBlock: 19, + EpochIndex: 1, + EpochFirstBlock: 10, + EpochLastBlock: 19, } prevClaims := map[address]claimRow{ @@ -537,4 +537,3 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { assert.Equal(t, len(errs), 1) assert.Equal(t, errs[0], ErrClaimMismatch) } - diff --git a/internal/claimer/side-effects.go b/internal/claimer/side-effects.go index 2b243072b..9ff7853f8 100644 --- a/internal/claimer/side-effects.go +++ b/internal/claimer/side-effects.go @@ -52,7 +52,7 @@ func (s *Service) selectClaimPairsPerApp() ( map[address]claimRow, error, ) { - computed, accepted, err := s.DBConn.SelectClaimPairsPerApp(s.Context) + computed, accepted, err := s.Repository.SelectClaimPairsPerApp(s.Context) if err != nil { s.Logger.Error("selectClaimPairsPerApp:failed", "service", s.Name, @@ -71,7 +71,7 @@ func (s *Service) updateEpochWithSubmittedClaim( claim *claimRow, txHash hash, ) error { - err := s.DBConn.UpdateEpochWithSubmittedClaim(s.Context, claim.EpochID, txHash) + err := s.Repository.UpdateEpochWithSubmittedClaim(s.Context, claim.EpochID, txHash) if err != nil { s.Logger.Error("updateEpochWithSubmittedClaim:failed", "service", s.Name, diff --git a/internal/config/config.go b/internal/config/config.go index aba2abfff..9ad9200fe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,39 +8,8 @@ package config import ( "fmt" "os" - - "github.com/cartesi/rollups-node/pkg/rollupsmachine/cartesimachine" ) -// NodeConfig contains all the Node variables. -// See the corresponding environment variable for the variable documentation. -type NodeConfig struct { - LogLevel LogLevel - LogPrettyEnabled bool - BlockchainID uint64 - BlockchainHttpEndpoint Redacted[string] - BlockchainWsEndpoint Redacted[string] - LegacyBlockchainEnabled bool - EvmReaderDefaultBlock DefaultBlock - EvmReaderRetryPolicyMaxRetries uint64 - EvmReaderRetryPolicyMaxDelay Duration - BlockchainBlockTimeout int - ContractsInputBoxAddress string - ContractsInputBoxDeploymentBlockNumber int64 - SnapshotDir string - PostgresEndpoint Redacted[string] - HttpAddress string - HttpPort int - FeatureClaimSubmissionEnabled bool - FeatureMachineHashCheckEnabled bool - Auth Auth - AdvancerPollingInterval Duration - ValidatorPollingInterval Duration - ClaimerPollingInterval Duration - // Temporary - MachineServerVerbosity cartesimachine.ServerVerbosity -} - // Auth is used to sign transactions. type Auth any @@ -70,38 +39,6 @@ func (r Redacted[T]) String() string { return "[REDACTED]" } -// FromEnv loads the config from environment variables. -func FromEnv() NodeConfig { - var config NodeConfig - config.LogLevel = GetLogLevel() - config.LogPrettyEnabled = GetLogPrettyEnabled() - config.BlockchainID = GetBlockchainId() - config.BlockchainHttpEndpoint = Redacted[string]{GetBlockchainHttpEndpoint()} - config.BlockchainWsEndpoint = Redacted[string]{GetBlockchainWsEndpoint()} - config.LegacyBlockchainEnabled = GetLegacyBlockchainEnabled() - config.EvmReaderDefaultBlock = GetEvmReaderDefaultBlock() - config.EvmReaderRetryPolicyMaxRetries = GetEvmReaderRetryPolicyMaxRetries() - config.EvmReaderRetryPolicyMaxDelay = GetEvmReaderRetryPolicyMaxDelay() - config.BlockchainBlockTimeout = GetBlockchainBlockTimeout() - config.ContractsInputBoxAddress = GetContractsInputBoxAddress() - config.ContractsInputBoxDeploymentBlockNumber = GetContractsInputBoxDeploymentBlockNumber() - config.SnapshotDir = GetSnapshotDir() - config.PostgresEndpoint = Redacted[string]{GetPostgresEndpoint()} - config.HttpAddress = GetHttpAddress() - config.HttpPort = GetHttpPort() - config.FeatureClaimSubmissionEnabled = GetFeatureClaimSubmissionEnabled() - config.FeatureMachineHashCheckEnabled = GetFeatureMachineHashCheckEnabled() - if config.FeatureClaimSubmissionEnabled { - config.Auth = AuthFromEnv() - } - config.AdvancerPollingInterval = GetAdvancerPollingInterval() - config.ValidatorPollingInterval = GetValidatorPollingInterval() - config.ClaimerPollingInterval = GetClaimerPollingInterval() - // Temporary. - config.MachineServerVerbosity = cartesimachine.ServerVerbosity(GetMachineServerVerbosity()) - return config -} - func AuthFromEnv() Auth { switch GetAuthKind() { case AuthKindPrivateKeyVar: diff --git a/internal/config/generate/Config.toml b/internal/config/generate/Config.toml index 9bdb12a0b..718f7e4bc 100644 --- a/internal/config/generate/Config.toml +++ b/internal/config/generate/Config.toml @@ -10,7 +10,7 @@ description = """ One of "debug", "info", "warn", "error".""" [logging.CARTESI_LOG_PRETTY_ENABLED] -default = "false" +default = "true" go-type = "bool" description = """ If set to true, the node will add colors to its log output.""" @@ -66,6 +66,12 @@ go-type = "Duration" description = """ How many seconds the node will wait before querying the database for new claims.""" +[rollups.CARTESI_MAX_STARTUP_TIME] +default = "5" +go-type = "Duration" +description = """ +How many seconds the node expects services take initializing before aborting.""" + # # Blockchain # diff --git a/internal/config/generated.go b/internal/config/generated.go index 259f04fdf..a798fc6b2 100644 --- a/internal/config/generated.go +++ b/internal/config/generated.go @@ -228,18 +228,6 @@ func GetBlockchainBlockTimeout() int { return val } -func GetBlockchainFinalityOffset() int { - s, ok := os.LookupEnv("CARTESI_BLOCKCHAIN_FINALITY_OFFSET") - if !ok { - s = "10" - } - val, err := toInt(s) - if err != nil { - panic(fmt.Sprintf("failed to parse CARTESI_BLOCKCHAIN_FINALITY_OFFSET: %v", err)) - } - return val -} - func GetBlockchainHttpEndpoint() string { s, ok := os.LookupEnv("CARTESI_BLOCKCHAIN_HTTP_ENDPOINT") if !ok { @@ -387,7 +375,7 @@ func GetLogLevel() LogLevel { func GetLogPrettyEnabled() bool { s, ok := os.LookupEnv("CARTESI_LOG_PRETTY_ENABLED") if !ok { - s = "false" + s = "true" } val, err := toBool(s) if err != nil { @@ -456,6 +444,18 @@ func GetEvmReaderRetryPolicyMaxRetries() uint64 { return val } +func GetMaxStartupTime() Duration { + s, ok := os.LookupEnv("CARTESI_MAX_STARTUP_TIME") + if !ok { + s = "5" + } + val, err := toDuration(s) + if err != nil { + panic(fmt.Sprintf("failed to parse CARTESI_MAX_STARTUP_TIME: %v", err)) + } + return val +} + func GetValidatorPollingInterval() Duration { s, ok := os.LookupEnv("CARTESI_VALIDATOR_POLLING_INTERVAL") if !ok { diff --git a/internal/evmreader/claim.go b/internal/evmreader/claim.go index a82688426..c0d257dcd 100644 --- a/internal/evmreader/claim.go +++ b/internal/evmreader/claim.go @@ -6,7 +6,6 @@ package evmreader import ( "cmp" "context" - "log/slog" . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" @@ -14,13 +13,13 @@ import ( "github.com/ethereum/go-ethereum/common" ) -func (r *EvmReader) checkForClaimStatus( +func (r *Service) checkForClaimStatus( ctx context.Context, apps []application, mostRecentBlockNumber uint64, ) { - slog.Debug("evmreader: Checking for new Claim Acceptance Events") + r.Logger.Debug("Checking for new Claim Acceptance Events") // Classify them by lastClaimCheck block appsIndexedByLastCheck := indexApps(keyByLastClaimCheck, apps) @@ -37,7 +36,7 @@ func (r *EvmReader) checkForClaimStatus( if mostRecentBlockNumber > lastClaimCheck { - slog.Debug("evmreader: Checking claim acceptance for applications", + r.Logger.Debug("Checking claim acceptance for applications", "apps", appAddresses, "last claim check block", lastClaimCheck, "most recent block", mostRecentBlockNumber) @@ -45,14 +44,14 @@ func (r *EvmReader) checkForClaimStatus( r.readAndUpdateClaims(ctx, apps, lastClaimCheck, mostRecentBlockNumber) } else if mostRecentBlockNumber < lastClaimCheck { - slog.Warn( - "evmreader: Not reading claim acceptance: most recent block is lower than the last processed one", //nolint:lll + r.Logger.Warn( + "Not reading claim acceptance: most recent block is lower than the last processed one", //nolint:lll "apps", appAddresses, "last claim check block", lastClaimCheck, "most recent block", mostRecentBlockNumber, ) } else { - slog.Warn("evmreader: Not reading claim acceptance: already checked the most recent blocks", + r.Logger.Warn("Not reading claim acceptance: already checked the most recent blocks", "apps", appAddresses, "last claim check block", lastClaimCheck, "most recent block", mostRecentBlockNumber, @@ -62,7 +61,7 @@ func (r *EvmReader) checkForClaimStatus( } } -func (r *EvmReader) readAndUpdateClaims( +func (r *Service) readAndUpdateClaims( ctx context.Context, apps []application, lastClaimCheck, mostRecentBlockNumber uint64, @@ -90,7 +89,7 @@ func (r *EvmReader) readAndUpdateClaims( appClaimAcceptanceEventMap, err := r.readClaimsAcceptance( ctx, consensusContract, appAddresses, lastClaimCheck+1, mostRecentBlockNumber) if err != nil { - slog.Error("evmreader: Error reading claim acceptance status", + r.Logger.Error("Error reading claim acceptance status", "apps", apps, "IConsensus", iConsensusAddress, "start", lastClaimCheck, @@ -110,14 +109,14 @@ func (r *EvmReader) readAndUpdateClaims( previousEpochs, err := r.repository.GetPreviousEpochsWithOpenClaims( ctx, app, claimAcceptance.LastProcessedBlockNumber.Uint64()) if err != nil { - slog.Error("evmreader: Error retrieving previous submitted claims", + r.Logger.Error("Error retrieving previous submitted claims", "app", app, "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), "error", err) continue APP_LOOP } if len(previousEpochs) > 0 { - slog.Error("evmreader: Application got 'not accepted' claims. It is in an invalid state", + r.Logger.Error("Application got 'not accepted' claims. It is in an invalid state", "claim last block", claimAcceptance.LastProcessedBlockNumber, "app", app) continue APP_LOOP @@ -130,7 +129,7 @@ func (r *EvmReader) readAndUpdateClaims( claimAcceptance.LastProcessedBlockNumber.Uint64()), app) if err != nil { - slog.Error("evmreader: Error retrieving Epoch", + r.Logger.Error("Error retrieving Epoch", "app", app, "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), "error", err) @@ -139,16 +138,16 @@ func (r *EvmReader) readAndUpdateClaims( // Check Epoch if epoch == nil { - slog.Error( - "evmreader: Found claim acceptance event for an unknown epoch. Application is in an invalid state", //nolint:lll + r.Logger.Error( + "Found claim acceptance event for an unknown epoch. Application is in an invalid state", //nolint:lll "app", app, "claim last block", claimAcceptance.LastProcessedBlockNumber, "hash", claimAcceptance.Claim) continue APP_LOOP } if epoch.ClaimHash == nil { - slog.Warn( - "evmreader: Found claim acceptance event, but claim hasn't been calculated yet", + r.Logger.Warn( + "Found claim acceptance event, but claim hasn't been calculated yet", "app", app, "lastBlock", claimAcceptance.LastProcessedBlockNumber, ) @@ -156,7 +155,7 @@ func (r *EvmReader) readAndUpdateClaims( } if claimAcceptance.Claim != *epoch.ClaimHash || claimAcceptance.LastProcessedBlockNumber.Uint64() != epoch.LastBlock { - slog.Error("evmreader: Accepted Claim does not match actual Claim. Application is in an invalid state", //nolint:lll + r.Logger.Error("Accepted Claim does not match actual Claim. Application is in an invalid state", //nolint:lll "app", app, "lastBlock", epoch.LastBlock, "hash", epoch.ClaimHash) @@ -164,7 +163,7 @@ func (r *EvmReader) readAndUpdateClaims( continue APP_LOOP } if epoch.Status == EpochStatusClaimAccepted { - slog.Debug("evmreader: Claim already accepted. Skipping", + r.Logger.Debug("Claim already accepted. Skipping", "app", app, "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), "claimStatus", epoch.Status, @@ -174,7 +173,7 @@ func (r *EvmReader) readAndUpdateClaims( if epoch.Status != EpochStatusClaimSubmitted { // this happens when running on latest. EvmReader can see the event before // the claim is marked as submitted by the claimer. - slog.Debug("evmreader: Claim status is not submitted. Skipping for now", + r.Logger.Debug("Claim status is not submitted. Skipping for now", "app", app, "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), "claimStatus", epoch.Status, @@ -183,7 +182,7 @@ func (r *EvmReader) readAndUpdateClaims( } // Update Epoch claim status - slog.Info("evmreader: Claim Accepted", + r.Logger.Info("Claim Accepted", "app", app, "lastBlock", epoch.LastBlock, "hash", epoch.ClaimHash, @@ -195,7 +194,7 @@ func (r *EvmReader) readAndUpdateClaims( err = r.repository.UpdateEpochs( ctx, app, []*Epoch{epoch}, claimAcceptance.Raw.BlockNumber) if err != nil { - slog.Error("evmreader: Error storing claims", "app", app, "error", err) + r.Logger.Error("Error storing claims", "app", app, "error", err) continue } } @@ -204,7 +203,7 @@ func (r *EvmReader) readAndUpdateClaims( } } -func (r *EvmReader) readClaimsAcceptance( +func (r *Service) readClaimsAcceptance( ctx context.Context, consensusContract ConsensusContract, appAddresses []common.Address, diff --git a/internal/evmreader/claim_test.go b/internal/evmreader/claim_test.go index ef9311e3f..beacc90cf 100644 --- a/internal/evmreader/claim_test.go +++ b/internal/evmreader/claim_test.go @@ -12,24 +12,15 @@ import ( . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" "github.com/cartesi/rollups-node/pkg/contracts/iinputbox" + "github.com/cartesi/rollups-node/pkg/service" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" ) func (s *EvmReaderSuite) TestNoClaimsAcceptance() { - wsClient := FakeWSEhtClient{} - - //New EVM Reader - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare repository s.repository.Unset("GetAllRunningApplications") @@ -115,7 +106,7 @@ func (s *EvmReaderSuite) TestNoClaimsAcceptance() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -154,15 +145,8 @@ func (s *EvmReaderSuite) TestReadClaimAcceptance() { //New EVM Reader wsClient := FakeWSEhtClient{} - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.contractFactory = contractFactory // Prepare Claims Acceptance Events @@ -266,7 +250,7 @@ func (s *EvmReaderSuite) TestReadClaimAcceptance() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -308,18 +292,13 @@ func (s *EvmReaderSuite) TestCheckClaimFails() { //New EVM Reader client := newMockEthClient() - wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + wsClient := &FakeWSEhtClient{} + s.evmReader.client = client + s.evmReader.wsClient = wsClient + s.evmReader.inputSource = inputBox + s.evmReader.repository = repository // Prepare Claims Acceptance Events @@ -414,7 +393,7 @@ func (s *EvmReaderSuite) TestCheckClaimFails() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(ctx, ready) + errChannel <- s.evmReader.Run(ctx, ready) }() select { @@ -458,15 +437,23 @@ func (s *EvmReaderSuite) TestCheckClaimFails() { wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + evmReader := Service{ + client: client, + wsClient: &wsClient, + inputSource: inputBox, + repository: repository, + inputBoxDeploymentBlock: 0x00, + defaultBlock: DefaultBlockStatusLatest, + contractFactory: contractFactory, + hasEnabledApps: true, + } + Create(&CreateInfo{ + MaxStartupTime: 5 * time.Second, + CreateInfo: service.CreateInfo{ + Name: "evm-reader", + Impl: &evmReader, + }, + }, &evmReader) // Prepare Claims Acceptance Events @@ -605,15 +592,10 @@ func (s *EvmReaderSuite) TestCheckClaimFails() { wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.client = client + s.evmReader.wsClient = &wsClient + s.evmReader.inputSource = inputBox + s.evmReader.repository = repository // Prepare Claims Acceptance Events @@ -702,7 +684,7 @@ func (s *EvmReaderSuite) TestCheckClaimFails() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(ctx, ready) + errChannel <- s.evmReader.Run(ctx, ready) }() select { diff --git a/internal/evmreader/evmreader.go b/internal/evmreader/evmreader.go index 72fc9d5a7..b1163bd58 100644 --- a/internal/evmreader/evmreader.go +++ b/internal/evmreader/evmreader.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "log/slog" "math/big" "time" @@ -30,21 +29,29 @@ import ( type CreateInfo struct { service.CreateInfo - model.EvmReaderPersistentConfig - DefaultBlockString string PostgresEndpoint config.Redacted[string] BlockchainHttpEndpoint config.Redacted[string] BlockchainWsEndpoint config.Redacted[string] Database *repository.Database MaxRetries uint64 MaxDelay time.Duration + MaxStartupTime time.Duration } type Service struct { service.Service - reader EvmReader + + client EthClient + wsClient EthWsClient + inputSource InputSource + repository EvmReaderRepository + contractFactory ContractFactory + inputBoxDeploymentBlock uint64 + defaultBlock DefaultBlock + epochLengthCache map[Address]uint64 + hasEnabledApps bool } func (c *CreateInfo) LoadEnv() { @@ -53,6 +60,9 @@ func (c *CreateInfo) LoadEnv() { c.MaxDelay = config.GetEvmReaderRetryPolicyMaxDelay() c.MaxRetries = config.GetEvmReaderRetryPolicyMaxRetries() c.PostgresEndpoint.Value = config.GetPostgresEndpoint() + c.LogLevel = service.LogLevel(config.GetLogLevel()) + c.LogPretty = config.GetLogPrettyEnabled() + c.MaxStartupTime = config.GetMaxStartupTime() // persistent c.DefaultBlock = config.GetEvmReaderDefaultBlock() @@ -69,50 +79,47 @@ func Create(c *CreateInfo, s *Service) error { return err } - c.DefaultBlock, err = config.ToDefaultBlockFromString(c.DefaultBlockString) - if err != nil { - return err - } + return service.WithTimeout(c.MaxStartupTime, func() error { + client, err := ethclient.DialContext(s.Context, c.BlockchainHttpEndpoint.Value) + if err != nil { + return err + } - client, err := ethclient.DialContext(s.Context, c.BlockchainHttpEndpoint.Value) - if err != nil { - return err - } + wsClient, err := ethclient.DialContext(s.Context, c.BlockchainWsEndpoint.Value) + if err != nil { + return err + } - wsClient, err := ethclient.DialContext(s.Context, c.BlockchainWsEndpoint.Value) - if err != nil { - return err - } + if c.Database == nil { + c.Database, err = repository.Connect(s.Context, c.PostgresEndpoint.Value, s.Logger) + if err != nil { + return err + } + } - if c.Database == nil { - c.Database, err = repository.Connect(s.Context, c.PostgresEndpoint.Value) + err = s.SetupPersistentConfig(s.Context, c.Database, &c.EvmReaderPersistentConfig) if err != nil { return err } - } - err = s.SetupPersistentConfig(s.Context, c.Database, &c.EvmReaderPersistentConfig) - if err != nil { - return err - } + inputSource, err := NewInputSourceAdapter(common.Address(c.InputBoxAddress), client) + if err != nil { + return err + } - inputSource, err := NewInputSourceAdapter(c.InputBoxAddress, client) - if err != nil { - return err - } + contractFactory := NewEvmReaderContractFactory(client, c.MaxRetries, c.MaxDelay) - contractFactory := NewEvmReaderContractFactory(client, c.MaxRetries, c.MaxDelay) - - s.reader = NewEvmReader( - NewEhtClientWithRetryPolicy(client, c.MaxRetries, c.MaxDelay), - NewEthWsClientWithRetryPolicy(wsClient, c.MaxRetries, c.MaxDelay), - NewInputSourceWithRetryPolicy(inputSource, c.MaxRetries, c.MaxDelay), - c.Database, - c.InputBoxDeploymentBlock, - c.DefaultBlock, - contractFactory, - ) - return nil + s.client = NewEhtClientWithRetryPolicy(client, c.MaxRetries, c.MaxDelay) + s.wsClient = NewEthWsClientWithRetryPolicy(wsClient, c.MaxRetries, c.MaxDelay) + s.inputSource = NewInputSourceWithRetryPolicy(inputSource, c.MaxRetries, c.MaxDelay) + s.repository = c.Database + s.inputBoxDeploymentBlock = c.InputBoxDeploymentBlock + s.defaultBlock = c.DefaultBlock + s.contractFactory = contractFactory + s.hasEnabledApps = true + + return nil + }) } func (s *Service) Alive() bool { @@ -135,18 +142,20 @@ func (s *Service) Tick() []error { return []error{} } -func (s *Service) Start(context context.Context, ready chan<- struct{}) error { - go s.reader.Run(s.Context, ready) - return s.Serve() +func (s *Service) Serve() error { + ready := make(chan struct{}, 1) + go s.Run(s.Context, ready) + return s.Service.Serve() } + func (s *Service) String() string { return s.Name } func (me *Service) SetupPersistentConfig( - ctx context.Context, + ctx context.Context, database *repository.Database, - c *model.EvmReaderPersistentConfig, + c *model.EvmReaderPersistentConfig, ) error { err := database.SelectEvmReaderConfig(ctx, c) if err == pgx.ErrNoRows { @@ -155,7 +164,7 @@ func (me *Service) SetupPersistentConfig( return err } } else if err == nil { - me.Logger.Info("Node was already configured. Using previous persistent config", "config", c) + me.Logger.Warn("Node was already configured. Using previous persistent config", "config", c) } else { me.Logger.Error("Could not retrieve persistent config from Database. %w", "error", err) } @@ -245,48 +254,7 @@ type application struct { consensusContract ConsensusContract } -// EvmReader reads Input Added, Claim Submitted and -// Output Executed events from the blockchain -type EvmReader struct { - client EthClient - wsClient EthWsClient - inputSource InputSource - repository EvmReaderRepository - contractFactory ContractFactory - inputBoxDeploymentBlock uint64 - defaultBlock DefaultBlock - epochLengthCache map[Address]uint64 - hasEnabledApps bool -} - -func (r *EvmReader) String() string { - return "evmreader" -} - -// Creates a new EvmReader -func NewEvmReader( - client EthClient, - wsClient EthWsClient, - inputSource InputSource, - repository EvmReaderRepository, - inputBoxDeploymentBlock uint64, - defaultBlock DefaultBlock, - contractFactory ContractFactory, -) EvmReader { - return EvmReader{ - client: client, - wsClient: wsClient, - inputSource: inputSource, - repository: repository, - inputBoxDeploymentBlock: inputBoxDeploymentBlock, - defaultBlock: defaultBlock, - contractFactory: contractFactory, - hasEnabledApps: true, - } -} - -func (r *EvmReader) Run(ctx context.Context, ready chan<- struct{}) error { - +func (r *Service) Run(ctx context.Context, ready chan struct{}) error { // Initialize epochLength cache r.epochLengthCache = make(map[Address]uint64) @@ -297,20 +265,20 @@ func (r *EvmReader) Run(ctx context.Context, ready chan<- struct{}) error { if _, ok := err.(*SubscriptionError); !ok { return err } - slog.Error(err.Error()) - slog.Info("evmreader: Restarting subscription") + r.Logger.Error(err.Error()) + r.Logger.Info("Restarting subscription") } } // watchForNewBlocks watches for new blocks and reads new inputs based on the // default block configuration, which have not been processed yet. -func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{}) error { +func (r *Service) watchForNewBlocks(ctx context.Context, ready chan<- struct{}) error { headers := make(chan *types.Header) sub, err := r.wsClient.SubscribeNewHead(ctx, headers) if err != nil { return fmt.Errorf("could not start subscription: %v", err) } - slog.Info("evmreader: Subscribed to new block events") + r.Logger.Info("Subscribed to new block events") ready <- struct{}{} defer sub.Unsubscribe() @@ -323,13 +291,13 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} case header := <-headers: // Every time a new block arrives - slog.Debug("evmreader: New block header received", "blockNumber", header.Number, "blockHash", header.Hash()) + r.Logger.Debug("New block header received", "blockNumber", header.Number, "blockHash", header.Hash()) - slog.Debug("evmreader: Retrieving enabled applications") + r.Logger.Debug("Retrieving enabled applications") // Get All Applications runningApps, err := r.repository.GetAllRunningApplications(ctx) if err != nil { - slog.Error("evmreader: Error retrieving running applications", + r.Logger.Error("Error retrieving running applications", "error", err, ) @@ -338,13 +306,13 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} if len(runningApps) == 0 { if r.hasEnabledApps { - slog.Info("evmreader: No registered applications enabled") + r.Logger.Info("No registered applications enabled") } r.hasEnabledApps = false continue } if !r.hasEnabledApps { - slog.Info("evmreader: Found enabled applications") + r.Logger.Info("Found enabled applications") } r.hasEnabledApps = true @@ -353,7 +321,7 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} for _, app := range runningApps { applicationContract, consensusContract, err := r.getAppContracts(app) if err != nil { - slog.Error("evmreader: Error retrieving application contracts", "app", app, "error", err) + r.Logger.Error("Error retrieving application contracts", "app", app, "error", err) continue } apps = append(apps, application{Application: app, @@ -362,7 +330,7 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} } if len(apps) == 0 { - slog.Info("evmreader: No correctly configured applications running") + r.Logger.Info("No correctly configured applications running") continue } @@ -373,14 +341,14 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} r.defaultBlock, ) if err != nil { - slog.Error("evmreader: Error fetching most recent block", + r.Logger.Error("Error fetching most recent block", "default block", r.defaultBlock, "error", err) continue } blockNumber = mostRecentHeader.Number.Uint64() - slog.Debug(fmt.Sprintf("evmreader: Using block %d and not %d because of commitment policy: %s", + r.Logger.Debug(fmt.Sprintf("Using block %d and not %d because of commitment policy: %s", mostRecentHeader.Number.Uint64(), header.Number.Uint64(), r.defaultBlock)) } @@ -396,7 +364,7 @@ func (r *EvmReader) watchForNewBlocks(ctx context.Context, ready chan<- struct{} // fetchMostRecentHeader fetches the most recent header up till the // given default block -func (r *EvmReader) fetchMostRecentHeader( +func (r *Service) fetchMostRecentHeader( ctx context.Context, defaultBlock DefaultBlock, ) (*types.Header, error) { @@ -431,7 +399,7 @@ func (r *EvmReader) fetchMostRecentHeader( // getAppContracts retrieves the ApplicationContract and ConsensusContract for a given Application. // Also validates if IConsensus configuration matches the blockchain registered one -func (r *EvmReader) getAppContracts(app Application, +func (r *Service) getAppContracts(app Application, ) (ApplicationContract, ConsensusContract, error) { applicationContract, err := r.contractFactory.NewApplication(app.ContractAddress) if err != nil { diff --git a/internal/evmreader/evmreader_test.go b/internal/evmreader/evmreader_test.go index 7a4a80753..6f6d0976f 100644 --- a/internal/evmreader/evmreader_test.go +++ b/internal/evmreader/evmreader_test.go @@ -17,6 +17,7 @@ import ( appcontract "github.com/cartesi/rollups-node/pkg/contracts/iapplication" "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" "github.com/cartesi/rollups-node/pkg/contracts/iinputbox" + "github.com/cartesi/rollups-node/pkg/service" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -73,7 +74,7 @@ type EvmReaderSuite struct { wsClient *MockEthClient inputBox *MockInputBox repository *MockRepository - evmReader *EvmReader + evmReader *Service contractFactory *MockEvmReaderContractFactory } @@ -107,23 +108,30 @@ func (s *EvmReaderSuite) TearDownSuite() { s.cancel() } -func (s *EvmReaderSuite) SetupTest() { - - s.client = newMockEthClient() - s.wsClient = s.client - s.inputBox = newMockInputBox() - s.repository = newMockRepository() - s.contractFactory = newEmvReaderContractFactory() - inputReader := NewEvmReader( - s.client, - s.wsClient, - s.inputBox, - s.repository, - 0, - DefaultBlockStatusLatest, - s.contractFactory, - ) - s.evmReader = &inputReader +func (me *EvmReaderSuite) SetupTest() { + me.client = newMockEthClient() + me.wsClient = me.client + me.inputBox = newMockInputBox() + me.repository = newMockRepository() + me.contractFactory = newEmvReaderContractFactory() + + me.evmReader = &Service{ + client: me.client, + wsClient: me.wsClient, + inputSource: me.inputBox, + repository: me.repository, + inputBoxDeploymentBlock: 0, + defaultBlock: DefaultBlockStatusLatest, + contractFactory: me.contractFactory, + hasEnabledApps: true, + } + c := CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "evm-reader", + Impl: me.evmReader, + }, + } + me.Require().NotNil(Create(&c, me.evmReader)) } // Service tests @@ -170,27 +178,19 @@ func (s *EvmReaderSuite) TestItFailsToSubscribeForNewInputsOnStart() { } func (s *EvmReaderSuite) TestItWrongIConsensus() { + s.SetupTest() consensusContract := &MockIConsensusContract{} - contractFactory := newEmvReaderContractFactory() - contractFactory.Unset("NewIConsensus") contractFactory.On("NewIConsensus", mock.Anything, ).Return(consensusContract, nil) wsClient := FakeWSEhtClient{} - - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 + s.evmReader.contractFactory = contractFactory // Prepare consensus claimEvent0 := &iconsensus.IConsensusClaimAcceptance{ @@ -229,7 +229,7 @@ func (s *EvmReaderSuite) TestItWrongIConsensus() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { diff --git a/internal/evmreader/input.go b/internal/evmreader/input.go index 9f169aa03..1a7bdda46 100644 --- a/internal/evmreader/input.go +++ b/internal/evmreader/input.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "log/slog" . "github.com/cartesi/rollups-node/internal/model" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -15,13 +14,13 @@ import ( ) // checkForNewInputs checks if is there new Inputs for all running Applications -func (r *EvmReader) checkForNewInputs( +func (r *Service) checkForNewInputs( ctx context.Context, apps []application, mostRecentBlockNumber uint64, ) { - slog.Debug("evmreader: Checking for new inputs") + r.Logger.Debug("Checking for new inputs") groupedApps := indexApps(byLastProcessedBlock, apps) @@ -37,7 +36,7 @@ func (r *EvmReader) checkForNewInputs( if mostRecentBlockNumber > lastProcessedBlock { - slog.Debug("evmreader: Checking inputs for applications", + r.Logger.Debug("Checking inputs for applications", "apps", appAddresses, "last processed block", lastProcessedBlock, "most recent block", mostRecentBlockNumber, @@ -49,7 +48,7 @@ func (r *EvmReader) checkForNewInputs( apps, ) if err != nil { - slog.Error("Error reading inputs", + r.Logger.Error("Error reading inputs", "apps", appAddresses, "last processed block", lastProcessedBlock, "most recent block", mostRecentBlockNumber, @@ -58,14 +57,14 @@ func (r *EvmReader) checkForNewInputs( continue } } else if mostRecentBlockNumber < lastProcessedBlock { - slog.Warn( - "evmreader: Not reading inputs: most recent block is lower than the last processed one", + r.Logger.Warn( + "Not reading inputs: most recent block is lower than the last processed one", "apps", appAddresses, "last processed block", lastProcessedBlock, "most recent block", mostRecentBlockNumber, ) } else { - slog.Info("evmreader: Not reading inputs: already checked the most recent blocks", + r.Logger.Info("Not reading inputs: already checked the most recent blocks", "apps", appAddresses, "last processed block", lastProcessedBlock, "most recent block", mostRecentBlockNumber, @@ -76,7 +75,7 @@ func (r *EvmReader) checkForNewInputs( // readAndStoreInputs reads, inputs from the InputSource given specific filter options, indexes // them into epochs and store the indexed inputs and epochs -func (r *EvmReader) readAndStoreInputs( +func (r *Service) readAndStoreInputs( ctx context.Context, startBlock uint64, endBlock uint64, @@ -89,7 +88,7 @@ func (r *EvmReader) readAndStoreInputs( // Get App EpochLength err := r.addAppEpochLengthIntoCache(app) if err != nil { - slog.Error("evmreader: Error adding epoch length into cache", + r.Logger.Error("Error adding epoch length into cache", "app", app.ContractAddress, "error", err) continue @@ -100,7 +99,7 @@ func (r *EvmReader) readAndStoreInputs( } if len(appsToProcess) == 0 { - slog.Warn("evmreader: No valid running applications") + r.Logger.Warn("No valid running applications") return nil } @@ -122,7 +121,7 @@ func (r *EvmReader) readAndStoreInputs( currentEpoch, err := r.repository.GetEpoch(ctx, calculateEpochIndex(epochLength, startBlock), address) if err != nil { - slog.Error("evmreader: Error retrieving existing current epoch", + r.Logger.Error("Error retrieving existing current epoch", "app", address, "error", err, ) @@ -131,7 +130,7 @@ func (r *EvmReader) readAndStoreInputs( // Check current epoch status if currentEpoch != nil && currentEpoch.Status != EpochStatusOpen { - slog.Error("evmreader: Current epoch is not open", + r.Logger.Error("Current epoch is not open", "app", address, "epoch_index", currentEpoch.Index, "status", currentEpoch.Status, @@ -150,7 +149,7 @@ func (r *EvmReader) readAndStoreInputs( // If input belongs into a new epoch, close the previous known one if currentEpoch != nil && currentEpoch.Index != inputEpochIndex { currentEpoch.Status = EpochStatusClosed - slog.Info("evmreader: Closing epoch", + r.Logger.Info("Closing epoch", "app", currentEpoch.AppAddress, "epoch_index", currentEpoch.Index, "start", currentEpoch.FirstBlock, @@ -168,7 +167,7 @@ func (r *EvmReader) readAndStoreInputs( epochInputMap[currentEpoch] = []Input{} } - slog.Info("evmreader: Found new Input", + r.Logger.Info("Found new Input", "app", address, "index", input.Index, "block", input.BlockNumber, @@ -185,7 +184,7 @@ func (r *EvmReader) readAndStoreInputs( // Indexed all inputs. Check if it is time to close this epoch if currentEpoch != nil && endBlock >= currentEpoch.LastBlock { currentEpoch.Status = EpochStatusClosed - slog.Info("evmreader: Closing epoch", + r.Logger.Info("Closing epoch", "app", currentEpoch.AppAddress, "epoch_index", currentEpoch.Index, "start", currentEpoch.FirstBlock, @@ -204,7 +203,7 @@ func (r *EvmReader) readAndStoreInputs( address, ) if err != nil { - slog.Error("evmreader: Error storing inputs and epochs", + r.Logger.Error("Error storing inputs and epochs", "app", address, "error", err, ) @@ -214,7 +213,7 @@ func (r *EvmReader) readAndStoreInputs( // Store everything if len(epochInputMap) > 0 { - slog.Debug("evmreader: Inputs and epochs stored successfully", + r.Logger.Debug("Inputs and epochs stored successfully", "app", address, "start-block", startBlock, "end-block", endBlock, @@ -222,7 +221,7 @@ func (r *EvmReader) readAndStoreInputs( "total inputs", len(inputs), ) } else { - slog.Debug("evmreader: No inputs or epochs to store") + r.Logger.Debug("No inputs or epochs to store") } } @@ -232,7 +231,7 @@ func (r *EvmReader) readAndStoreInputs( // addAppEpochLengthIntoCache checks the epoch length cache and read epoch length from IConsensus // contract and add it to the cache if needed -func (r *EvmReader) addAppEpochLengthIntoCache(app application) error { +func (r *Service) addAppEpochLengthIntoCache(app application) error { epochLength, ok := r.epochLengthCache[app.ContractAddress] if !ok { @@ -245,11 +244,11 @@ func (r *EvmReader) addAppEpochLengthIntoCache(app application) error { err) } r.epochLengthCache[app.ContractAddress] = epochLength - slog.Info("evmreader: Got epoch length from IConsensus", + r.Logger.Info("Got epoch length from IConsensus", "app", app.ContractAddress, "epoch length", epochLength) } else { - slog.Debug("evmreader: Got epoch length from cache", + r.Logger.Debug("Got epoch length from cache", "app", app.ContractAddress, "epoch length", epochLength) } @@ -258,7 +257,7 @@ func (r *EvmReader) addAppEpochLengthIntoCache(app application) error { } // readInputsFromBlockchain read the inputs from the blockchain ordered by Input index -func (r *EvmReader) readInputsFromBlockchain( +func (r *Service) readInputsFromBlockchain( ctx context.Context, appsAddresses []Address, startBlock, endBlock uint64, @@ -282,7 +281,7 @@ func (r *EvmReader) readInputsFromBlockchain( // Order inputs as order is not enforced by RetrieveInputs method nor the APIs for _, event := range inputsEvents { - slog.Debug("evmreader: Received input", + r.Logger.Debug("Received input", "app", event.AppContract, "index", event.Index, "block", event.Raw.BlockNumber) diff --git a/internal/evmreader/input_test.go b/internal/evmreader/input_test.go index 918a21f65..467eff1ce 100644 --- a/internal/evmreader/input_test.go +++ b/internal/evmreader/input_test.go @@ -14,18 +14,10 @@ import ( ) func (s *EvmReaderSuite) TestItReadsInputsFromNewBlocks() { - + //New EVM Reader wsClient := FakeWSEhtClient{} - - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare repository s.repository.Unset("GetAllRunningApplications") @@ -99,7 +91,7 @@ func (s *EvmReaderSuite) TestItReadsInputsFromNewBlocks() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -122,18 +114,9 @@ func (s *EvmReaderSuite) TestItReadsInputsFromNewBlocks() { } func (s *EvmReaderSuite) TestItUpdatesLastProcessedBlockWhenThereIsNoInputs() { - wsClient := FakeWSEhtClient{} - - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare repository s.repository.Unset("GetAllRunningApplications") @@ -207,7 +190,7 @@ func (s *EvmReaderSuite) TestItUpdatesLastProcessedBlockWhenThereIsNoInputs() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -231,17 +214,10 @@ func (s *EvmReaderSuite) TestItUpdatesLastProcessedBlockWhenThereIsNoInputs() { func (s *EvmReaderSuite) TestItReadsMultipleInputsFromSingleNewBlock() { + //New EVM Reader wsClient := FakeWSEhtClient{} - - inputReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare Client s.client.Unset("HeaderByNumber") @@ -302,7 +278,7 @@ func (s *EvmReaderSuite) TestItReadsMultipleInputsFromSingleNewBlock() { errChannel := make(chan error, 1) go func() { - errChannel <- inputReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -325,17 +301,10 @@ func (s *EvmReaderSuite) TestItReadsMultipleInputsFromSingleNewBlock() { } func (s *EvmReaderSuite) TestItStartsWhenLasProcessedBlockIsTheMostRecentBlock() { - + //New EVM Reader wsClient := FakeWSEhtClient{} - inputReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare Client s.client.Unset("HeaderByNumber") @@ -361,7 +330,7 @@ func (s *EvmReaderSuite) TestItStartsWhenLasProcessedBlockIsTheMostRecentBlock() errChannel := make(chan error, 1) go func() { - errChannel <- inputReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { diff --git a/internal/evmreader/output.go b/internal/evmreader/output.go index 5723a26c2..334717a22 100644 --- a/internal/evmreader/output.go +++ b/internal/evmreader/output.go @@ -6,13 +6,12 @@ package evmreader import ( "bytes" "context" - "log/slog" . "github.com/cartesi/rollups-node/internal/model" "github.com/ethereum/go-ethereum/accounts/abi/bind" ) -func (r *EvmReader) checkForOutputExecution( +func (r *Service) checkForOutputExecution( ctx context.Context, apps []application, mostRecentBlockNumber uint64, @@ -20,7 +19,7 @@ func (r *EvmReader) checkForOutputExecution( appAddresses := appsToAddresses(apps) - slog.Debug("evmreader: Checking for new Output Executed Events", "apps", appAddresses) + r.Logger.Debug("Checking for new Output Executed Events", "apps", appAddresses) for _, app := range apps { @@ -34,7 +33,7 @@ func (r *EvmReader) checkForOutputExecution( if mostRecentBlockNumber > LastOutputCheck { - slog.Debug("evmreader: Checking output execution for application", + r.Logger.Debug("Checking output execution for application", "app", app.ContractAddress, "last output check block", LastOutputCheck, "most recent block", mostRecentBlockNumber) @@ -42,14 +41,14 @@ func (r *EvmReader) checkForOutputExecution( r.readAndUpdateOutputs(ctx, app, LastOutputCheck, mostRecentBlockNumber) } else if mostRecentBlockNumber < LastOutputCheck { - slog.Warn( - "evmreader: Not reading output execution: most recent block is lower than the last processed one", //nolint:lll + r.Logger.Warn( + "Not reading output execution: most recent block is lower than the last processed one", //nolint:lll "app", app.ContractAddress, "last output check block", LastOutputCheck, "most recent block", mostRecentBlockNumber, ) } else { - slog.Warn("evmreader: Not reading output execution: already checked the most recent blocks", + r.Logger.Warn("Not reading output execution: already checked the most recent blocks", "app", app.ContractAddress, "last output check block", LastOutputCheck, "most recent block", mostRecentBlockNumber, @@ -59,7 +58,7 @@ func (r *EvmReader) checkForOutputExecution( } -func (r *EvmReader) readAndUpdateOutputs( +func (r *Service) readAndUpdateOutputs( ctx context.Context, app application, lastOutputCheck, mostRecentBlockNumber uint64) { contract := app.applicationContract @@ -71,7 +70,7 @@ func (r *EvmReader) readAndUpdateOutputs( outputExecutedEvents, err := contract.RetrieveOutputExecutionEvents(opts) if err != nil { - slog.Error("evmreader: Error reading output events", "app", app.ContractAddress, "error", err) + r.Logger.Error("Error reading output events", "app", app.ContractAddress, "error", err) return } @@ -82,30 +81,30 @@ func (r *EvmReader) readAndUpdateOutputs( // Compare output to check it is the correct one output, err := r.repository.GetOutput(ctx, app.ContractAddress, event.OutputIndex) if err != nil { - slog.Error("evmreader: Error retrieving output", + r.Logger.Error("Error retrieving output", "app", app.ContractAddress, "index", event.OutputIndex, "error", err) return } if output == nil { - slog.Warn("evmreader: Found OutputExecuted event but output does not exist in the database yet", + r.Logger.Warn("Found OutputExecuted event but output does not exist in the database yet", "app", app.ContractAddress, "index", event.OutputIndex) return } if !bytes.Equal(output.RawData, event.Output) { - slog.Debug("evmreader: Output mismatch", + r.Logger.Debug("Output mismatch", "app", app.ContractAddress, "index", event.OutputIndex, "actual", output.RawData, "event's", event.Output) - slog.Error("evmreader: Output mismatch. Application is in an invalid state", + r.Logger.Error("Output mismatch. Application is in an invalid state", "app", app.ContractAddress, "index", event.OutputIndex) return } - slog.Info("evmreader: Output executed", "app", app.ContractAddress, "index", event.OutputIndex) + r.Logger.Info("Output executed", "app", app.ContractAddress, "index", event.OutputIndex) output.TransactionHash = &event.Raw.TxHash executedOutputs = append(executedOutputs, output) } @@ -113,7 +112,7 @@ func (r *EvmReader) readAndUpdateOutputs( err = r.repository.UpdateOutputExecutionTransaction( ctx, app.ContractAddress, executedOutputs, mostRecentBlockNumber) if err != nil { - slog.Error("evmreader: Error storing output execution statuses", "app", app, "error", err) + r.Logger.Error("Error storing output execution statuses", "app", app, "error", err) } } diff --git a/internal/evmreader/output_test.go b/internal/evmreader/output_test.go index c05a6c585..143068b0c 100644 --- a/internal/evmreader/output_test.go +++ b/internal/evmreader/output_test.go @@ -11,25 +11,16 @@ import ( . "github.com/cartesi/rollups-node/internal/model" appcontract "github.com/cartesi/rollups-node/pkg/contracts/iapplication" "github.com/cartesi/rollups-node/pkg/contracts/iinputbox" + "github.com/cartesi/rollups-node/pkg/service" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/mock" ) func (s *EvmReaderSuite) TestOutputExecution() { - wsClient := FakeWSEhtClient{} - - //New EVM Reader - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x10, - DefaultBlockStatusLatest, - s.contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.inputBoxDeploymentBlock = 0x10 // Prepare repository s.repository.Unset("GetAllRunningApplications") @@ -112,7 +103,7 @@ func (s *EvmReaderSuite) TestOutputExecution() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -151,15 +142,8 @@ func (s *EvmReaderSuite) TestReadOutputExecution() { //New EVM Reader wsClient := FakeWSEhtClient{} - evmReader := NewEvmReader( - s.client, - &wsClient, - s.inputBox, - s.repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.wsClient = &wsClient + s.evmReader.contractFactory = contractFactory // Prepare Output Executed Events outputExecution0 := &appcontract.IApplicationOutputExecuted{ @@ -240,7 +224,7 @@ func (s *EvmReaderSuite) TestReadOutputExecution() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(s.ctx, ready) + errChannel <- s.evmReader.Run(s.ctx, ready) }() select { @@ -263,8 +247,8 @@ func (s *EvmReaderSuite) TestReadOutputExecution() { func (s *EvmReaderSuite) TestCheckOutputFails() { s.Run("whenRetrieveOutputsFails", func() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + //ctx, cancel := context.WithCancel(context.Background()) + //defer cancel() appAddress := common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E") @@ -284,15 +268,17 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + evmReader := Service{ + client: client, + wsClient: &wsClient, + inputSource: inputBox, + repository: repository, + inputBoxDeploymentBlock: 0x00, + defaultBlock: DefaultBlockStatusLatest, + contractFactory: contractFactory, + hasEnabledApps: true, + } + service.Create(&service.CreateInfo{}, &evmReader.Service) applicationContract.On("RetrieveOutputExecutionEvents", mock.Anything, @@ -348,29 +334,29 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { mock.Anything, ).Return(&header0, nil).Once() - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) + //// Start service + //ready := make(chan struct{}, 1) + //errChannel := make(chan error, 1) - go func() { - errChannel <- evmReader.Run(ctx, ready) - }() + //go func() { + // errChannel <- evmReader.Run(ctx, ready) + //}() - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } + //select { + //case <-ready: + // break + //case err := <-errChannel: + // s.FailNow("unexpected error signal", err) + //} - wsClient.fireNewHead(&header0) - time.Sleep(1 * time.Second) + //wsClient.fireNewHead(&header0) + //time.Sleep(1 * time.Second) - s.repository.AssertNumberOfCalls( - s.T(), - "UpdateOutputExecutionTransaction", - 0, - ) + //s.repository.AssertNumberOfCalls( + // s.T(), + // "UpdateOutputExecutionTransaction", + // 0, + //) }) @@ -396,15 +382,11 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.client = client + s.evmReader.wsClient = &wsClient + s.evmReader.inputSource = inputBox + s.evmReader.repository = repository + s.evmReader.contractFactory = contractFactory // Prepare Output Executed Events outputExecution0 := &appcontract.IApplicationOutputExecuted{ @@ -470,7 +452,7 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(ctx, ready) + errChannel <- s.evmReader.Run(ctx, ready) }() select { @@ -513,15 +495,11 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { wsClient := FakeWSEhtClient{} inputBox := newMockInputBox() repository := newMockRepository() - evmReader := NewEvmReader( - client, - &wsClient, - inputBox, - repository, - 0x00, - DefaultBlockStatusLatest, - contractFactory, - ) + s.evmReader.client = client + s.evmReader.wsClient = &wsClient + s.evmReader.inputSource = inputBox + s.evmReader.repository = repository + s.evmReader.contractFactory = contractFactory // Prepare Output Executed Events outputExecution0 := &appcontract.IApplicationOutputExecuted{ @@ -592,7 +570,7 @@ func (s *EvmReaderSuite) TestCheckOutputFails() { errChannel := make(chan error, 1) go func() { - errChannel <- evmReader.Run(ctx, ready) + errChannel <- s.evmReader.Run(ctx, ready) }() select { diff --git a/internal/inspect/inspect.go b/internal/inspect/inspect.go index bef157b24..231dc32eb 100644 --- a/internal/inspect/inspect.go +++ b/internal/inspect/inspect.go @@ -27,6 +27,7 @@ var ( type Inspector struct { IInspectMachines + Logger *slog.Logger } type ReportResponse struct { @@ -51,7 +52,7 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) { ) if r.PathValue("dapp") == "" { - slog.Info("inspect: Bad request", + inspect.Logger.Info("inspect: Bad request", "err", "Missing application address") http.Error(w, "Missing application address", http.StatusBadRequest) return @@ -61,21 +62,21 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { payload, err = io.ReadAll(r.Body) if err != nil { - slog.Info("inspect: Bad request", + inspect.Logger.Info("inspect: Bad request", "err", err) http.Error(w, err.Error(), http.StatusBadRequest) return } } else { if r.PathValue("payload") == "" { - slog.Info("inspect: Bad request", + inspect.Logger.Info("inspect: Bad request", "err", "Missing payload") http.Error(w, "Missing payload", http.StatusBadRequest) return } decodedValue, err := url.PathUnescape(r.PathValue("payload")) if err != nil { - slog.Error("inspect: Internal server error", + inspect.Logger.Error("inspect: Internal server error", "err", err) http.Error(w, err.Error(), http.StatusBadRequest) return @@ -83,15 +84,15 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) { payload = []byte(decodedValue) } - slog.Info("inspect: Got new inspect request", "application", dapp.String()) + inspect.Logger.Info("inspect: Got new inspect request", "application", dapp.String()) result, err := inspect.process(r.Context(), dapp, payload) if err != nil { if errors.Is(err, ErrNoApp) { - slog.Error("inspect: Application not found", "address", dapp, "err", err) + inspect.Logger.Error("inspect: Application not found", "address", dapp, "err", err) http.Error(w, "Application not found", http.StatusNotFound) return } - slog.Error("inspect: Internal server error", "err", err) + inspect.Logger.Error("inspect: Internal server error", "err", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -121,12 +122,12 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(response) if err != nil { - slog.Error("inspect: Internal server error", + inspect.Logger.Error("inspect: Internal server error", "err", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - slog.Info("inspect: Request executed", + inspect.Logger.Info("inspect: Request executed", "status", status, "application", dapp.String()) } diff --git a/internal/inspect/inspect_test.go b/internal/inspect/inspect_test.go index be5e6f9e8..3c8bc64b6 100644 --- a/internal/inspect/inspect_test.go +++ b/internal/inspect/inspect_test.go @@ -4,6 +4,7 @@ package inspect import ( + "log/slog" "bytes" "context" crand "crypto/rand" @@ -20,6 +21,7 @@ import ( . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/internal/nodemachine" "github.com/cartesi/rollups-node/internal/services" + "github.com/cartesi/rollups-node/pkg/service" "github.com/stretchr/testify/suite" ) @@ -53,12 +55,12 @@ func (s *InspectSuite) TestGetOk() { router := http.NewServeMux() router.Handle("/inspect/{dapp}/{payload}", inspect) - service := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} + httpService := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- httpService.Start(ctx, ready, service.NewLogger(slog.LevelDebug, true)) }() select { @@ -85,12 +87,12 @@ func (s *InspectSuite) TestGetInvalidPayload() { router := http.NewServeMux() router.Handle("/inspect/{dapp}/{payload}", inspect) - service := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} + httpService := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- httpService.Start(ctx, ready, service.NewLogger(slog.LevelDebug, true)) }() select { @@ -118,12 +120,12 @@ func (s *InspectSuite) TestPostOk() { router := http.NewServeMux() router.Handle("/inspect/{dapp}", inspect) - service := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} + httpService := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router} result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- httpService.Start(ctx, ready, service.NewLogger(slog.LevelDebug, true)) }() select { @@ -147,7 +149,10 @@ func (s *InspectSuite) setup() (*Inspector, Address, Hash) { app := randomAddress() machines := newMockMachines() machines.Map[app] = &MockMachine{} - inspect := &Inspector{machines} + inspect := &Inspector{ + IInspectMachines: machines, + Logger: service.NewLogger(slog.LevelDebug, true), + } payload := randomHash() return inspect, app, payload } diff --git a/internal/node/chainid.go b/internal/node/chainid.go deleted file mode 100644 index 12e2a46c0..000000000 --- a/internal/node/chainid.go +++ /dev/null @@ -1,45 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package node - -import ( - "context" - "fmt" - "time" - - "github.com/ethereum/go-ethereum/ethclient" -) - -const defaultTimeout = 45 * time.Second - -// Checks if the chain id from the configuration matches the chain id reported -// by the Ethereum node. If they don't, it returns an error. -func validateChainId(ctx context.Context, chainId uint64, ethereumNodeAddr string) error { - remoteChainId, err := getChainId(ctx, ethereumNodeAddr) - if err != nil { - return err - } else if chainId != remoteChainId { - return fmt.Errorf( - "chainId mismatch; expected %v but Ethereum node returned %v", - chainId, - remoteChainId, - ) - } - return nil -} - -func getChainId(ctx context.Context, ethereumNodeAddr string) (uint64, error) { - ctx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - - client, err := ethclient.Dial(ethereumNodeAddr) - if err != nil { - return 0, fmt.Errorf("create RPC client: %w", err) - } - chainId, err := client.ChainID(ctx) - if err != nil { - return 0, fmt.Errorf("get chain id: %w", err) - } - return chainId.Uint64(), nil -} diff --git a/internal/node/chainid_test.go b/internal/node/chainid_test.go deleted file mode 100644 index 9e4fb9d0b..000000000 --- a/internal/node/chainid_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package node - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/suite" -) - -type ValidateChainIdSuite struct { - suite.Suite -} - -func TestValidateChainId(t *testing.T) { - suite.Run(t, new(ValidateChainIdSuite)) -} - -func (s *ValidateChainIdSuite) TestItFailsIfChainIdsDoNotMatch() { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprintln(w, `{"jsonrpc":"2.0","id":67,"result":"0x7a69"}`) - })) - defer ts.Close() - localChainId := uint64(11111) - - err := validateChainId(context.Background(), localChainId, ts.URL) - - s.NotNil(err) -} - -func (s *ValidateChainIdSuite) TestItReturnsNilIfChainIdsMatch() { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprintln(w, `{"jsonrpc":"2.0","id":67,"result":"0x7a69"}`) - })) - defer ts.Close() - localChainId := uint64(31337) - - err := validateChainId(context.Background(), localChainId, ts.URL) - - s.Nil(err) -} diff --git a/internal/node/handlers.go b/internal/node/handlers.go deleted file mode 100644 index 7e197b70b..000000000 --- a/internal/node/handlers.go +++ /dev/null @@ -1,14 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package node - -import ( - "log/slog" - "net/http" -) - -func healthcheckHandler(w http.ResponseWriter, r *http.Request) { - slog.Debug("Node received a healthcheck request") - w.WriteHeader(http.StatusOK) -} diff --git a/internal/node/node.go b/internal/node/node.go index d5f41c8ad..88e927688 100644 --- a/internal/node/node.go +++ b/internal/node/node.go @@ -4,25 +4,281 @@ package node import ( - "context" + "fmt" + "log/slog" + "net/http" + "time" + "github.com/cartesi/rollups-node/pkg/service" + + "github.com/cartesi/rollups-node/internal/advancer" + "github.com/cartesi/rollups-node/internal/claimer" "github.com/cartesi/rollups-node/internal/config" + "github.com/cartesi/rollups-node/internal/evmreader" + "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/internal/repository" - "github.com/cartesi/rollups-node/internal/services" + "github.com/cartesi/rollups-node/internal/validator" + + "github.com/ethereum/go-ethereum/ethclient" ) -// Setup creates the Node top-level supervisor. -func Setup( - ctx context.Context, - c config.NodeConfig, +type CreateInfo struct { + service.CreateInfo + + BlockchainHttpEndpoint config.Redacted[string] + BlockchainID uint64 + HTTPEndpoint config.Redacted[string] + PostgresEndpoint config.Redacted[string] + EnableClaimSubmission bool + MaxStartupTime time.Duration +} + +type Service struct { + service.Service + + Children []service.IService + Client *ethclient.Client + Repository *repository.Database +} + +func (c *CreateInfo) LoadEnv() { + c.BlockchainHttpEndpoint = config.Redacted[string]{config.GetBlockchainHttpEndpoint()} + c.BlockchainID = config.GetBlockchainId() + c.EnableClaimSubmission = config.GetFeatureClaimSubmissionEnabled() + c.PostgresEndpoint = config.Redacted[string]{config.GetPostgresEndpoint()} + c.MaxStartupTime = config.GetMaxStartupTime() + c.LogLevel = service.LogLevel(config.GetLogLevel()) + c.LogPretty = config.GetLogPrettyEnabled() + + httpAddress := config.GetHttpAddress() + httpPort := config.GetHttpPort() + c.HTTPEndpoint.Value = fmt.Sprintf("%v:%v", httpAddress, httpPort) +} + +func Create(c *CreateInfo, s *Service) error { + var err error + + err = service.Create(&c.CreateInfo, &s.Service) + if err != nil { + return err + } + + err = service.WithTimeout(c.MaxStartupTime, func() error { + // database connection + s.Repository, err = repository.Connect(s.Context, c.PostgresEndpoint.Value, s.Logger) + if err != nil { + return err + } + + // blockchain connection + chainID check + s.Client, err = ethclient.Dial(c.BlockchainHttpEndpoint.Value) + if err != nil { + return err + } + chainID, err := s.Client.ChainID(s.Context) + if err != nil { + return err + } + if c.BlockchainID != chainID.Uint64() { + return fmt.Errorf( + "chainId mismatch; got: %v, expected: %v", + chainID, + c.BlockchainID, + ) + } + return nil + }) + + if err != nil { + s.Logger.Error(fmt.Sprint(err)) + return err + } + return createServices(c, s) +} + +func createServices(c *CreateInfo, s *Service) error { + ch := make(chan service.IService) + deadline := time.After(c.MaxStartupTime) + numChildren := 0 + + numChildren++ + go func() { + ch <- newEVMReader(c, s.Logger, s.Repository) + }() + + numChildren++ + go func() { + ch <- newAdvancer(c, s.Logger, s.Repository, s.ServeMux) + }() + + numChildren++ + go func() { + ch <- newValidator(c, s.Logger, s.Repository) + }() + + numChildren++ + go func() { + ch <- newClaimer(c, s.Logger, s.Repository) + }() + + for range numChildren { + select { + case child := <-ch: + s.Children = append(s.Children, child) + case <-deadline: + s.Logger.Error("Failed to create services. Time limit exceded", + "limit", c.MaxStartupTime) + return fmt.Errorf("Failed to create services. Time limit exceded") + } + } + return nil +} + +func (me *Service) Alive() bool { + allAlive := true + for _, s := range me.Children { + allAlive = allAlive && s.Alive() + } + return allAlive +} + +func (me *Service) Ready() bool { + allReady := true + for _, s := range me.Children { + allReady = allReady && s.Ready() + } + return allReady +} + +func (s *Service) Reload() []error { return nil } +func (s *Service) Tick() []error { return nil } +func (me *Service) Stop(force bool) []error { + errs := []error{} + for _, s := range me.Children { + errs = append(errs, s.Stop(force)...) + } + return errs +} + +func (me *Service) Serve() error { + for _, s := range me.Children { + go s.Serve() + } + return me.Service.Serve() +} + +// services creation + +func newEVMReader( + nc *CreateInfo, + logger *slog.Logger, + database *repository.Database, +) service.IService { + s := evmreader.Service{} + c := evmreader.CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "evm-reader", + Impl: &s, + ProcOwner: true, // TODO: Remove this after updating supervisor + }, + EvmReaderPersistentConfig: model.EvmReaderPersistentConfig{ + DefaultBlock: model.DefaultBlockStatusSafe, + }, + Database: database, + } + c.LoadEnv() + c.LogLevel = nc.LogLevel + c.LogPretty = nc.LogPretty + + err := evmreader.Create(&c, &s) + if err != nil { + logger.Error("Fatal", + "error", err) + panic(err) + } + return &s +} + +func newAdvancer( + nc *CreateInfo, + logger *slog.Logger, + database *repository.Database, + serveMux *http.ServeMux, +) service.IService { + s := advancer.Service{} + c := advancer.CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "advancer", + Impl: &s, + ProcOwner: true, + ServeMux: serveMux, + }, + Repository: database, + } + c.LoadEnv() + c.LogLevel = nc.LogLevel + c.LogPretty = nc.LogPretty + + err := advancer.Create(&c, &s) + if err != nil { + logger.Error("Fatal", + "error", err) + panic(err) + } + return &s +} + +func newValidator( + nc *CreateInfo, + logger *slog.Logger, database *repository.Database, -) (services.Service, error) { - // checks - err := validateChainId(ctx, c.BlockchainID, c.BlockchainHttpEndpoint.Value) +) service.IService { + s := validator.Service{} + c := validator.CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "validator", + Impl: &s, + ProcOwner: true, + }, + Repository: database, + } + c.LoadEnv() + c.LogLevel = nc.LogLevel + c.LogPretty = nc.LogPretty + + err := validator.Create(&c, &s) if err != nil { - return nil, err + slog.Error("Fatal", + "error", err) + panic(err) + } + return &s +} + +func newClaimer( + nc *CreateInfo, + logger *slog.Logger, + database *repository.Database, +) service.IService { + s := claimer.Service{} + c := claimer.CreateInfo{ + CreateInfo: service.CreateInfo{ + Name: "claimer", + Impl: &s, + ProcOwner: true, + }, + Repository: database, } + c.LoadEnv() + c.LogLevel = nc.LogLevel + c.LogPretty = nc.LogPretty + c.EnableSubmission = nc.EnableClaimSubmission - // create service - return newSupervisorService(c, database), nil + err := claimer.Create(&c, &s) + if err != nil { + logger.Error("Fatal", + "error", err) + panic(err) + } + return &s } diff --git a/internal/node/services.go b/internal/node/services.go deleted file mode 100644 index 65e154fae..000000000 --- a/internal/node/services.go +++ /dev/null @@ -1,153 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package node - -import ( - "fmt" - "net/http" - - advancerservice "github.com/cartesi/rollups-node/internal/advancer" - claimerservice "github.com/cartesi/rollups-node/internal/claimer" - "github.com/cartesi/rollups-node/internal/config" - readerservice "github.com/cartesi/rollups-node/internal/evmreader" - "github.com/cartesi/rollups-node/internal/repository" - "github.com/cartesi/rollups-node/internal/services" - validatorservice "github.com/cartesi/rollups-node/internal/validator" - "github.com/cartesi/rollups-node/pkg/service" -) - -// We use an enum to define the ports of each service and avoid conflicts. -type portOffset = int - -const ( - portOffsetProxy = iota -) - -// Get the port of the given service. -func getPort(c config.NodeConfig, offset portOffset) int { - return c.HttpPort + int(offset) -} - -func newSupervisorService( - c config.NodeConfig, - database *repository.Database, -) *services.SupervisorService { - var s []services.Service - - serveMux := http.NewServeMux() - serveMux.Handle("/healthz", http.HandlerFunc(healthcheckHandler)) - - s = append(s, newClaimerService(c, database)) - s = append(s, newEvmReaderService(c, database)) - s = append(s, newAdvancerService(c, database, serveMux)) - s = append(s, newValidatorService(c, database)) - s = append(s, newHttpService(c, serveMux)) - - supervisor := services.SupervisorService{ - Name: "rollups-node", - Services: s, - } - return &supervisor -} - -func newHttpService(c config.NodeConfig, serveMux *http.ServeMux) services.Service { - addr := fmt.Sprintf("%v:%v", c.HttpAddress, getPort(c, portOffsetProxy)) - return &services.HttpService{ - Name: "http", - Address: addr, - Handler: serveMux, - } -} - -func newEvmReaderService(c config.NodeConfig, database *repository.Database) services.Service { - readerService := readerservice.Service{} - createInfo := readerservice.CreateInfo{ - CreateInfo: service.CreateInfo{ - Name: "reader", - Impl: &readerService, - ProcOwner: true, // TODO: Remove this after updating supervisor - LogLevel: service.LogLevel(c.LogLevel), - }, - Database: database, - } - - err := readerservice.Create(&createInfo, &readerService) - if err != nil { - readerService.Logger.Error("Fatal", - "service", readerService.Name, - "error", err) - } - return &readerService -} - -func newAdvancerService(c config.NodeConfig, database *repository.Database, serveMux *http.ServeMux) services.Service { - advancerService := advancerservice.Service{} - createInfo := advancerservice.CreateInfo{ - CreateInfo: service.CreateInfo{ - Name: "advancer", - PollInterval: c.AdvancerPollingInterval, - Impl: &advancerService, - ProcOwner: true, // TODO: Remove this after updating supervisor - LogLevel: service.LogLevel(c.LogLevel), - ServeMux: serveMux, - }, - Repository: database, - } - - err := advancerservice.Create(&createInfo, &advancerService) - if err != nil { - advancerService.Logger.Error("Fatal", - "service", advancerService.Name, - "error", err) - } - return &advancerService -} - -func newValidatorService(c config.NodeConfig, database *repository.Database) services.Service { - validatorService := validatorservice.Service{} - createInfo := validatorservice.CreateInfo{ - CreateInfo: service.CreateInfo{ - Name: "validator", - PollInterval: c.ValidatorPollingInterval, - Impl: &validatorService, - ProcOwner: true, // TODO: Remove this after updating supervisor - LogLevel: service.LogLevel(c.LogLevel), - }, - Repository: database, - } - - err := validatorservice.Create(createInfo, &validatorService) - if err != nil { - validatorService.Logger.Error("Fatal", - "service", validatorService.Name, - "error", err) - } - return &validatorService -} - -func newClaimerService(c config.NodeConfig, database *repository.Database) services.Service { - claimerService := claimerservice.Service{} - createInfo := claimerservice.CreateInfo{ - Auth: c.Auth, - DBConn: database, - PostgresEndpoint: c.PostgresEndpoint, - BlockchainHttpEndpoint: c.BlockchainHttpEndpoint, - EnableSubmission: c.FeatureClaimSubmissionEnabled, - CreateInfo: service.CreateInfo{ - Name: "claimer", - PollInterval: c.ClaimerPollingInterval, - Impl: &claimerService, - ProcOwner: true, // TODO: Remove this after updating supervisor - LogLevel: service.LogLevel(c.LogLevel), - }, - } - - err := claimerservice.Create(createInfo, &claimerService) - if err != nil { - claimerService.Logger.Error("Fatal", - "service", claimerService.Name, - "error", err) - } - return &claimerService -} diff --git a/internal/repository/base.go b/internal/repository/base.go index 64862861e..d047441de 100644 --- a/internal/repository/base.go +++ b/internal/repository/base.go @@ -19,6 +19,7 @@ import ( type Database struct { db *pgxpool.Pool + Logger *slog.Logger } var ( @@ -58,6 +59,7 @@ func ValidateSchemaWithRetry(endpoint string, maxRetries int, delay time.Duratio func Connect( ctx context.Context, postgresEndpoint string, + logger *slog.Logger, ) (*Database, error) { var ( pgError error @@ -77,7 +79,10 @@ func Connect( return } - pgInstance = &Database{dbpool} + pgInstance = &Database{ + db: dbpool, + Logger: logger, + } }) return pgInstance, pgError @@ -399,7 +404,7 @@ func (pg *Database) GetApplication( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetApplication returned no rows", + pg.Logger.Debug("GetApplication returned no rows", "service", "repository", "app", appAddressKey) return nil, nil @@ -446,7 +451,7 @@ func (pg *Database) UpdateApplicationStatus( } if commandTag.RowsAffected() == 0 { - slog.Debug("UpdateApplicationStatus affected no rows", + pg.Logger.Debug("UpdateApplicationStatus affected no rows", "service", "repository", "app", appAddressKey) return fmt.Errorf("no application found with contract address: %s", appAddressKey) @@ -561,7 +566,7 @@ func (pg *Database) GetEpoch( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetEpoch returned no rows", + pg.Logger.Debug("GetEpoch returned no rows", "service", "repository", "app", appAddressKey, "epoch", indexKey) @@ -669,7 +674,7 @@ func (pg *Database) GetInput( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetInput returned no rows", + pg.Logger.Debug("GetInput returned no rows", "service", "repository", "app", appAddressKey, "index", indexKey) @@ -860,7 +865,7 @@ func (pg *Database) GetOutput( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetOutput returned no rows", + pg.Logger.Debug("GetOutput returned no rows", "service", "repository", "app", appAddressKey, "index", indexKey) @@ -1034,7 +1039,7 @@ func (pg *Database) GetReport( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetReport returned no rows", + pg.Logger.Debug("GetReport returned no rows", "service", "repository", "app", appAddressKey, "index", indexKey) @@ -1092,7 +1097,7 @@ func (pg *Database) GetSnapshot( ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Debug("GetSnapshot returned no rows", + pg.Logger.Debug("GetSnapshot returned no rows", "service", "repository", "app", appAddressKey, "input index", inputIndexKey) diff --git a/internal/repository/base_test.go b/internal/repository/base_test.go index 5181c1d45..d9b70cd66 100644 --- a/internal/repository/base_test.go +++ b/internal/repository/base_test.go @@ -5,11 +5,13 @@ package repository import ( "context" + "log/slog" "math" "testing" "time" . "github.com/cartesi/rollups-node/internal/model" + "github.com/cartesi/rollups-node/pkg/service" "github.com/cartesi/rollups-node/test/tooling/db" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/suite" @@ -35,7 +37,7 @@ func (s *RepositorySuite) SetupSuite() { err = db.SetupTestPostgres(endpoint) s.Require().Nil(err) - s.database, err = Connect(s.ctx, endpoint) + s.database, err = Connect(s.ctx, endpoint, service.NewLogger(slog.LevelDebug, true)) s.Require().Nil(err) s.SetupDatabase() diff --git a/internal/repository/claimer_test.go b/internal/repository/claimer_test.go index 201bcf026..06207c019 100644 --- a/internal/repository/claimer_test.go +++ b/internal/repository/claimer_test.go @@ -5,9 +5,12 @@ package repository import ( "context" + "log/slog" "testing" . "github.com/cartesi/rollups-node/internal/model" + "github.com/cartesi/rollups-node/pkg/service" + // "github.com/cartesi/rollups-node/internal/repository" "github.com/cartesi/rollups-node/test/tooling/db" "github.com/ethereum/go-ethereum/common" @@ -24,7 +27,7 @@ func setup(t *testing.T, ctx context.Context) (*require.Assertions, *Database, e err = db.SetupTestPostgres(endpoint) require.Nil(err) - database, err := Connect(ctx, endpoint) + database, err := Connect(ctx, endpoint, service.NewLogger(slog.LevelDebug, true)) require.Nil(err) require.NotNil(database) @@ -65,20 +68,20 @@ func TestClaimerRepository(t *testing.T) { apps := []Application{ { - Id: 0, - ContractAddress: common.HexToAddress("0"), - Status: ApplicationStatusRunning, + Id: 0, + ContractAddress: common.HexToAddress("0"), + Status: ApplicationStatusRunning, }, { - Id: 1, - ContractAddress: common.HexToAddress("1"), - Status: ApplicationStatusRunning, + Id: 1, + ContractAddress: common.HexToAddress("1"), + Status: ApplicationStatusRunning, }, { - Id: 2, - ContractAddress: common.HexToAddress("2"), - Status: ApplicationStatusRunning, + Id: 2, + ContractAddress: common.HexToAddress("2"), + Status: ApplicationStatusRunning, }, } - for _, app := range(apps) { + for _, app := range apps { _, err = database.InsertApplication(ctx, &app) require.Nil(err) } @@ -141,7 +144,7 @@ func TestClaimerRepository(t *testing.T) { Status: EpochStatusClaimComputed, }, } - for _, epoch := range(epochs) { + for _, epoch := range epochs { _, err = database.InsertEpoch(ctx, &epoch) require.Nil(err) } @@ -165,20 +168,20 @@ func TestClaimerRepository(t *testing.T) { apps := []Application{ { - Id: 0, - ContractAddress: common.HexToAddress("0"), - Status: ApplicationStatusRunning, + Id: 0, + ContractAddress: common.HexToAddress("0"), + Status: ApplicationStatusRunning, }, { - Id: 1, - ContractAddress: common.HexToAddress("1"), - Status: ApplicationStatusRunning, + Id: 1, + ContractAddress: common.HexToAddress("1"), + Status: ApplicationStatusRunning, }, { - Id: 2, - ContractAddress: common.HexToAddress("2"), - Status: ApplicationStatusRunning, + Id: 2, + ContractAddress: common.HexToAddress("2"), + Status: ApplicationStatusRunning, }, } - for _, app := range(apps) { + for _, app := range apps { _, err = database.InsertApplication(ctx, &app) require.Nil(err) } @@ -241,7 +244,7 @@ func TestClaimerRepository(t *testing.T) { Status: EpochStatusClaimComputed, }, } - for _, epoch := range(epochs) { + for _, epoch := range epochs { _, err = database.InsertEpoch(ctx, &epoch) require.Nil(err) } diff --git a/internal/repository/evmreader.go b/internal/repository/evmreader.go index c3f35b56d..8309957f7 100644 --- a/internal/repository/evmreader.go +++ b/internal/repository/evmreader.go @@ -498,4 +498,3 @@ func (pg *Database) InsertEvmReaderConfig( return pg.db.Exec(ctx, query, args) } - diff --git a/internal/repository/machine_test.go b/internal/repository/machine_test.go index 90a6897ec..b40f86b14 100644 --- a/internal/repository/machine_test.go +++ b/internal/repository/machine_test.go @@ -5,11 +5,13 @@ package repository import ( "context" + "log/slog" "testing" . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/rollupsmachine" + "github.com/cartesi/rollups-node/pkg/service" "github.com/ethereum/go-ethereum/common" "github.com/cartesi/rollups-node/test/tooling/db" @@ -29,7 +31,7 @@ func TestMachineRepository(t *testing.T) { err = db.SetupTestPostgres(endpoint) require.Nil(err) - database, err := Connect(ctx, endpoint) + database, err := Connect(ctx, endpoint, service.NewLogger(slog.LevelDebug, true)) require.Nil(err) require.NotNil(database) @@ -84,7 +86,7 @@ func TestMachineRepository(t *testing.T) { err = db.SetupTestPostgres(endpoint) require.Nil(err) - database, err := Connect(ctx, endpoint) + database, err := Connect(ctx, endpoint, service.NewLogger(slog.LevelDebug, true)) require.Nil(err) require.NotNil(database) diff --git a/internal/repository/validator.go b/internal/repository/validator.go index 77f2cafc2..b51ea2782 100644 --- a/internal/repository/validator.go +++ b/internal/repository/validator.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "log/slog" . "github.com/cartesi/rollups-node/internal/model" "github.com/jackc/pgx/v5" @@ -184,7 +183,7 @@ func (pg *Database) GetLastInputOutputsHash( err = pg.db.QueryRow(ctx, query, args).Scan(&outputHash) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - slog.Warn( + pg.Logger.Warn( "no inputs", "service", "repository", "epoch", epoch.Index, diff --git a/internal/services/http.go b/internal/services/http.go index 2aa3c0eb8..18ffad25b 100644 --- a/internal/services/http.go +++ b/internal/services/http.go @@ -9,8 +9,11 @@ import ( "log/slog" "net" "net/http" + "time" ) +const DefaultServiceTimeout = 1 * time.Minute + // FIXME: Simple CORS middleware. Improve this func CorsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -30,17 +33,19 @@ func CorsMiddleware(next http.Handler) http.Handler { }) } +// Used for testing type HttpService struct { Name string Address string Handler http.Handler + Logger *slog.Logger } func (s *HttpService) String() string { return s.Name } -func (s *HttpService) Start(ctx context.Context, ready chan<- struct{}) error { +func (s *HttpService) Start(ctx context.Context, ready chan<- struct{}, Logger *slog.Logger) error { server := http.Server{ Addr: s.Address, Handler: CorsMiddleware(s.Handler), diff --git a/internal/services/http_test.go b/internal/services/http_test.go index 47840fdc1..2b9329644 100644 --- a/internal/services/http_test.go +++ b/internal/services/http_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" "testing" "time" @@ -42,7 +43,7 @@ func (s *HttpServiceSuite) TestItStopsWhenContextIsClosed() { result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- service.Start(ctx, ready, slog.Default()) }() select { @@ -71,7 +72,7 @@ func (s *HttpServiceSuite) TestItRespondsToRequests() { result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- service.Start(ctx, ready, slog.Default()) }() select { @@ -103,7 +104,7 @@ func (s *HttpServiceSuite) TestItRespondsOngoingRequestsAfterContextIsClosed() { result := make(chan error, 1) ready := make(chan struct{}, 1) go func() { - result <- service.Start(ctx, ready) + result <- service.Start(ctx, ready, slog.Default()) }() select { diff --git a/internal/services/poller/poller.go b/internal/services/poller/poller.go deleted file mode 100644 index 174f63fb1..000000000 --- a/internal/services/poller/poller.go +++ /dev/null @@ -1,53 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package poller - -import ( - "context" - "errors" - "fmt" - "log/slog" - "time" -) - -type Service interface { - Step(context.Context) error -} - -type Poller struct { - name string - service Service - ticker *time.Ticker -} - -var ErrInvalidPollingInterval = errors.New("polling interval must be greater than zero") - -func New(name string, service Service, pollingInterval time.Duration) (*Poller, error) { - if pollingInterval <= 0 { - return nil, ErrInvalidPollingInterval - } - ticker := time.NewTicker(pollingInterval) - return &Poller{name: name, service: service, ticker: ticker}, nil -} - -func (poller *Poller) Start(ctx context.Context) error { - slog.Debug(fmt.Sprintf("%s: poller started", poller.name)) - - for { - // Runs the service's inner routine. - err := poller.service.Step(ctx) - if err != nil { - return err - } - - // Waits for the polling interval to elapse (or for the context to be canceled). - select { - case <-poller.ticker.C: - continue - case <-ctx.Done(): - poller.ticker.Stop() - return nil - } - } -} diff --git a/internal/services/service.go b/internal/services/service.go deleted file mode 100644 index caf9083eb..000000000 --- a/internal/services/service.go +++ /dev/null @@ -1,18 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -// Package services provides mechanisms to start multiple services in the -// background -package services - -import ( - "context" - "fmt" -) - -type Service interface { - fmt.Stringer - - // Starts a service and sends a message to the channel when ready - Start(ctx context.Context, ready chan<- struct{}) error -} diff --git a/internal/services/supervisor.go b/internal/services/supervisor.go deleted file mode 100644 index 7a8733b1e..000000000 --- a/internal/services/supervisor.go +++ /dev/null @@ -1,121 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package services - -import ( - "context" - "errors" - "log/slog" - "time" - - "golang.org/x/sync/errgroup" -) - -const DefaultServiceTimeout = 60 * time.Second - -var ( - ServiceTimeoutError = errors.New("timed out waiting for service to be ready") - SupervisorTimeoutError = errors.New("timed out waiting for services to stop") -) - -// SupervisorService is a simple implementation of a supervisor. -// It runs its services until the first returns a non-nil error. -type SupervisorService struct { - // Name of the service - Name string - - // Services to be managed - Services []Service - - // The amount of time to wait for a service to be ready. - // Default is 5 seconds - ReadyTimeout time.Duration - - // The amount of time to wait for a service to exit after - // its context is canceled. Default is 5 seconds - StopTimeout time.Duration -} - -func (s SupervisorService) String() string { - return s.Name -} - -func (s SupervisorService) Start(ctx context.Context, ready chan<- struct{}) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - group, ctx := errgroup.WithContext(ctx) - - // flag indicating if a service timed out during start - var serviceTimedOut bool - readyTimeout := s.ReadyTimeout - if readyTimeout <= 0 { - readyTimeout = DefaultServiceTimeout - } - stopTimeout := s.StopTimeout - if stopTimeout <= 0 { - stopTimeout = DefaultServiceTimeout - } - -Loop: - // start services one by one - for _, service := range s.Services { - service := service - serviceReady := make(chan struct{}, 1) - - group.Go(func() error { - err := service.Start(ctx, serviceReady) - if err != nil && !errors.Is(err, context.Canceled) { - slog.Error("Service exited with error", - "service", service, - "error", err, - ) - } else { - slog.Info("Service exited successfully", "service", service) - } - return err - }) - - select { - // service is ready, move along - case <-serviceReady: - slog.Info("Service is ready", "service", service) - // a service exited with error - case <-ctx.Done(): - break Loop - // service took too long to become ready - case <-time.After(readyTimeout): - slog.Error("Service timed out", "service", service) - cancel() - serviceTimedOut = true - break Loop - } - } - - // if nothing went wrong while starting services, SupervisorService is ready - if ctx.Err() == nil { - ready <- struct{}{} - slog.Info("All services are ready", "service", s.Name) - } - - // wait until a service exits with error or the external context is canceled - <-ctx.Done() - - // wait for all services to stop - wait := make(chan error) - go func() { - wait <- group.Wait() - }() - - select { - case err := <-wait: - slog.Info("All services exited successfully", "service", s.Name) - if serviceTimedOut { - return ServiceTimeoutError - } - return err - case <-time.After(stopTimeout): - slog.Error("Service timed out", "service", s.Name, "error", SupervisorTimeoutError) - return SupervisorTimeoutError - } -} diff --git a/internal/services/supervisor_test.go b/internal/services/supervisor_test.go deleted file mode 100644 index 87defb930..000000000 --- a/internal/services/supervisor_test.go +++ /dev/null @@ -1,363 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package services - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/suite" -) - -type SupervisorServiceSuite struct { - suite.Suite -} - -func TestSupervisorService(t *testing.T) { - suite.Run(t, new(SupervisorServiceSuite)) -} - -func (s *SupervisorServiceSuite) TestItIsReadyAfterStartingAllServices() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var services = []Service{ - NewMockService("Mock1", 0), - NewMockService("Mock2", 0), - NewMockService("Mock3", 0), - } - - ctxClosed := make(chan time.Time) - go func() { - <-ctx.Done() - close(ctxClosed) - }() - - for _, service := range services { - mockService := service.(*MockService) - mockService. - On("Start", mock.Anything, mock.Anything). - Return(nil). - WaitUntil(ctxClosed) - } - - supervisor := SupervisorService{ - Name: "supervisor", - Services: services, - } - - ready := make(chan struct{}) - go func() { - _ = supervisor.Start(ctx, ready) - }() - - select { - case <-ready: - for _, service := range services { - mockService := service.(*MockService) - mockService.AssertCalled(s.T(), "Start", mock.Anything, mock.Anything) - } - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to be ready") - } -} - -func (s *SupervisorServiceSuite) TestItStopsAllServicesWhenContextIsCanceled() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - services := []Service{ - NewMockService("Mock1", 0), - NewMockService("Mock2", 0), - NewMockService("Mock3", 0), - } - - ctxClosed := make(chan time.Time) - go func() { - <-ctx.Done() - close(ctxClosed) - }() - - for _, service := range services { - mockService := service.(*MockService) - mockService. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - WaitUntil(ctxClosed) - } - supervisor := SupervisorService{ - Name: "supervisor", - Services: services, - } - - result := make(chan error) - ready := make(chan struct{}) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - select { - case <-ready: - cancel() - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to be ready") - } - - select { - case err := <-result: - s.ErrorIs(err, context.Canceled) - for _, service := range services { - mockService := service.(*MockService) - mockService.AssertExpectations(s.T()) - } - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to be ready") - } -} - -func (s *SupervisorServiceSuite) TestItStopsAllServicesIfAServiceStops() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mockErr := errors.New("err") - services := []Service{ - NewMockService("Mock1", 0), - NewMockService("Mock2", 0), - NewMockService("Mock3", 0), - } - - for idx, service := range services { - mockService := service.(*MockService) - if idx == len(services)-1 { - mockService. - On("Start", mock.Anything, mock.Anything). - Return(mockErr). - After(100 * time.Millisecond) - } else { - mockService. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - After(500 * time.Millisecond) - } - } - - supervisor := SupervisorService{ - Name: "supervisor", - Services: services, - } - - result := make(chan error, 1) - ready := make(chan struct{}, 1) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - select { - case err := <-result: - s.ErrorIs(err, mockErr) - for _, service := range services { - mockService := service.(*MockService) - mockService.AssertExpectations(s.T()) - } - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to return") - } -} - -func (s *SupervisorServiceSuite) TestItStopsCreatingServicesIfAServiceFailsToStart() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mockErr := errors.New("err") - services := []Service{ - NewMockService("Mock1", 0), - NewMockService("Mock2", -1), - NewMockService("Mock3", 0), - } - - for idx, service := range services { - mockService := service.(*MockService) - if idx == 1 { - mockService.On("Start", mock.Anything, mock.Anything).Return(mockErr) - } else { - mockService. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - After(300 * time.Millisecond) - } - } - - supervisor := SupervisorService{ - Name: "supervisor", - Services: services, - } - - result := make(chan error, 1) - ready := make(chan struct{}, 1) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - select { - case err := <-result: - s.ErrorIs(err, mockErr) - last := services[len(services)-1].(*MockService) - last.AssertNotCalled(s.T(), "Start", mock.Anything, mock.Anything) - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to return") - } -} - -func (s *SupervisorServiceSuite) TestItStopsCreatingServicesIfContextIsCanceled() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - services := []Service{ - NewMockService("Mock1", 0), - NewMockService("Mock2", time.Second), - NewMockService("Mock3", 0), - NewMockService("Mock4", 0), - } - - ctxClosed := make(chan time.Time) - go func() { - <-ctx.Done() - close(ctxClosed) - }() - - for _, service := range services { - mockService := service.(*MockService) - mockService. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - WaitUntil(ctxClosed) - } - supervisor := SupervisorService{ - Name: "supervisor", - Services: services, - } - - result := make(chan error) - ready := make(chan struct{}, 1) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - <-time.After(300 * time.Millisecond) - cancel() - - select { - case err := <-result: - s.ErrorIs(err, context.Canceled) - for idx, service := range services { - mockService := service.(*MockService) - if idx > 1 { - mockService.AssertNotCalled(s.T(), "Start", mock.Anything, mock.Anything) - } else { - mockService.AssertExpectations(s.T()) - } - } - case <-ready: - s.FailNow("supervisor shouldn't be ready") - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to return") - } -} - -func (s *SupervisorServiceSuite) TestItTimesOutIfServiceTakesTooLongToBeReady() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mock1 := NewMockService("Mock1", 500*time.Millisecond) - mock1. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - After(time.Second) - - supervisor := SupervisorService{ - Name: "supervisor", - Services: []Service{mock1}, - ReadyTimeout: 200 * time.Millisecond, - } - - result := make(chan error) - ready := make(chan struct{}, 1) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - select { - case err := <-result: - s.ErrorIs(err, ServiceTimeoutError) - mock1.AssertCalled(s.T(), "Start", mock.Anything, mock.Anything) - case <-ready: - s.FailNow("supervisor shouldn't be ready") - case <-time.After(DefaultServiceTimeout): - s.FailNow("timed out waiting for supervisor to return") - } -} - -func (s *SupervisorServiceSuite) TestItTimesOutIfServicesTakeTooLongToStop() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - mock1 := NewMockService("Mock1", 0) - mock1. - On("Start", mock.Anything, mock.Anything). - Return(context.Canceled). - After(time.Second) - - timeout := 500 * time.Millisecond - supervisor := SupervisorService{ - Name: "supervisor", - Services: []Service{mock1}, - StopTimeout: timeout, - } - - result := make(chan error) - ready := make(chan struct{}, 1) - go func() { - result <- supervisor.Start(ctx, ready) - }() - - <-ready - cancel() - - err := <-result - s.ErrorIs(err, SupervisorTimeoutError) -} - -type MockService struct { - mock.Mock - Name string - // The time to wait before notifying it is ready. Provide a negative value - // to prevent such notification from being sent - ReadyDelay time.Duration -} - -func (m *MockService) Start(ctx context.Context, ready chan<- struct{}) error { - if m.ReadyDelay >= 0 { - go func() { - <-time.After(m.ReadyDelay) - ready <- struct{}{} - }() - } - - returnArgs := m.Called(ctx, ready) - return returnArgs.Error(0) -} - -func (m *MockService) String() string { - return m.Name -} - -func NewMockService(name string, readyDelay time.Duration) *MockService { - return &MockService{ - Name: name, - ReadyDelay: readyDelay, - } -} diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 613db042d..b83898b2d 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -8,7 +8,6 @@ package validator import ( "context" "fmt" - "log/slog" "time" "github.com/cartesi/rollups-node/internal/config" @@ -29,30 +28,35 @@ type CreateInfo struct { PostgresEndpoint config.Redacted[string] Repository ValidatorRepository PollingInterval time.Duration + MaxStartupTime time.Duration } func (c *CreateInfo) LoadEnv() { c.PostgresEndpoint.Value = config.GetPostgresEndpoint() c.PollInterval = config.GetValidatorPollingInterval() c.LogLevel = service.LogLevel(config.GetLogLevel()) + c.LogPretty = config.GetLogPrettyEnabled() + c.MaxStartupTime = config.GetMaxStartupTime() } -func Create(ci CreateInfo, s *Service) error { +func Create(c *CreateInfo, s *Service) error { var err error - err = service.Create(&ci.CreateInfo, &s.Service) + err = service.Create(&c.CreateInfo, &s.Service) if err != nil { return err } - if ci.Repository == nil { - ci.Repository, err = repository.Connect(s.Context, ci.PostgresEndpoint.Value) - if err != nil { - return err + return service.WithTimeout(c.MaxStartupTime, func() error { + if c.Repository == nil { + c.Repository, err = repository.Connect(s.Context, c.PostgresEndpoint.Value, s.Logger) + if err != nil { + return err + } } - } - s.repository = ci.Repository - return nil + s.repository = c.Repository + return nil + }) } func (s *Service) Alive() bool { return true } @@ -139,7 +143,7 @@ func (v *Service) Run(ctx context.Context) error { // validateApplication calculates, validates and stores the claim and/or proofs // for each processed epoch of the application. func (v *Service) validateApplication(ctx context.Context, app Application) error { - slog.Debug("validator: starting validation", "application", app.ContractAddress) + v.Logger.Debug("starting validation", "application", app.ContractAddress) processedEpochs, err := v.repository.GetProcessedEpochs(ctx, app.ContractAddress) if err != nil { return fmt.Errorf( @@ -149,12 +153,12 @@ func (v *Service) validateApplication(ctx context.Context, app Application) erro } for _, epoch := range processedEpochs { - slog.Debug("validator: started calculating claim", + v.Logger.Debug("started calculating claim", "app", app.ContractAddress, "epoch_index", epoch.Index, ) claim, outputs, err := v.createClaimAndProofs(ctx, epoch) - slog.Info("validator: claim calculated", + v.Logger.Info("claim calculated", "app", app.ContractAddress, "epoch_index", epoch.Index, ) @@ -209,7 +213,7 @@ func (v *Service) validateApplication(ctx context.Context, app Application) erro } if len(processedEpochs) == 0 { - slog.Debug("validator: no processed epochs to validate", + v.Logger.Debug("no processed epochs to validate", "app", app.ContractAddress, ) } diff --git a/internal/validator/validator_test.go b/internal/validator/validator_test.go index db58109e1..026cb623a 100644 --- a/internal/validator/validator_test.go +++ b/internal/validator/validator_test.go @@ -7,9 +7,11 @@ import ( "context" crand "crypto/rand" "testing" + "time" "github.com/cartesi/rollups-node/internal/merkle" . "github.com/cartesi/rollups-node/internal/model" + "github.com/cartesi/rollups-node/pkg/service" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" ) @@ -30,9 +32,14 @@ var ( func (s *ValidatorSuite) SetupSubTest() { repo = newMockrepo() - validator = &Service{ - repository: repo, - } + validator = &Service{} + s.Require().Nil(Create(&CreateInfo{ + Repository: repo, + MaxStartupTime: 5 * time.Second, + CreateInfo: service.CreateInfo{ + Impl: validator, + }, + }, validator)) dummyEpochs = []Epoch{ {Index: 0, FirstBlock: 0, LastBlock: 9}, {Index: 1, FirstBlock: 10, LastBlock: 19}, diff --git a/pkg/rollupsmachine/cartesimachine/server.go b/pkg/rollupsmachine/cartesimachine/server.go index 32df6f27d..31869b37c 100644 --- a/pkg/rollupsmachine/cartesimachine/server.go +++ b/pkg/rollupsmachine/cartesimachine/server.go @@ -35,7 +35,7 @@ const ( // StartServer also redirects the server's stdout and stderr to the provided io.Writers. // // It returns the server's address. -func StartServer(verbosity ServerVerbosity, port uint32, stdout, stderr io.Writer) (string, error) { +func StartServer(logger *slog.Logger, verbosity ServerVerbosity, port uint32, stdout, stderr io.Writer) (string, error) { // Configures the command's arguments. args := []string{} if verbosity.valid() { @@ -58,7 +58,7 @@ func StartServer(verbosity ServerVerbosity, port uint32, stdout, stderr io.Write cmd.Stderr = linewriter.New(interceptor) // Starts the server. - slog.Info("running", "command", cmd.String()) + logger.Info("running", "command", cmd.String()) if err := cmd.Start(); err != nil { return "", err } @@ -74,8 +74,8 @@ func StartServer(verbosity ServerVerbosity, port uint32, stdout, stderr io.Write } // StopServer shuts down the JSON RPC remote cartesi machine server hosted at address. -func StopServer(address string) error { - slog.Info("Stopping server at", "address", address) +func StopServer(address string, logger *slog.Logger) error { + logger.Info("Stopping server at", "address", address) remote, err := emulator.NewRemoteMachineManager(address) if err != nil { return err diff --git a/pkg/rollupsmachine/machine_test.go b/pkg/rollupsmachine/machine_test.go index c430b51f8..7c98d1fda 100644 --- a/pkg/rollupsmachine/machine_test.go +++ b/pkg/rollupsmachine/machine_test.go @@ -13,6 +13,7 @@ import ( "github.com/cartesi/rollups-node/pkg/emulator" "github.com/cartesi/rollups-node/pkg/rollupsmachine/cartesimachine" + "github.com/cartesi/rollups-node/pkg/service" "github.com/cartesi/rollups-node/test/tooling/snapshot" "github.com/stretchr/testify/require" @@ -57,6 +58,7 @@ type NewSuite struct { acceptSnapshot *snapshot.Snapshot rejectSnapshot *snapshot.Snapshot + logger *slog.Logger } func (s *NewSuite) SetupSuite() { @@ -75,6 +77,8 @@ func (s *NewSuite) SetupSuite() { s.rejectSnapshot, err = snapshot.FromScript(script, cycles) require.Nil(err) require.Equal(emulator.BreakReasonYieldedManually, s.rejectSnapshot.BreakReason) + + s.logger = service.NewLogger(slog.LevelDebug, true) } func (s *NewSuite) TearDownSuite() { @@ -83,13 +87,13 @@ func (s *NewSuite) TearDownSuite() { } func (s *NewSuite) SetupTest() { - address, err := cartesimachine.StartServer(serverVerbosity, 0, os.Stdout, os.Stderr) + address, err := cartesimachine.StartServer(s.logger, serverVerbosity, 0, os.Stdout, os.Stderr) s.Require().Nil(err) s.address = address } func (s *NewSuite) TearDownTest() { - err := cartesimachine.StopServer(s.address) + err := cartesimachine.StopServer(s.address, s.logger) s.Require().Nil(err) } @@ -166,7 +170,8 @@ func (s *ForkSuite) TestOk() { defer func() { require.Nil(snapshot.Close()) }() // Starts the server. - address, err := cartesimachine.StartServer(serverVerbosity, 0, os.Stdout, os.Stderr) + logger := service.NewLogger(slog.LevelDebug, true) + address, err := cartesimachine.StartServer(logger, serverVerbosity, 0, os.Stdout, os.Stderr) require.Nil(err) // Loads the machine. @@ -198,6 +203,7 @@ type AdvanceSuite struct { snapshotEcho *snapshot.Snapshot snapshotReject *snapshot.Snapshot address string + logger *slog.Logger } func (s *AdvanceSuite) SetupSuite() { @@ -216,6 +222,8 @@ func (s *AdvanceSuite) SetupSuite() { s.snapshotReject, err = snapshot.FromScript(script, cycles) require.Nil(err) require.Equal(emulator.BreakReasonYieldedManually, s.snapshotReject.BreakReason) + + s.logger = service.NewLogger(slog.LevelDebug, true) } func (s *AdvanceSuite) TearDownSuite() { @@ -224,7 +232,7 @@ func (s *AdvanceSuite) TearDownSuite() { } func (s *AdvanceSuite) SetupTest() { - address, err := cartesimachine.StartServer(serverVerbosity, 0, os.Stdout, os.Stderr) + address, err := cartesimachine.StartServer(s.logger, serverVerbosity, 0, os.Stdout, os.Stderr) s.Require().Nil(err) s.address = address } @@ -358,6 +366,7 @@ type InspectSuite struct { suite.Suite snapshotEcho *snapshot.Snapshot address string + logger *slog.Logger } func (s *InspectSuite) SetupSuite() { @@ -371,6 +380,7 @@ func (s *InspectSuite) SetupSuite() { s.snapshotEcho, err = snapshot.FromScript(script, cycles) require.Nil(err) require.Equal(emulator.BreakReasonYieldedManually, s.snapshotEcho.BreakReason) + s.logger = service.NewLogger(slog.LevelDebug, true) } func (s *InspectSuite) TearDownSuite() { @@ -378,7 +388,7 @@ func (s *InspectSuite) TearDownSuite() { } func (s *InspectSuite) SetupTest() { - address, err := cartesimachine.StartServer(serverVerbosity, 0, os.Stdout, os.Stderr) + address, err := cartesimachine.StartServer(s.logger, serverVerbosity, 0, os.Stdout, os.Stderr) s.Require().Nil(err) s.address = address } diff --git a/pkg/service/address.go b/pkg/service/address.go new file mode 100644 index 000000000..7cc6e6da8 --- /dev/null +++ b/pkg/service/address.go @@ -0,0 +1,18 @@ +// Implementation of the pflags Value interface. +package service + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type EthAddress common.Address + +func (me EthAddress) String() string { + return common.Address(me).String() +} +func (me *EthAddress) Set(s string) error { + return (*common.Address)(me).UnmarshalText([]byte(s)) +} +func (me *EthAddress) Type() string { + return "EthAddress" +} diff --git a/pkg/service/dummy.go b/pkg/service/dummy.go deleted file mode 100644 index 264a0d48c..000000000 --- a/pkg/service/dummy.go +++ /dev/null @@ -1,23 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package service - -// A Service implementation that does nothing -type DummyService struct { - Service -} - -type CreateDummyInfo struct { - CreateInfo -} - -func CreateDummy(ci CreateDummyInfo, null *DummyService) error { - return Create(&ci.CreateInfo, &null.Service) -} - -func (s *DummyService) Alive() bool { return true } -func (s *DummyService) Ready() bool { return true } -func (s *DummyService) Reload() []error { return nil } -func (s *DummyService) Tick() []error { return nil } -func (s *DummyService) Stop(bool) []error { return nil } diff --git a/pkg/service/list.go b/pkg/service/list.go deleted file mode 100644 index f4ae96709..000000000 --- a/pkg/service/list.go +++ /dev/null @@ -1,148 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package service - -import ( - "fmt" - "net/http" - "strings" - "time" -) - -// A Service implementation in terms of a list of other services -type ListService struct { - Service - Children []*Service -} - -type CreateListInfo struct { - CreateInfo -} - -func CreateList(ci CreateListInfo, list *ListService) error { - err := Create(&ci.CreateInfo, &list.Service) - if err != nil { - return err - } - - if ci.CreateInfo.TelemetryCreate { - list.ServeMux.Handle("/"+list.Name+"/readyz", - http.HandlerFunc(list.ReadyHandler)) - list.ServeMux.Handle("/"+list.Name+"/livez", - http.HandlerFunc(list.AliveHandler)) - } - return nil -} - -func (me *ListService) Alive() bool { - alive := true - for _, s := range me.Children { - alive = alive && s.Alive() - } - return alive -} - -func (me *ListService) Ready() bool { - allReady := true - for _, s := range me.Children { - ready := s.Ready() - allReady = allReady && ready - } - return allReady -} - -func (me *ListService) Reload() []error { - var all []error - for _, s := range me.Children { - es := s.Reload() - all = append(all, es...) - } - return all -} - -/* -// Simple tick runs children one at a time -func (me *ListService) Tick() []error { - var all []error - for _, s := range me.Children { - all = append(all, s.Tick()...) - } - return all -} -*/ - -// * -func (me *ListService) Tick() []error { - c := make(chan []error) - var all []error - for _, s := range me.Children { - go func() { - c <- s.Tick() - }() - } - - limit := time.Duration(0.95 * float64(me.Service.PollInterval)) - deadline := time.After(limit) - for range me.Children { - select { - case errs := <-c: - all = append(all, errs...) - case <-deadline: - me.Logger.Warn("Tick:time limit exceeded", - "service", me.Name, - "limit", limit) - return all - } - } - return all -} - -//*/ - -func (me *ListService) Stop(force bool) []error { - var all []error - for _, s := range me.Children { - es := s.Stop(force) - all = append(all, es...) - } - return all -} - -func (me *ListService) AliveHandler(w http.ResponseWriter, r *http.Request) { - ss := []string{} - - allAlive := true - for _, s := range me.Children { - alive := s.Alive() - allAlive = allAlive && alive - ss = append(ss, fmt.Sprintf("%s: %v", s.Name, alive)) - } - ss = append(ss, fmt.Sprintf("%s: %v", me.Name, allAlive)) - - if allAlive { - http.Error(w, strings.Join(ss, "\n"), - http.StatusInternalServerError) - } else { - fmt.Fprintf(w, strings.Join(ss, "\n")) - } -} - -func (me *ListService) ReadyHandler(w http.ResponseWriter, r *http.Request) { - ss := []string{} - - allReady := true - for _, s := range me.Children { - ready := s.Ready() - allReady = allReady && ready - ss = append(ss, fmt.Sprintf("%s: %v", s.Name, ready)) - } - ss = append(ss, fmt.Sprintf("%s: %v", me.Name, allReady)) - - if allReady { - http.Error(w, strings.Join(ss, "\n"), - http.StatusInternalServerError) - } else { - fmt.Fprintf(w, strings.Join(ss, "\n")) - } -} diff --git a/pkg/service/log.go b/pkg/service/log.go index ea454a105..6445c63ae 100644 --- a/pkg/service/log.go +++ b/pkg/service/log.go @@ -7,6 +7,7 @@ import ( ) type LogLevel slog.Level + func (me LogLevel) String() string { return slog.Level(me).String() } @@ -25,5 +26,5 @@ func (me *LogLevel) Set(s string) error { } } func (me *LogLevel) Type() string { - return "service.LogLevel" + return "LogLevel" } diff --git a/pkg/service/service.go b/pkg/service/service.go index afb1db1c0..d6108f81c 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -63,6 +63,8 @@ import ( "sync/atomic" "syscall" "time" + + "github.com/lmittmann/tint" ) var ( @@ -77,11 +79,22 @@ type ServiceImpl interface { Stop(bool) []error } +type IService interface { + Alive() bool + Ready() bool + Reload() []error + Tick() []error + Stop(bool) []error + Serve() error + String() string +} + // CreateInfo stores initialization data for the Create function type CreateInfo struct { Name string Impl ServiceImpl LogLevel LogLevel + LogPretty bool ProcOwner bool ServeMux *http.ServeMux Context context.Context @@ -110,61 +123,41 @@ type Service struct { // Create a service by: // - using values from s if non zero, -// - using values from ci, +// - using values from c, // - using default values when applicable -func Create(ci *CreateInfo, s *Service) error { - if ci == nil { - return ErrInvalid - } - - if s == nil { +func Create(c *CreateInfo, s *Service) error { + if c == nil || c.Impl == nil || c.Impl == s || s == nil { return ErrInvalid } - // running s.Running.Store(false) - - // name - if s.Name == "" { - s.Name = ci.Name - } - - // impl - if s.Impl == nil { - s.Impl = ci.Impl - } + s.Name = c.Name + s.Impl = c.Impl // log if s.Logger == nil { - // opts := &tint.Options{ - // Level: LogLevel, - // AddSource: LogLevel == slog.LevelDebug, - // // RFC3339 with milliseconds and without timezone - // TimeFormat: "2006-01-02T15:04:05.000", - // } - // handler := tint.NewHandler(os.Stdout, opts) - // s.Logger = slog.New(handler) - s.Logger = slog.Default() + s.Logger = NewLogger(slog.Level(c.LogLevel), c.LogPretty) + s.Logger = s.Logger.With("service", s.Name) } // context and cancelation if s.Context == nil { - if ci.Context == nil { - ci.Context = context.Background() + if c.Context == nil { + c.Context = context.Background() } - s.Context = ci.Context + s.Context = c.Context } if s.Cancel == nil { - s.Context, s.Cancel = context.WithCancel(ci.Context) + s.Context, s.Cancel = context.WithCancel(c.Context) } - if ci.ProcOwner { + if c.ProcOwner { // ticker if s.Ticker == nil { - if ci.PollInterval == 0 { - ci.PollInterval = 1000 * time.Millisecond + if c.PollInterval == 0 { + c.PollInterval = 60 * time.Second } - s.PollInterval = ci.PollInterval + s.PollInterval = c.PollInterval s.Ticker = time.NewTicker(s.PollInterval) } @@ -180,38 +173,35 @@ func Create(ci *CreateInfo, s *Service) error { } // telemetry - if ci.TelemetryCreate { + if c.TelemetryCreate { if s.ServeMux == nil { - if ci.ServeMux == nil { - if !ci.ProcOwner { + if c.ServeMux == nil { + if !c.ProcOwner { s.Logger.Warn("Create:Created a new ServeMux", - "service", s.Name, - "ProcOwner", ci.ProcOwner, - "LogLevel", ci.LogLevel) + "ProcOwner", c.ProcOwner, + "LogLevel", c.LogLevel) } - ci.ServeMux = http.NewServeMux() + c.ServeMux = http.NewServeMux() } - s.ServeMux = ci.ServeMux + s.ServeMux = c.ServeMux } - if ci.TelemetryAddress == "" { - ci.TelemetryAddress = ":8080" + if c.TelemetryAddress == "" { + c.TelemetryAddress = ":8080" } s.HTTPServer, s.HTTPServerFunc = s.CreateDefaultTelemetry( - ci.TelemetryAddress, 3, 5*time.Second, s.ServeMux) + c.TelemetryAddress, 3, 5*time.Second, s.ServeMux) go s.HTTPServerFunc() } // ProcOwner will be ready on the call to Serve - if ci.ProcOwner { + if c.ProcOwner { s.Logger.Info("Create", - "service", s.Name, - "LogLevel", ci.LogLevel, + "LogLevel", c.LogLevel, "pid", os.Getpid()) } else { s.Running.Store(true) s.Logger.Info("Create", - "service", s.Name, - "LogLevel", ci.LogLevel) + "LogLevel", c.LogLevel) } return nil } @@ -231,12 +221,10 @@ func (s *Service) Reload() []error { if len(errs) > 0 { s.Logger.Error("Reload", - "service", s.Name, "duration", elapsed, "error", errs) } else { s.Logger.Info("Reload", - "service", s.Name, "duration", elapsed) } return errs @@ -249,12 +237,10 @@ func (s *Service) Tick() []error { if len(errs) > 0 { s.Logger.Error("Tick", - "service", s.Name, "duration", elapsed, "error", errs) } else { s.Logger.Debug("Tick", - "service", s.Name, "duration", elapsed) } return errs @@ -271,13 +257,11 @@ func (s *Service) Stop(force bool) []error { s.Running.Store(false) if len(errs) > 0 { s.Logger.Error("Stop", - "service", s.Name, "force", force, "duration", elapsed, "error", errs) } else { s.Logger.Info("Stop", - "service", s.Name, "force", force, "duration", elapsed) } @@ -302,6 +286,43 @@ func (s *Service) Serve() error { return nil } +func (s *Service) String() string { + return s.Name +} + +func NewLogger(level slog.Level, pretty bool) *slog.Logger { + logger := &slog.Logger{} + if pretty { + opts := &tint.Options{ + Level: level, + AddSource: level == slog.LevelDebug, + // RFC3339 with milliseconds and without timezone + TimeFormat: "2006-01-02T15:04:05.000", + NoColor: !pretty, + } + handler := tint.NewHandler(os.Stdout, opts) + logger = slog.New(handler) + } else { + logger = slog.Default() + } + return logger +} + +func WithTimeout(limit time.Duration, fn func() error) error { + ch := make(chan error) + deadline := time.After(limit) + go func() { + ch <- fn() + }() + + select { + case err := <-ch: + return err + case <-deadline: + return fmt.Errorf("Time limit exceded") + } +} + // Telemetry func (s *Service) CreateDefaultHandlers(prefix string) { s.ServeMux.Handle(prefix+"/readyz", http.HandlerFunc(s.ReadyHandler)) @@ -315,8 +336,9 @@ func (s *Service) CreateDefaultTelemetry( mux *http.ServeMux, ) (*http.Server, func() error) { server := &http.Server{ - Addr: addr, - Handler: mux, + Addr: addr, + Handler: mux, + ErrorLog: slog.NewLogLogger(s.Logger.Handler(), slog.LevelError), } return server, func() error { s.Logger.Info("Telemetry", "service", s.Name, "addr", addr) @@ -327,7 +349,6 @@ func (s *Service) CreateDefaultTelemetry( return nil default: s.Logger.Error("http", - "service", s.Name, "error", err, "try", retry+1, "maxRetries", maxRetries, diff --git a/pkg/service/slow.go b/pkg/service/slow.go deleted file mode 100644 index 6dd843b31..000000000 --- a/pkg/service/slow.go +++ /dev/null @@ -1,43 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package service - -import "time" - -// A Service implementation that takes time to Tick -type SlowService struct { - Service - Time time.Duration -} - -type CreateSlowInfo struct { - CreateInfo - Time time.Duration -} - -func CreateSlow(ci CreateSlowInfo, slow *SlowService) error { - slow.Time = ci.Time - return Create(&ci.CreateInfo, &slow.Service) -} - -func (me *SlowService) Alive() bool { - return true -} - -func (me *SlowService) Ready() bool { - return true -} - -func (me *SlowService) Reload() []error { - return nil -} - -func (me *SlowService) Tick() []error { - time.Sleep(me.Time) - return nil -} - -func (me *SlowService) Stop(bool) []error { - return nil -} diff --git a/test/config.go b/test/config.go index 45b2bf1f8..5f972d74f 100644 --- a/test/config.go +++ b/test/config.go @@ -4,15 +4,6 @@ // Package endtoendtests package endtoendtests -import ( - "log" - "log/slog" - - "github.com/cartesi/rollups-node/internal/config" - "github.com/cartesi/rollups-node/pkg/addresses" - "github.com/cartesi/rollups-node/pkg/ethutil" -) - const ( LocalBlockchainID = 31337 LocalInputBoxDeploymentBlockNumber = 16 @@ -22,53 +13,3 @@ const ( LocalFinalityOffset = 1 LocalEpochLength = 5 ) - -func NewLocalNodeConfig(localPostgresEndpoint string, localBlockchainHttpEndpoint string, - localBlockchainWsEndpoint string, snapshotDir string) config.NodeConfig { - - var nodeConfig config.NodeConfig - - book, err := addresses.GetBookFromFile("deployment.json") - if err != nil { - log.Fatalf("failed to load address book: %v", err) - } - - //Log - nodeConfig.LogLevel = slog.LevelInfo - nodeConfig.LogPrettyEnabled = false - - //Postgres - nodeConfig.PostgresEndpoint = - config.Redacted[string]{Value: localPostgresEndpoint} - - //Blockchain - nodeConfig.BlockchainID = LocalBlockchainID - nodeConfig.BlockchainHttpEndpoint = - config.Redacted[string]{Value: localBlockchainHttpEndpoint} - nodeConfig.BlockchainWsEndpoint = - config.Redacted[string]{Value: localBlockchainWsEndpoint} - nodeConfig.LegacyBlockchainEnabled = false - nodeConfig.BlockchainBlockTimeout = LocalBlockTimeout - - //Contracts - nodeConfig.ContractsInputBoxAddress = book.InputBox.Hex() - nodeConfig.ContractsInputBoxDeploymentBlockNumber = LocalInputBoxDeploymentBlockNumber - - //HTTP endpoint - nodeConfig.HttpAddress = LocalHttpAddress - nodeConfig.HttpPort = LocalHttpPort - - //Features - nodeConfig.FeatureClaimSubmissionEnabled = true - nodeConfig.FeatureMachineHashCheckEnabled = true - - //Auth - nodeConfig.Auth = config.AuthMnemonic{ - Mnemonic: config.Redacted[string]{Value: ethutil.FoundryMnemonic}, - AccountIndex: config.Redacted[int]{Value: 0}, - } - - nodeConfig.SnapshotDir = snapshotDir - - return nodeConfig -} diff --git a/test/validator/validator_test.go b/test/validator/validator_test.go index afe6cba76..d25d7c932 100644 --- a/test/validator/validator_test.go +++ b/test/validator/validator_test.go @@ -5,6 +5,7 @@ package validator import ( "context" + "log/slog" "testing" "time" @@ -48,7 +49,7 @@ func (s *ValidatorRepositoryIntegrationSuite) SetupSuite() { func (s *ValidatorRepositoryIntegrationSuite) SetupSubTest() { var err error - s.database, err = repository.Connect(s.ctx, s.postgresEndpoint) + s.database, err = repository.Connect(s.ctx, s.postgresEndpoint, service.NewLogger(slog.LevelDebug, true)) s.Require().Nil(err) err = db.SetupTestPostgres(s.postgresEndpoint) @@ -59,9 +60,10 @@ func (s *ValidatorRepositoryIntegrationSuite) SetupSubTest() { Name: "validator", Impl: &s.validator, }, - Repository: s.database, + Repository: s.database, + MaxStartupTime: 1 * time.Second, } - s.Require().Nil(validator.Create(c, &s.validator)) + s.Require().Nil(validator.Create(&c, &s.validator)) } func (s *ValidatorRepositoryIntegrationSuite) TearDownSubTest() {