Skip to content

Commit dd4e204

Browse files
committed
feat: configurable receipt timestamps
1 parent d506dde commit dd4e204

6 files changed

Lines changed: 71 additions & 21 deletions

File tree

execution/dispatcher/dispatcher.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ type handler struct {
1717
// Dispatcher executes UCAN invocations by dispatching them to registered
1818
// handlers.
1919
type Dispatcher struct {
20-
authority principal.Signer
21-
handlers map[ucan.Command]handler
22-
validationOpts []validator.Option
20+
authority principal.Signer
21+
handlers map[ucan.Command]handler
22+
validationOpts []validator.Option
23+
receiptTimestamps bool
2324
}
2425

2526
// New creates an invocation executor that executes UCAN invocations by
@@ -33,9 +34,10 @@ func New(authority principal.Signer, options ...Option) *Dispatcher {
3334
opt(&cfg)
3435
}
3536
return &Dispatcher{
36-
authority: authority,
37-
handlers: map[ucan.Command]handler{},
38-
validationOpts: cfg.validationOpts,
37+
authority: authority,
38+
handlers: map[ucan.Command]handler{},
39+
validationOpts: cfg.validationOpts,
40+
receiptTimestamps: cfg.receiptTimestamps,
3941
}
4042
}
4143

@@ -52,6 +54,7 @@ func (d *Dispatcher) Execute(req execution.Request) (execution.Response, error)
5254
return execution.NewResponse(
5355
req.Invocation().Task().Link(),
5456
execution.WithSigner(d.authority),
57+
execution.WithReceiptTimestamp(d.receiptTimestamps),
5558
execution.WithFailure(execution.NewInvalidAudienceError(d.authority, aud)),
5659
)
5760
}
@@ -62,6 +65,7 @@ func (d *Dispatcher) Execute(req execution.Request) (execution.Response, error)
6265
return execution.NewResponse(
6366
req.Invocation().Task().Link(),
6467
execution.WithSigner(d.authority),
68+
execution.WithReceiptTimestamp(d.receiptTimestamps),
6569
execution.WithFailure(NewHandlerNotFoundError(cmd)),
6670
)
6771
}
@@ -82,11 +86,16 @@ func (d *Dispatcher) Execute(req execution.Request) (execution.Response, error)
8286
return execution.NewResponse(
8387
req.Invocation().Task().Link(),
8488
execution.WithSigner(d.authority),
89+
execution.WithReceiptTimestamp(d.receiptTimestamps),
8590
execution.WithFailure(err),
8691
)
8792
}
8893

89-
res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(d.authority))
94+
res, err := execution.NewResponse(
95+
req.Invocation().Task().Link(),
96+
execution.WithSigner(d.authority),
97+
execution.WithReceiptTimestamp(d.receiptTimestamps),
98+
)
9099
if err != nil {
91100
return nil, fmt.Errorf("failed to create response: %w", err)
92101
}
@@ -96,6 +105,7 @@ func (d *Dispatcher) Execute(req execution.Request) (execution.Response, error)
96105
return execution.NewResponse(
97106
req.Invocation().Task().Link(),
98107
execution.WithSigner(d.authority),
108+
execution.WithReceiptTimestamp(d.receiptTimestamps),
99109
execution.WithFailure(execution.NewHandlerExecutionError(cmd, err)),
100110
)
101111
}

execution/dispatcher/options.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ import (
88
type Option func(cfg *execConfig)
99

1010
type execConfig struct {
11-
validationOpts []validator.Option
11+
validationOpts []validator.Option
12+
receiptTimestamps bool
1213
}
1314

1415
func WithValidationOptions(options ...validator.Option) Option {
1516
return func(cfg *execConfig) {
1617
cfg.validationOpts = append(cfg.validationOpts, options...)
1718
}
1819
}
20+
21+
// WithReceiptTimestamps configures the dispatcher to issue receipts with
22+
// issuance timestamps or not.
23+
func WithReceiptTimestamps(enabled bool) Option {
24+
return func(cfg *execConfig) {
25+
cfg.receiptTimestamps = enabled
26+
}
27+
}

execution/response.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import (
1414
)
1515

1616
type ExecResponse struct {
17-
signer ucan.Signer
18-
task cid.Cid
19-
receipt ucan.Receipt
20-
metadata ucan.Container
17+
signer ucan.Signer
18+
task cid.Cid
19+
receipt ucan.Receipt
20+
metadata ucan.Container
21+
receiptTimestamp bool
2122
}
2223

2324
type ResponseOption func(r *ExecResponse) error
@@ -36,6 +37,16 @@ func WithReceipt(receipt ucan.Receipt) ResponseOption {
3637
}
3738
}
3839

40+
// WithReceiptTimestamp configures the response to issue receipts with
41+
// issuance timestamps. Note: this option should be ordered before [WithSuccess]
42+
// or [WithFailure], since these options issue a receipt.
43+
func WithReceiptTimestamp(enabled bool) ResponseOption {
44+
return func(resp *ExecResponse) error {
45+
resp.receiptTimestamp = enabled
46+
return nil
47+
}
48+
}
49+
3950
// WithSuccess issues and sets a receipt for a successful execution of a task.
4051
func WithSuccess(o ipld.Any) ResponseOption {
4152
return func(resp *ExecResponse) error {

server/http.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ func NewHTTP(id principal.Signer, options ...HTTPOption) *HTTPServer {
3131
for _, opt := range options {
3232
opt(&cfg)
3333
}
34-
executor := dispatcher.New(id, dispatcher.WithValidationOptions(cfg.validationOpts...))
34+
executor := dispatcher.New(
35+
id,
36+
dispatcher.WithValidationOptions(cfg.validationOpts...),
37+
dispatcher.WithReceiptTimestamps(cfg.receiptTimestamps),
38+
)
3539
return &HTTPServer{
36-
id: id,
37-
codec: cfg.codec,
38-
executor: executor,
40+
id: id,
41+
codec: cfg.codec,
42+
executor: executor,
43+
listeners: cfg.listeners,
3944
}
4045
}
4146

server/http_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"io"
55
"net/http"
66
"testing"
7+
"time"
78

89
"github.com/alanshaw/ucantone/execution"
910
"github.com/alanshaw/ucantone/ipld"
@@ -22,7 +23,7 @@ func TestHTTPServer(t *testing.T) {
2223
alice := testutil.RandomSigner(t)
2324

2425
t.Run("invocation execution round trip", func(t *testing.T) {
25-
server := server.NewHTTP(service)
26+
server := server.NewHTTP(service, server.WithReceiptTimestamps(true))
2627

2728
var messages []ipld.Any
2829
server.Handle(testutil.ConsoleLogCapability, func(req execution.Request, res execution.Response) error {
@@ -97,8 +98,13 @@ func TestHTTPServer(t *testing.T) {
9798
require.NoError(t, err)
9899

99100
require.Len(t, ctResp.Receipts(), 1)
101+
rcpt := ctResp.Receipts()[0]
100102

101-
o, x := result.Unwrap(ctResp.Receipts()[0].Out())
103+
require.NotNil(t, rcpt.IssuedAt())
104+
// we can't assert an exact timestamp, but check that it is recent
105+
require.GreaterOrEqual(t, int64(*rcpt.IssuedAt()), time.Now().Add(-time.Second).Unix())
106+
107+
o, x := result.Unwrap(rcpt.Out())
102108
require.NotNil(t, o)
103109
require.Nil(t, x)
104110
t.Log(o)

server/options.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111
type HTTPOption func(cfg *httpServerConfig)
1212

1313
type httpServerConfig struct {
14-
codec transport.InboundCodec[*http.Request, *http.Response]
15-
validationOpts []validator.Option
16-
listeners []EventListener
14+
codec transport.InboundCodec[*http.Request, *http.Response]
15+
validationOpts []validator.Option
16+
receiptTimestamps bool
17+
listeners []EventListener
1718
}
1819

1920
func WithHTTPCodec(codec transport.InboundCodec[*http.Request, *http.Response]) HTTPOption {
@@ -28,6 +29,14 @@ func WithValidationOptions(options ...validator.Option) HTTPOption {
2829
}
2930
}
3031

32+
// WithReceiptTimestamps configures the server to issue receipts with
33+
// issuance timestamps or not.
34+
func WithReceiptTimestamps(enabled bool) HTTPOption {
35+
return func(cfg *httpServerConfig) {
36+
cfg.receiptTimestamps = enabled
37+
}
38+
}
39+
3140
// WithEventListener adds an event listener to the HTTP server for monitoring
3241
// requests and responses.
3342
func WithEventListener(listener EventListener) HTTPOption {

0 commit comments

Comments
 (0)