Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 364 additions & 0 deletions docs/http-posture.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/deepmap/oapi-codegen/v2 v2.2.0
github.com/go-jet/jet/v2 v2.14.1
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/google/uuid v1.6.0
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/jackc/pgx/v5 v5.8.0
github.com/lmittmann/tint v1.1.3
Expand Down Expand Up @@ -66,7 +67,6 @@ require (
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
Expand Down
36 changes: 22 additions & 14 deletions internal/advancer/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

// httpShutdownTimeout is how long to wait for in-flight inspect HTTP requests
// to drain before forcibly closing the server during shutdown.
const httpShutdownTimeout = 10 * time.Second //nolint: mnd
const httpShutdownTimeout = 10 * time.Second

// Service is the main advancer service that processes inputs through Cartesi machines
type Service struct {
Expand All @@ -30,8 +30,6 @@ type Service struct {
repository AdvancerRepository
machineManager manager.MachineProvider
inspector *inspect.Inspector
HTTPServer *http.Server
HTTPServerFunc func() error

// cleanedUp ensures HTTP server shutdown and machine manager close run
// exactly once, even when Stop() is called multiple times (by the child's
Expand Down Expand Up @@ -83,13 +81,23 @@ func Create(ctx context.Context, c *CreateInfo) (*Service, error) {

// Initialize the inspect service if enabled
if c.Config.FeatureInspectEnabled {
s.inspector, s.HTTPServer, s.HTTPServerFunc = inspect.NewInspector(
c.Repository,
manager,
c.Config.InspectAddress,
c.LogLevel,
c.LogColor,
)
var admission *service.SemaphoreAdmission
if c.Config.InspectMaxInflight > 0 {
admission = service.NewSemaphoreAdmission(c.Config.InspectMaxInflight)
}
inspector, err := inspect.NewInspector(inspect.CreateInfo{
Repository: c.Repository,
Machines: manager,
Address: c.Config.InspectAddress,
LogLevel: c.LogLevel,
LogPretty: c.LogColor,
Admission: admission,
CORSAllowedOrigins: c.Config.InspectCorsAllowedOrigins,
})
if err != nil {
return nil, fmt.Errorf("failed to create inspect service: %w", err)
}
s.inspector = inspector
}

s.snapshotsDir = c.Config.SnapshotsDir
Expand Down Expand Up @@ -137,11 +145,11 @@ func (s *Service) Stop(b bool) []error {
// resources would not see IsStopping() == true.
s.SetStopping()
var errs []error
if s.HTTPServer != nil {
if s.inspector != nil {
s.Logger.Info("Shutting down inspect HTTP server")
shutdownCtx, cancel := context.WithTimeout(context.Background(), httpShutdownTimeout)
defer cancel()
if err := s.HTTPServer.Shutdown(shutdownCtx); err != nil {
if err := s.inspector.Shutdown(shutdownCtx); err != nil {
errs = append(errs, fmt.Errorf("failed to shutdown inspect HTTP server: %w", err))
}
}
Expand All @@ -154,9 +162,9 @@ func (s *Service) Stop(b bool) []error {
return errs
}
func (s *Service) Serve() error {
if s.inspector != nil && s.HTTPServerFunc != nil {
if s.inspector != nil {
go func() {
if err := s.HTTPServerFunc(); err != nil && !errors.Is(err, http.ErrServerClosed) {
if err := s.inspector.Serve(); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.Logger.Error("Inspect HTTP server failed — shutting down", "error", err)
s.Cancel()
}
Expand Down
39 changes: 39 additions & 0 deletions internal/config/generate/Config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,52 @@ description = """
HTTP address for the JSON-RPC API."""
used-by = ["jsonrpc", "node"]

[http.CARTESI_JSONRPC_MAX_INFLIGHT]
default = "64"
go-type = "uint64"
description = """
Maximum number of concurrent in-flight JSON-RPC requests.
Requests beyond this limit receive HTTP 503 Service Unavailable
with Retry-After: 1. Set to 0 to disable HTTP-level admission
control."""
used-by = ["jsonrpc", "node"]

[http.CARTESI_JSONRPC_CORS_ALLOWED_ORIGINS]
default = ""
go-type = "string"
description = """
Comma-separated list of allowed browser origins for the JSON-RPC API.
If empty, CORS is disabled. Origins are lowercased and validated at
startup. Example: "http://localhost:3000,https://app.example.com"."""
used-by = ["jsonrpc", "node"]

[http.CARTESI_INSPECT_CORS_ALLOWED_ORIGINS]
default = ""
go-type = "string"
description = """
Comma-separated list of allowed browser origins for inspect.
If empty, CORS is disabled. Origins are lowercased and validated at
startup. Example: "http://localhost:3000,https://app.example.com"."""
used-by = ["advancer", "node"]

[http.CARTESI_INSPECT_ADDRESS]
default = ":10012"
go-type = "string"
description = """
HTTP address for inspect."""
used-by = ["advancer", "node"]

[http.CARTESI_INSPECT_MAX_INFLIGHT]
default = "64"
go-type = "uint64"
description = """
Maximum number of concurrent in-flight HTTP inspect requests.
Requests beyond this limit receive HTTP 503 Service Unavailable
with Retry-After: 1. Set to 0 to disable HTTP-level admission
control (backpressure then falls back to the per-application
machine semaphore)."""
used-by = ["advancer", "node"]

[http.CARTESI_JSONRPC_API_URL]
default = "http://localhost:10011/rpc"
go-type = "string"
Expand Down
9 changes: 9 additions & 0 deletions internal/config/generate/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ var funcMap = template.FuncMap{
"mapstructure": func(s string) string {
return "`mapstructure:\"" + s + "\"`"
},
// hasEmptyStringDefault checks if a variable has default="" and is a string type.
// These vars need viper.IsSet instead of s!="" because empty string is a valid value.
"hasEmptyStringDefault": func(env Env) bool {
return env.Default != nil && *env.Default == "" && env.GoType == "string"
},
// isUsedBy checks if a variable is used by a specific service
"isUsedBy": func(env Env, service string) bool {
return slices.Contains(env.UsedBy, service)
Expand Down Expand Up @@ -223,7 +228,11 @@ func Get{{ toFieldName .Name }}() ({{ .GoType }}, error) {
s = strings.TrimSpace(string(contents))
}
{{- end }}
{{- if hasEmptyStringDefault . }}
if viper.IsSet({{ toConstName .Name }}) {
{{- else }}
if s != "" {
{{- end }}
v, err := {{ toGoFunc .GoType }}(s)
if err != nil {
return v, fmt.Errorf("failed to parse %s: %w", {{ toConstName .Name }}, err)
Expand Down
Loading
Loading