Skip to content

Commit 62a9019

Browse files
committed
[CRE-954] Extra logging limits in WASM Module
+ set a missing "mode" field on executor
1 parent 415d51d commit 62a9019

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

pkg/workflows/wasm/host/execution.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type execution[T any] struct {
2727
mode sdkpb.Mode
2828
donSeed int64
2929
nodeSeed int64
30+
donLogCount uint32
31+
nodeLogCount uint32
3032
}
3133

3234
// callCapAsync async calls a capability by placing execution results onto a
@@ -139,6 +141,35 @@ func (e *execution[T]) awaitSecrets(ctx context.Context, acr *sdkpb.AwaitSecrets
139141
}
140142

141143
func (e *execution[T]) log(caller *wasmtime.Caller, ptr int32, ptrlen int32) {
144+
switch e.mode {
145+
case sdkpb.Mode_MODE_DON:
146+
e.donLogCount++
147+
if e.donLogCount == e.module.cfg.MaxLogCountDONMode {
148+
e.module.cfg.Logger.Errorf("max log count for don mode reached: %d - all subsequent logs will be dropped", e.donLogCount)
149+
}
150+
if e.donLogCount > e.module.cfg.MaxLogCountDONMode {
151+
// silently drop to avoid spamming logs
152+
return
153+
}
154+
case sdkpb.Mode_MODE_NODE:
155+
e.nodeLogCount++
156+
if e.nodeLogCount == e.module.cfg.MaxLogCountNodeMode {
157+
e.module.cfg.Logger.Errorf("max log count for node mode reached: %d - all subsequent logs will be dropped", e.nodeLogCount)
158+
}
159+
if e.nodeLogCount > e.module.cfg.MaxLogCountNodeMode {
160+
// silently drop to avoid spamming logs
161+
return
162+
}
163+
default:
164+
// unexpected / malicious
165+
return
166+
}
167+
168+
if ptrlen > int32(e.module.cfg.MaxLogLenBytes) {
169+
e.module.cfg.Logger.Errorf("log message too long: %d - dropping", ptrlen)
170+
return
171+
}
172+
142173
b, innerErr := wasmRead(caller, ptr, ptrlen)
143174
if innerErr != nil {
144175
e.module.cfg.Logger.Errorf("error calling log: %s", innerErr)

pkg/workflows/wasm/host/module.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ var (
4141
defaultMaxCompressedBinarySize = 20 * 1024 * 1024 // 20 MB
4242
defaultMaxDecompressedBinarySize = 100 * 1024 * 1024 // 100 MB
4343
defaultMaxResponseSizeBytes = 5 * 1024 * 1024 // 5 MB
44+
defaultMaxLogLenBytes = 1024 * 1024 // 1 MB
45+
defaultMaxLogCountDONMode = 10_000
46+
defaultMaxLogCountNodeMode = 10_000
4447
ResponseBufferTooSmall = "response buffer too small"
4548
)
4649

@@ -62,6 +65,10 @@ type ModuleConfig struct {
6265
MaxDecompressedBinarySize uint64
6366
MaxResponseSizeBytes uint64
6467

68+
MaxLogLenBytes uint32
69+
MaxLogCountDONMode uint32
70+
MaxLogCountNodeMode uint32
71+
6572
// Labeler is used to emit messages from the module.
6673
Labeler custmsg.MessageEmitter
6774

@@ -183,6 +190,15 @@ func NewModule(modCfg *ModuleConfig, binary []byte, opts ...func(*ModuleConfig))
183190
if modCfg.MaxResponseSizeBytes == 0 {
184191
modCfg.MaxResponseSizeBytes = uint64(defaultMaxResponseSizeBytes)
185192
}
193+
if modCfg.MaxLogLenBytes == 0 {
194+
modCfg.MaxLogLenBytes = uint32(defaultMaxLogLenBytes)
195+
}
196+
if modCfg.MaxLogCountDONMode == 0 {
197+
modCfg.MaxLogCountDONMode = uint32(defaultMaxLogCountDONMode)
198+
}
199+
if modCfg.MaxLogCountNodeMode == 0 {
200+
modCfg.MaxLogCountNodeMode = uint32(defaultMaxLogCountNodeMode)
201+
}
186202

187203
// Take the max of the min and the configured max memory mbs.
188204
// We do this because Go requires a minimum of 16 megabytes to run,

pkg/workflows/wasm/host/standard_tests/logging/main_wasip1.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host/internal/rawsdk"
5+
"github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
56
)
67

78
func main() {
9+
rawsdk.SwitchModes(int32(sdk.Mode_MODE_DON))
810
request := rawsdk.GetRequest()
911
msg := []byte("log from wasm!")
1012
rawsdk.Log(rawsdk.BufferToPointerLen(msg))
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host/internal/rawsdk"
5+
"github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
6+
)
7+
8+
func main() {
9+
rawsdk.SwitchModes(int32(sdk.Mode_MODE_DON))
10+
request := rawsdk.GetRequest()
11+
msg := []byte("short log 1")
12+
rawsdk.Log(rawsdk.BufferToPointerLen(msg))
13+
msg = []byte("super duper excessively long log 2") // exceeding 20 byte limit set in the test
14+
rawsdk.Log(rawsdk.BufferToPointerLen(msg))
15+
msg = []byte("short log 3")
16+
rawsdk.Log(rawsdk.BufferToPointerLen(msg))
17+
msg = []byte("short log 4")
18+
rawsdk.Log(rawsdk.BufferToPointerLen(msg))
19+
rawsdk.SendResponse(request.Config)
20+
}

pkg/workflows/wasm/host/wasm_nodag_test.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ import (
99

1010
"google.golang.org/protobuf/types/known/emptypb"
1111

12+
"github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/protoc/pkg/test_capabilities/basictrigger"
1213
"github.com/smartcontractkit/chainlink-common/pkg/logger"
1314
"github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
1415

16+
mock "github.com/stretchr/testify/mock"
1517
"github.com/stretchr/testify/require"
1618
)
1719

1820
const (
19-
nodagRandomBinaryCmd = "standard_tests/multiple_triggers"
20-
nodagRandomBinaryLocation = nodagRandomBinaryCmd + "/testmodule.wasm"
21+
nodagRandomBinaryCmd = "standard_tests/multiple_triggers"
22+
nodagRandomBinaryLocation = nodagRandomBinaryCmd + "/testmodule.wasm"
23+
loggingLimitsBinaryCmd = "test/logging_limits/cmd"
24+
loggingLimitsBinaryLocation = loggingLimitsBinaryCmd + "/testmodule.wasm"
2125
)
2226

2327
func Test_Sleep_Timeout(t *testing.T) {
@@ -89,6 +93,48 @@ func Test_NoDag_Run(t *testing.T) {
8993
})
9094
}
9195

96+
func Test_NoDAG_LoggingWithLimits(t *testing.T) {
97+
t.Parallel()
98+
mockExecutionHelper := NewMockExecutionHelper(t)
99+
mockExecutionHelper.EXPECT().GetWorkflowExecutionID().Return("id")
100+
mockExecutionHelper.EXPECT().GetNodeTime().RunAndReturn(func() time.Time {
101+
return time.Now()
102+
}).Maybe()
103+
mockExecutionHelper.EXPECT().GetDONTime().RunAndReturn(func() (time.Time, error) {
104+
return time.Now(), nil
105+
}).Maybe()
106+
107+
logs := []string{}
108+
mockExecutionHelper.EXPECT().EmitUserLog(mock.Anything).RunAndReturn(func(s string) error {
109+
logs = append(logs, s)
110+
return nil
111+
})
112+
113+
trigger := &basictrigger.Outputs{CoolOutput: anyTestTriggerValue}
114+
executeRequest := triggerExecuteRequest(t, 0, trigger)
115+
cfg := &ModuleConfig{
116+
Logger: logger.Test(t),
117+
IsUncompressed: true,
118+
MaxLogLenBytes: 20,
119+
MaxLogCountDONMode: 3,
120+
MaxLogCountNodeMode: 3,
121+
}
122+
123+
binary := createTestBinary(loggingLimitsBinaryCmd, loggingLimitsBinaryLocation, true, t)
124+
125+
m, err := NewModule(cfg, binary)
126+
require.NoError(t, err)
127+
128+
_, err = m.Execute(t.Context(), executeRequest, mockExecutionHelper)
129+
require.NoError(t, err)
130+
131+
// allowed 3 logs max, one of which got rejected because it was too long
132+
// so expect 2 logs to be emitted
133+
require.Equal(t, 2, len(logs))
134+
require.Equal(t, "short log 1", logs[0])
135+
require.Equal(t, "short log 3", logs[1])
136+
}
137+
92138
func defaultNoDAGModCfg(t testing.TB) *ModuleConfig {
93139
return &ModuleConfig{
94140
Logger: logger.Test(t),

0 commit comments

Comments
 (0)