Skip to content

Commit 31c47a2

Browse files
rodrodrosclaude
andauthored
feat: make compilation process start on a separte process (#3415)
feat(compiler): add process-based compiler and RPC request timeout Add a safe process-isolation compiler that runs Sierra-to-CASM compilation in child processes with semaphore-based concurrency control. Add RPC request timeout configuration with HTTP write timeout support. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f81341 commit 31c47a2

File tree

9 files changed

+288
-10
lines changed

9 files changed

+288
-10
lines changed

cmd/juno/compile.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
8+
"github.com/NethermindEth/juno/starknet"
9+
"github.com/NethermindEth/juno/starknet/compiler"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func CompileSierraCmd() *cobra.Command {
14+
return &cobra.Command{
15+
Use: "compile-sierra",
16+
Short: "Compile a Sierra class to CASM via stdin/stdout.",
17+
RunE: func(cmd *cobra.Command, args []string) error {
18+
input, err := io.ReadAll(cmd.InOrStdin())
19+
if err != nil {
20+
return fmt.Errorf("read stdin: %w", err)
21+
}
22+
23+
var sierra starknet.SierraClass
24+
if err := json.Unmarshal(input, &sierra); err != nil {
25+
return fmt.Errorf("unmarshal sierra class: %w", err)
26+
}
27+
28+
casmClass, err := compiler.CompileFFI(&sierra)
29+
if err != nil {
30+
return err
31+
}
32+
33+
output, err := json.Marshal(casmClass)
34+
if err != nil {
35+
return fmt.Errorf("marshal casm class: %w", err)
36+
}
37+
38+
_, err = cmd.OutOrStdout().Write(output)
39+
return err
40+
},
41+
}
42+
}

cmd/juno/compile_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main_test
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
juno "github.com/NethermindEth/juno/cmd/juno"
9+
"github.com/NethermindEth/juno/starknet"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestCompileSierraCmd(t *testing.T) {
14+
t.Run("invalid input returns error", func(t *testing.T) {
15+
cmd := juno.CompileSierraCmd()
16+
cmd.SetIn(bytes.NewReader([]byte("not json")))
17+
cmd.SetOut(&bytes.Buffer{})
18+
19+
err := cmd.Execute()
20+
require.Error(t, err)
21+
})
22+
23+
t.Run("empty sierra class returns error", func(t *testing.T) {
24+
input, err := json.Marshal(starknet.SierraClass{})
25+
require.NoError(t, err)
26+
27+
cmd := juno.CompileSierraCmd()
28+
cmd.SetIn(bytes.NewReader(input))
29+
30+
var stdout bytes.Buffer
31+
cmd.SetOut(&stdout)
32+
33+
err = cmd.Execute()
34+
require.Error(t, err)
35+
})
36+
}

cmd/juno/juno.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ const (
103103
dbMemtableCountF = "db-memtable-count"
104104
dbCompressionF = "db-compression"
105105
transactionCombinedLayoutF = node.FlagTransactionCombinedLayout
106+
rpcRequestTimeoutF = "rpc-request-timeout"
107+
maxConcurrentCompilationsF = "max-concurrent-compilations"
106108

107109
defaultConfig = ""
108110
defaultHost = "localhost"
@@ -158,6 +160,8 @@ const (
158160
defaultDBMemtableCount = 2
159161
defaultDBCompression = "snappy"
160162
defaultTransactionCombinedLayout = false
163+
defaultRPCRequestTimeout = 1 * time.Minute
164+
defaultMaxConcurrentCompilations = 8
161165

162166
configFlagUsage = "The YAML configuration file."
163167
logLevelFlagUsage = "Options: trace, debug, info, warn, error."
@@ -238,6 +242,8 @@ const (
238242
"Use zstd for low storage."
239243
transactionCombinedLayoutUsage = "EXPERIMENTAL: Enable combined (per-block) transaction " +
240244
"storage layout. Once enabled, cannot be disabled."
245+
rpcRequestTimeoutUsage = "Maximum time for an RPC request to complete."
246+
maxConcurrentCompilationsUsage = "Maximum concurrent Sierra compilations."
241247
)
242248

243249
var Version string
@@ -458,7 +464,11 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr
458464
junoCmd.Flags().Bool(
459465
transactionCombinedLayoutF, defaultTransactionCombinedLayout, transactionCombinedLayoutUsage,
460466
)
461-
junoCmd.AddCommand(GenP2PKeyPair(), DBCmd(defaultDBPath))
467+
junoCmd.Flags().Duration(rpcRequestTimeoutF, defaultRPCRequestTimeout, rpcRequestTimeoutUsage)
468+
junoCmd.Flags().Uint(
469+
maxConcurrentCompilationsF, defaultMaxConcurrentCompilations, maxConcurrentCompilationsUsage,
470+
)
471+
junoCmd.AddCommand(GenP2PKeyPair(), DBCmd(defaultDBPath), CompileSierraCmd())
462472

463473
return junoCmd
464474
}

cmd/juno/juno_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ func TestConfigPrecedence(t *testing.T) {
7171
defaultGwTimeout := "5s"
7272
defaultSubmittedTransactionsCacheSize := uint(10_000)
7373
defaultSubmittedTransactionsCacheEntryTTL := 5 * time.Minute
74+
defaultRPCRequestTimeout := 1 * time.Minute
75+
defaultMaxConcurrentCompilations := uint(8)
76+
7477
expectedConfig1 := node.Config{
7578
LogLevel: "debug",
7679
HTTP: defaultHTTP,
@@ -110,6 +113,8 @@ func TestConfigPrecedence(t *testing.T) {
110113
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
111114
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
112115
ReadinessBlockTolerance: 6,
116+
RPCRequestTimeout: defaultRPCRequestTimeout,
117+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
113118
}
114119

115120
expectedConfig2 := node.Config{
@@ -151,6 +156,8 @@ func TestConfigPrecedence(t *testing.T) {
151156
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
152157
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
153158
ReadinessBlockTolerance: 6,
159+
RPCRequestTimeout: defaultRPCRequestTimeout,
160+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
154161
}
155162
tests := map[string]struct {
156163
cfgFile bool
@@ -253,6 +260,8 @@ pprof: true
253260
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
254261
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
255262
ReadinessBlockTolerance: 6,
263+
RPCRequestTimeout: defaultRPCRequestTimeout,
264+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
256265
},
257266
},
258267
"config file with some settings but without any other flags": {
@@ -300,6 +309,8 @@ http-port: 4576
300309
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
301310
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
302311
ReadinessBlockTolerance: 6,
312+
RPCRequestTimeout: defaultRPCRequestTimeout,
313+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
303314
},
304315
},
305316
"all flags without config file": {
@@ -346,6 +357,8 @@ http-port: 4576
346357
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
347358
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
348359
ReadinessBlockTolerance: 6,
360+
RPCRequestTimeout: defaultRPCRequestTimeout,
361+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
349362
},
350363
},
351364
"some flags without config file": {
@@ -392,6 +405,8 @@ http-port: 4576
392405
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
393406
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
394407
ReadinessBlockTolerance: 6,
408+
RPCRequestTimeout: defaultRPCRequestTimeout,
409+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
395410
},
396411
},
397412
"all setting set in both config file and flags": {
@@ -463,6 +478,8 @@ db-cache-size: 1024
463478
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
464479
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
465480
ReadinessBlockTolerance: 6,
481+
RPCRequestTimeout: defaultRPCRequestTimeout,
482+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
466483
},
467484
},
468485
"some setting set in both config file and flags": {
@@ -512,6 +529,8 @@ network: sepolia
512529
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
513530
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
514531
ReadinessBlockTolerance: 6,
532+
RPCRequestTimeout: defaultRPCRequestTimeout,
533+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
515534
},
516535
},
517536
"some setting set in default, config file and flags": {
@@ -557,6 +576,8 @@ network: sepolia
557576
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
558577
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
559578
ReadinessBlockTolerance: 6,
579+
RPCRequestTimeout: defaultRPCRequestTimeout,
580+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
560581
},
561582
},
562583
"only set env variables": {
@@ -600,6 +621,8 @@ network: sepolia
600621
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
601622
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
602623
ReadinessBlockTolerance: 6,
624+
RPCRequestTimeout: defaultRPCRequestTimeout,
625+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
603626
},
604627
},
605628
"some setting set in both env variables and flags": {
@@ -644,6 +667,8 @@ network: sepolia
644667
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
645668
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
646669
ReadinessBlockTolerance: 6,
670+
RPCRequestTimeout: defaultRPCRequestTimeout,
671+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
647672
},
648673
},
649674
"some setting set in both env variables and config file": {
@@ -689,6 +714,8 @@ network: sepolia
689714
SubmittedTransactionsCacheSize: defaultSubmittedTransactionsCacheSize,
690715
SubmittedTransactionsCacheEntryTTL: defaultSubmittedTransactionsCacheEntryTTL,
691716
ReadinessBlockTolerance: 6,
717+
RPCRequestTimeout: defaultRPCRequestTimeout,
718+
MaxConcurrentCompilations: defaultMaxConcurrentCompilations,
692719
},
693720
},
694721
}

jsonrpc/http.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package jsonrpc
22

33
import (
44
"compress/gzip"
5+
"context"
56
"io"
67
"maps"
78
"net/http"
89
"strings"
10+
"time"
911

1012
"github.com/NethermindEth/juno/utils"
1113
"go.uber.org/zap"
@@ -17,7 +19,8 @@ type HTTP struct {
1719
rpc *Server
1820
log utils.StructuredLogger
1921

20-
listener NewRequestListener
22+
listener NewRequestListener
23+
requestTimeout time.Duration
2124
}
2225

2326
func NewHTTP(rpc *Server, log utils.StructuredLogger) *HTTP {
@@ -35,6 +38,13 @@ func (h *HTTP) WithListener(listener NewRequestListener) *HTTP {
3538
return h
3639
}
3740

41+
// WithRequestTimeout sets the maximum duration for handling an
42+
// RPC request. Zero means no timeout.
43+
func (h *HTTP) WithRequestTimeout(d time.Duration) *HTTP {
44+
h.requestTimeout = d
45+
return h
46+
}
47+
3848
// ServeHTTP processes an incoming HTTP request
3949
func (h *HTTP) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
4050
if req.Method == http.MethodGet {
@@ -51,7 +61,14 @@ func (h *HTTP) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
5161

5262
req.Body = http.MaxBytesReader(writer, req.Body, MaxRequestBodySize)
5363
h.listener.OnNewRequest("any")
54-
resp, header, err := h.rpc.HandleReader(req.Context(), req.Body)
64+
65+
ctx := req.Context()
66+
if h.requestTimeout > 0 {
67+
var cancel context.CancelFunc
68+
ctx, cancel = context.WithTimeout(ctx, h.requestTimeout)
69+
defer cancel()
70+
}
71+
resp, header, err := h.rpc.HandleReader(ctx, req.Body)
5572

5673
writer.Header().Set("Content-Type", "application/json")
5774
maps.Copy(writer.Header(), header) // overwrites duplicate headers

node/http.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ func (h *httpService) registerOnShutdown(f func()) {
6464
h.srv.RegisterOnShutdown(f)
6565
}
6666

67+
func (h *httpService) withWriteTimeout(d time.Duration) *httpService {
68+
h.srv.WriteTimeout = d
69+
return h
70+
}
71+
6772
func makeHTTPService(host string, port uint16, handler http.Handler) *httpService {
6873
portStr := strconv.FormatUint(uint64(port), 10)
6974
return &httpService{
@@ -89,9 +94,14 @@ func exactPathServer(path string, handler http.Handler) http.HandlerFunc {
8994
}
9095

9196
func makeRPCOverHTTP(
92-
host string, port uint16, servers map[string]*jsonrpc.Server,
93-
httpHandlers map[string]http.HandlerFunc, log utils.StructuredLogger,
94-
metricsEnabled bool, corsEnabled bool,
97+
host string,
98+
port uint16,
99+
servers map[string]*jsonrpc.Server,
100+
httpHandlers map[string]http.HandlerFunc,
101+
log utils.StructuredLogger,
102+
metricsEnabled bool,
103+
corsEnabled bool,
104+
rpcRequestTimeout time.Duration,
95105
) *httpService {
96106
var listener jsonrpc.NewRequestListener
97107
if metricsEnabled {
@@ -100,7 +110,8 @@ func makeRPCOverHTTP(
100110

101111
mux := http.NewServeMux()
102112
for path, server := range servers {
103-
httpHandler := jsonrpc.NewHTTP(server, log)
113+
httpHandler := jsonrpc.NewHTTP(server, log).
114+
WithRequestTimeout(rpcRequestTimeout)
104115
if listener != nil {
105116
httpHandler = httpHandler.WithListener(listener)
106117
}
@@ -114,7 +125,12 @@ func makeRPCOverHTTP(
114125
if corsEnabled {
115126
handler = cors.Default().Handler(handler)
116127
}
117-
return makeHTTPService(host, port, handler)
128+
129+
svc := makeHTTPService(host, port, handler)
130+
if rpcRequestTimeout > 0 {
131+
svc = svc.withWriteTimeout(rpcRequestTimeout + 5*time.Second)
132+
}
133+
return svc
118134
}
119135

120136
func makeRPCOverWebsocket(host string, port uint16, servers map[string]*jsonrpc.Server,

node/node.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ type Config struct {
126126
ForbidRPCBatchRequests bool `mapstructure:"disable-rpc-batch-requests"`
127127

128128
TransactionCombinedLayout bool `mapstructure:"transaction-combined-layout"`
129+
130+
RPCRequestTimeout time.Duration `mapstructure:"rpc-request-timeout"`
131+
MaxConcurrentCompilations uint `mapstructure:"max-concurrent-compilations"`
129132
}
130133

131134
type Node struct {
@@ -220,7 +223,11 @@ func New(cfg *Config, version string, logLevel *utils.LogLevel) (*Node, error) {
220223
var nodeVM vm.VM
221224
var throttledVM *ThrottledVM
222225

223-
compiler := compiler.NewUnsafe()
226+
compiler := compiler.New(
227+
cfg.MaxConcurrentCompilations,
228+
"",
229+
log,
230+
)
224231

225232
if cfg.Sequencer {
226233
// Sequencer mode only supports known networks and
@@ -415,7 +422,8 @@ func New(cfg *Config, version string, logLevel *utils.LogLevel) (*Node, error) {
415422
"/ready": readinessHandlers.HandleReadySync,
416423
"/ready/sync": readinessHandlers.HandleReadySync,
417424
}
418-
services = append(services,
425+
services = append(
426+
services,
419427
makeRPCOverHTTP(
420428
cfg.HTTPHost,
421429
cfg.HTTPPort,
@@ -424,6 +432,7 @@ func New(cfg *Config, version string, logLevel *utils.LogLevel) (*Node, error) {
424432
log,
425433
cfg.Metrics,
426434
cfg.RPCCorsEnable,
435+
cfg.RPCRequestTimeout,
427436
),
428437
)
429438
}

0 commit comments

Comments
 (0)