Skip to content

Commit 0dc24ee

Browse files
Create SDK Time Hook (#1478)
* Use node time * Update execution.go * Remove ctx * Use Node Mode for clockTimeGet * Undo secondary hook * Use mode * Secondary time hook * Implement SDK now * Fix expected DonTime calls * Panic on err
1 parent 40de7f5 commit 0dc24ee

File tree

5 files changed

+49
-14
lines changed

5 files changed

+49
-14
lines changed

pkg/workflows/wasm/host/execution.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func (e *execution[T]) switchModes(_ *wasmtime.Caller, mode int32) {
170170
// clockTimeGet is the default time.Now() which is also called by Go many times.
171171
// This implementation uses Node Mode to not have to wait for OCR rounds.
172172
func (e *execution[T]) clockTimeGet(caller *wasmtime.Caller, id int32, precision int64, resultTimestamp int32) int32 {
173-
donTime, err := e.timeFetcher.GetTime(e.mode)
173+
donTime, err := e.timeFetcher.GetTime(sdkpb.Mode_MODE_NODE)
174174
if err != nil {
175175
return ErrnoInval
176176
}
@@ -198,6 +198,21 @@ func (e *execution[T]) clockTimeGet(caller *wasmtime.Caller, id int32, precision
198198
return ErrnoSuccess
199199
}
200200

201+
// now is used by rawsdk for Workflows and should be called instead of Go's time.Now().
202+
func (e *execution[T]) now(caller *wasmtime.Caller, resultTimestamp int32) int32 {
203+
donTime, err := e.timeFetcher.GetTime(e.mode)
204+
if err != nil {
205+
return ErrnoInval
206+
}
207+
208+
val := donTime.UnixNano()
209+
uint64Size := int32(8)
210+
trg := make([]byte, uint64Size)
211+
binary.LittleEndian.PutUint64(trg, uint64(val))
212+
wasmWrite(caller, trg, resultTimestamp, uint64Size)
213+
return ErrnoSuccess
214+
}
215+
201216
// Loosely based off the implementation here:
202217
// https://github.com/tetratelabs/wazero/blob/main/imports/wasi_snapshot_preview1/poll.go#L52
203218
// For an overview of the spec, including the datatypes being referred to, see:

pkg/workflows/wasm/host/internal/rawsdk/helpers_wasip1.go

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

33
import (
44
"encoding/base64"
5+
"encoding/binary"
56
"errors"
67
"fmt"
78
"os"
9+
"time"
810
"unsafe"
911

1012
"google.golang.org/protobuf/proto"
@@ -15,6 +17,10 @@ import (
1517
valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb"
1618
)
1719

20+
const (
21+
ErrnoSuccess = 0
22+
)
23+
1824
func GetRequest() *sdk.ExecuteRequest {
1925
if len(os.Args) != 2 {
2026
SendError(errors.New("invalid request: request must contain a payload"))
@@ -54,6 +60,16 @@ func SendSubscription(subscriptions *sdk.TriggerSubscriptionRequest) {
5460
sendResponse(BufferToPointerLen(Must(proto.Marshal(execResult))))
5561
}
5662

63+
func Now() time.Time {
64+
var buf [8]byte // host writes UnixNano as little-endian uint64
65+
rc := now(unsafe.Pointer(&buf[0]))
66+
if rc != ErrnoSuccess {
67+
panic(fmt.Errorf("failed to fetch time from host: now() returned errno %d", rc))
68+
}
69+
ns := int64(binary.LittleEndian.Uint64(buf[:]))
70+
return time.Unix(0, ns)
71+
}
72+
5773
var donCall = int32(0)
5874
var nodeCall = int32(-1)
5975

@@ -211,6 +227,9 @@ func sendResponse(response unsafe.Pointer, responseLen int32) int32
211227
//go:wasmimport env switch_modes
212228
func SwitchModes(mode int32)
213229

230+
//go:wasmimport env now
231+
func now(response unsafe.Pointer) int32
232+
214233
//go:wasmimport env call_capability
215234
func callCapability(req unsafe.Pointer, reqLen int32) int64
216235

pkg/workflows/wasm/host/module.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,13 @@ func linkNoDAG(m *module, store *wasmtime.Store, exec *execution[*sdkpb.Executio
330330
return nil, fmt.Errorf("error wrapping getSeed func: %w", err)
331331
}
332332

333+
if err = linker.FuncWrap(
334+
"env",
335+
"now",
336+
exec.now); err != nil {
337+
return nil, fmt.Errorf("error wrapping get_time func: %w", err)
338+
}
339+
333340
return linker.Instantiate(store, m.module)
334341
}
335342

pkg/workflows/wasm/host/standard_test.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,20 @@ func TestStandardModeSwitch(t *testing.T) {
120120
mockExecutionHelper := NewMockExecutionHelper(t)
121121
mockExecutionHelper.EXPECT().GetWorkflowExecutionID().Return("id")
122122
// Node calls may occur on initialization depending on the language.
123-
var donCall1 bool
123+
var donCall bool
124124
var nodeCall bool
125-
var donCall2 bool
126125
mockExecutionHelper.EXPECT().GetNodeTime().RunAndReturn(func() time.Time {
127-
if donCall1 {
126+
if donCall {
128127
nodeCall = true
129128
}
130129
return time.Now()
131130
})
132131

133132
// We want to make sure time.Now() is called at least twice in DON mode and once in node Mode
134133
mockExecutionHelper.EXPECT().GetDONTime().RunAndReturn(func() (time.Time, error) {
135-
if nodeCall {
136-
donCall2 = true
137-
} else {
138-
donCall1 = true
139-
}
134+
donCall = true
140135
return time.Now(), nil
141-
})
136+
}).Times(2)
142137
mockExecutionHelper.EXPECT().CallCapability(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, request *sdk.CapabilityRequest) (*sdk.CapabilityResponse, error) {
143138
if request.Id == "[email protected]" {
144139
input := &basicaction.Inputs{}
@@ -157,9 +152,8 @@ func TestStandardModeSwitch(t *testing.T) {
157152
request := triggerExecuteRequest(t, 0, &basictrigger.Outputs{CoolOutput: anyTestTriggerValue})
158153
result := executeWithResult[string](t, m, request, mockExecutionHelper)
159154
require.Equal(t, "test556", result)
160-
require.True(t, donCall1)
155+
require.True(t, donCall)
161156
require.True(t, nodeCall)
162-
require.True(t, donCall2)
163157
})
164158

165159
t.Run("node runtime in don mode", func(t *testing.T) {

pkg/workflows/wasm/host/standard_tests/mode_switch/successful_mode_switch/main_wasip1.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ func main() {
4848
rawsdk.SendResponse(fmt.Sprintf("%s%d", doutput.AdaptedThing, coutput.OutputThing))
4949
}
5050

51-
// ignoreTimeCall makes a time now call and forces the compiler not to optimize it away.
51+
// ignoreTimeCall makes a rawsdk now call and forces the compiler not to optimize it away.
5252
func ignoreTimeCall() {
53-
t := time.Now()
53+
t := rawsdk.Now()
5454
if t.Before(time.Unix(-1, 322)) {
5555
panic("Test should not run before 1970")
5656
}

0 commit comments

Comments
 (0)