@@ -21,8 +21,11 @@ import (
2121 "github.com/bytecodealliance/wasmtime-go/v28"
2222 "google.golang.org/protobuf/proto"
2323
24+ "github.com/smartcontractkit/chainlink-common/pkg/config"
2425 "github.com/smartcontractkit/chainlink-common/pkg/custmsg"
2526 "github.com/smartcontractkit/chainlink-common/pkg/logger"
27+ "github.com/smartcontractkit/chainlink-common/pkg/settings"
28+ "github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
2629 dagsdk "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk"
2730 "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm"
2831 wasmdagpb "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/pb"
@@ -52,18 +55,22 @@ type DeterminismConfig struct {
5255 Seed int64
5356}
5457type ModuleConfig struct {
55- TickInterval time.Duration
56- Timeout * time.Duration
57- MaxMemoryMBs uint64
58- MinMemoryMBs uint64
59- InitialFuel uint64
60- Logger logger.Logger
61- IsUncompressed bool
62- Fetch func (ctx context.Context , req * FetchRequest ) (* FetchResponse , error )
63- MaxFetchRequests int
64- MaxCompressedBinarySize uint64
65- MaxDecompressedBinarySize uint64
66- MaxResponseSizeBytes uint64
58+ TickInterval time.Duration
59+ Timeout * time.Duration
60+ MaxMemoryMBs uint64
61+ MinMemoryMBs uint64
62+ MemoryLimiter limits.BoundLimiter [config.Size ] // supersedes Max/MinMemoryMBs if set
63+ InitialFuel uint64
64+ Logger logger.Logger
65+ IsUncompressed bool
66+ Fetch func (ctx context.Context , req * FetchRequest ) (* FetchResponse , error )
67+ MaxFetchRequests int
68+ MaxCompressedBinarySize uint64
69+ MaxCompressedBinaryLimiter limits.BoundLimiter [config.Size ] // supersedes MaxCompressedBinarySize if set
70+ MaxDecompressedBinarySize uint64
71+ MaxDecompressedBinaryLimiter limits.BoundLimiter [config.Size ] // supersedes MaxDecompressedBinarySize if set
72+ MaxResponseSizeBytes uint64
73+ MaxResponseSizeLimiter limits.BoundLimiter [config.Size ] // supersedes MaxResponseSizeBytes if set
6774
6875 MaxLogLenBytes uint32
6976 MaxLogCountDONMode uint32
@@ -143,7 +150,7 @@ func WithDeterminism() func(*ModuleConfig) {
143150 }
144151}
145152
146- func NewModule (modCfg * ModuleConfig , binary []byte , opts ... func (* ModuleConfig )) (* module , error ) {
153+ func NewModule (ctx context. Context , modCfg * ModuleConfig , binary []byte , opts ... func (* ModuleConfig )) (* module , error ) {
147154 // Apply options to the module config.
148155 for _ , opt := range opts {
149156 opt (modCfg )
@@ -200,33 +207,59 @@ func NewModule(modCfg *ModuleConfig, binary []byte, opts ...func(*ModuleConfig))
200207 modCfg .MaxLogCountNodeMode = uint32 (defaultMaxLogCountNodeMode )
201208 }
202209
203- // Take the max of the min and the configured max memory mbs.
204- // We do this because Go requires a minimum of 16 megabytes to run,
205- // and local testing has shown that with less than the min, some
206- // binaries may error sporadically.
207- modCfg .MaxMemoryMBs = uint64 (math .Max (float64 (modCfg .MinMemoryMBs ), float64 (modCfg .MaxMemoryMBs )))
208-
209- cfg := wasmtime .NewConfig ()
210- cfg .SetEpochInterruption (true )
211- if modCfg .InitialFuel > 0 {
212- cfg .SetConsumeFuel (true )
210+ lf := limits.Factory {Logger : modCfg .Logger }
211+ if modCfg .MemoryLimiter == nil {
212+ // Take the max of the min and the configured max memory mbs.
213+ // We do this because Go requires a minimum of 16 megabytes to run,
214+ // and local testing has shown that with less than the min, some
215+ // binaries may error sporadically.
216+ modCfg .MaxMemoryMBs = uint64 (math .Max (float64 (modCfg .MinMemoryMBs ), float64 (modCfg .MaxMemoryMBs )))
217+ limit := settings .Size (config .Size (modCfg .MaxMemoryMBs ) * config .MByte )
218+ var err error
219+ modCfg .MemoryLimiter , err = limits .MakeBoundLimiter (lf , limit )
220+ if err != nil {
221+ return nil , fmt .Errorf ("failed to make memory limiter: %w" , err )
222+ }
223+ }
224+ if modCfg .MaxCompressedBinaryLimiter == nil {
225+ limit := settings .Size (config .Size (modCfg .MaxCompressedBinarySize ))
226+ var err error
227+ modCfg .MaxCompressedBinaryLimiter , err = limits .MakeBoundLimiter (lf , limit )
228+ if err != nil {
229+ return nil , fmt .Errorf ("failed to make compressed binary size limiter: %w" , err )
230+ }
231+ }
232+ if modCfg .MaxDecompressedBinaryLimiter == nil {
233+ limit := settings .Size (config .Size (modCfg .MaxDecompressedBinarySize ))
234+ var err error
235+ modCfg .MaxDecompressedBinaryLimiter , err = limits .MakeBoundLimiter (lf , limit )
236+ if err != nil {
237+ return nil , fmt .Errorf ("failed to make decompressed binary size limiter: %w" , err )
238+ }
239+ }
240+ if modCfg .MaxResponseSizeLimiter == nil {
241+ limit := settings .Size (config .Size (modCfg .MaxResponseSizeBytes ))
242+ var err error
243+ modCfg .MaxResponseSizeLimiter , err = limits .MakeBoundLimiter (lf , limit )
244+ if err != nil {
245+ return nil , fmt .Errorf ("failed to make response size limiter: %w" , err )
246+ }
213247 }
214248
215- cfg .CacheConfigLoadDefault ()
216- cfg .SetCraneliftOptLevel (wasmtime .OptLevelSpeedAndSize )
217-
218- // Handled differenty based on host OS.
219- SetUnwinding (cfg )
220-
221- engine := wasmtime .NewEngineWithConfig (cfg )
222249 if ! modCfg .IsUncompressed {
223250 // validate the binary size before decompressing
224251 // this is to prevent decompression bombs
225- if uint64 (len (binary )) > modCfg .MaxCompressedBinarySize {
226- return nil , fmt .Errorf ("compressed binary size exceeds the maximum allowed size of %d bytes" , modCfg .MaxCompressedBinarySize )
252+ if err := modCfg .MaxCompressedBinaryLimiter .Check (ctx , config .SizeOf (binary )); err != nil {
253+ if errors .Is (err , limits.ErrorBoundLimited [config.Size ]{}) {
254+ return nil , fmt .Errorf ("compressed binary size exceeds the maximum allowed size of %d bytes: %w" , modCfg .MaxCompressedBinarySize , err )
255+ }
256+ return nil , fmt .Errorf ("failed to check compressed binary size limit: %w" , err )
227257 }
228-
229- rdr := io .LimitReader (brotli .NewReader (bytes .NewBuffer (binary )), int64 (modCfg .MaxDecompressedBinarySize + 1 ))
258+ maxDecompressedBinarySize , err := modCfg .MaxDecompressedBinaryLimiter .Limit (ctx )
259+ if err != nil {
260+ return nil , fmt .Errorf ("failed to get decompressed binary size limit: %w" , err )
261+ }
262+ rdr := io .LimitReader (brotli .NewReader (bytes .NewBuffer (binary )), int64 (maxDecompressedBinarySize + 1 ))
230263 decompedBinary , err := io .ReadAll (rdr )
231264 if err != nil {
232265 return nil , fmt .Errorf ("failed to decompress binary: %w" , err )
@@ -238,9 +271,27 @@ func NewModule(modCfg *ModuleConfig, binary []byte, opts ...func(*ModuleConfig))
238271 // Validate the decompressed binary size.
239272 // io.LimitReader prevents decompression bombs by reading up to a set limit, but it will not return an error if the limit is reached.
240273 // The Read() method will return io.EOF, and ReadAll will gracefully handle it and return nil.
241- if uint64 (len (binary )) > modCfg .MaxDecompressedBinarySize {
242- return nil , fmt .Errorf ("decompressed binary size reached the maximum allowed size of %d bytes" , modCfg .MaxDecompressedBinarySize )
274+ if err := modCfg .MaxDecompressedBinaryLimiter .Check (ctx , config .SizeOf (binary )); err != nil {
275+ if errors .Is (err , limits.ErrorBoundLimited [config.Size ]{}) {
276+ return nil , fmt .Errorf ("decompressed binary size reached the maximum allowed size of %d bytes: %w" , modCfg .MaxDecompressedBinarySize , err )
277+ }
278+ return nil , fmt .Errorf ("failed to check decompressed binary size limit: %w" , err )
279+ }
280+
281+ return newModule (modCfg , binary )
282+ }
283+
284+ func newModule (modCfg * ModuleConfig , binary []byte ) (* module , error ) {
285+ cfg := wasmtime .NewConfig ()
286+ cfg .SetEpochInterruption (true )
287+ if modCfg .InitialFuel > 0 {
288+ cfg .SetConsumeFuel (true )
243289 }
290+ cfg .CacheConfigLoadDefault ()
291+ cfg .SetCraneliftOptLevel (wasmtime .OptLevelSpeedAndSize )
292+ SetUnwinding (cfg ) // Handled differenty based on host OS.
293+
294+ engine := wasmtime .NewEngineWithConfig (cfg )
244295
245296 mod , err := wasmtime .NewModule (engine , binary )
246297 if err != nil {
@@ -256,16 +307,14 @@ func NewModule(modCfg *ModuleConfig, binary []byte, opts ...func(*ModuleConfig))
256307 }
257308 }
258309
259- m := & module {
310+ return & module {
260311 engine : engine ,
261312 module : mod ,
262313 wconfig : cfg ,
263314 cfg : modCfg ,
264315 stopCh : make (chan struct {}),
265316 v2ImportName : v2ImportName ,
266- }
267-
268- return m , nil
317+ }, nil
269318}
270319
271320func linkNoDAG (m * module , store * wasmtime.Store , exec * execution [* sdkpb.ExecutionResult ]) (* wasmtime.Instance , error ) {
@@ -468,7 +517,7 @@ func (m *module) Run(ctx context.Context, request *wasmdagpb.Request) (*wasmdagp
468517 computeRequest := r .GetComputeRequest ()
469518 if computeRequest != nil {
470519 computeRequest .RuntimeConfig = & wasmdagpb.RuntimeConfig {
471- MaxResponseSizeBytes : int64 (m . cfg . MaxResponseSizeBytes ),
520+ MaxResponseSizeBytes : int64 (maxSize ),
472521 }
473522 }
474523 }
@@ -493,7 +542,11 @@ func runWasm[I, O proto.Message](
493542
494543 defer store .Close ()
495544
496- setMaxResponseSize (request , m .cfg .MaxResponseSizeBytes )
545+ maxResponseSizeBytes , err := m .cfg .MaxResponseSizeLimiter .Limit (ctx )
546+ if err != nil {
547+ return o , fmt .Errorf ("failed to get response size limit: %w" , err )
548+ }
549+ setMaxResponseSize (request , uint64 (maxResponseSizeBytes ))
497550 reqpb , err := proto .Marshal (request )
498551 if err != nil {
499552 return o , err
@@ -517,8 +570,12 @@ func runWasm[I, O proto.Message](
517570 }
518571
519572 // Limit memory to max memory megabytes per instance.
573+ maxMemoryBytes , err := m .cfg .MemoryLimiter .Limit (ctx )
574+ if err != nil {
575+ return o , fmt .Errorf ("failed to get memory limit: %w" , err )
576+ }
520577 store .Limiter (
521- int64 (m . cfg . MaxMemoryMBs )* int64 (math .Pow (10 , 6 )),
578+ int64 (maxMemoryBytes / config . MByte )* int64 (math .Pow (10 , 6 )),
522579 - 1 , // tableElements, -1 == default
523580 1 , // instances
524581 1 , // tables
0 commit comments