Skip to content

Commit 880f18e

Browse files
krpeacockilberttnathanosdev
authored
feat!: breaking out readState into signed and unsigned (#1000)
Co-authored-by: Luca Bertelli <luca.bertelli@dfinity.org> Co-authored-by: Nathan Mc Grath <contact@nathanos.dev>
1 parent 0972640 commit 880f18e

File tree

28 files changed

+1166
-1594
lines changed

28 files changed

+1166
-1594
lines changed

docs/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
### Changed
66

7+
- feat!: changes polling strategy for `read_state` requests to support presigned requests. By default, `read_state` requests will create a new signature with a new ingress expiry each time they are made. However, the new `preSignReadStateRequest` will make one signature and use it for all polling requests. This is useful for hardware wallets or other external signing solutions that make signing cumbersome.
8+
- pollForResponse now moves `strategy`, `request`, and `preSignReadStateRequest` to the `options: PollingOptions` object
9+
- new export: `PollingOptions` type
10+
- Actor also includes a `pollingOptions` object that can be passed to the `actor` function and will be passed to the `pollForResponse` method
711
- feat!: removes the unused `defaultAgent` global concept and the `getDefaultAgent` function. The `HttpAgent` constructor is now the only way to create an agent.
812
- feat!: removes the `ProxyAgent` class.
913
- chore: formatting files and changelog

e2e/node/basic/basic.test.ts

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
Certificate,
44
LookupResultFound,
55
LookupStatus,
6+
bufFromBufLike,
67
getManagementCanister,
8+
strToUtf8,
79
} from '@dfinity/agent';
810
import { IDL } from '@dfinity/candid';
911
import { Principal } from '@dfinity/principal';
@@ -15,8 +17,8 @@ import { test, expect } from 'vitest';
1517
* @returns the default effective canister id
1618
*/
1719
export async function getDefaultEffectiveCanisterId() {
18-
const res = await fetch('http://localhost:4943/_/topology'); //?
19-
const data = await res.json(); //?
20+
const res = await fetch('http://localhost:4943/_/topology');
21+
const data = await res.json();
2022
const id = data['default_effective_canister_id']['canister_id'];
2123
// decode from base64
2224
const decoded = Buffer.from(id, 'base64').toString('hex');
@@ -39,7 +41,7 @@ test('read_state', async () => {
3941
const ecid = await getDefaultEffectiveCanisterId();
4042
const resolvedAgent = await agent;
4143
const now = Date.now() / 1000;
42-
const path = [new TextEncoder().encode('time')];
44+
const path = [strToUtf8('time')];
4345
const response = await resolvedAgent.readState(ecid, {
4446
paths: [path],
4547
});
@@ -49,7 +51,7 @@ test('read_state', async () => {
4951
rootKey: resolvedAgent.rootKey,
5052
canisterId: ecid,
5153
});
52-
expect(cert.lookup([new TextEncoder().encode('Time')])).toEqual({
54+
expect(cert.lookup([strToUtf8('Time')])).toEqual({
5355
status: LookupStatus.Unknown,
5456
});
5557

@@ -63,10 +65,12 @@ test('read_state', async () => {
6365

6466
const decoded = IDL.decode(
6567
[IDL.Nat],
66-
new Uint8Array([
67-
...new TextEncoder().encode('DIDL\x00\x01\x7d'),
68-
...(new Uint8Array(rawTime.value) || []),
69-
]),
68+
bufFromBufLike(
69+
new Uint8Array([
70+
...new TextEncoder().encode('DIDL\x00\x01\x7d'),
71+
...(new Uint8Array(rawTime.value) || []),
72+
]),
73+
),
7074
)[0];
7175
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7276
const time = Number(decoded as any) / 1e9;
@@ -77,24 +81,17 @@ test('read_state', async () => {
7781
test('read_state with passed request', async () => {
7882
const resolvedAgent = await agent;
7983
const now = Date.now() / 1000;
80-
const path = [new TextEncoder().encode('time')];
84+
const path = [strToUtf8('time')];
8185
const canisterId = await getDefaultEffectiveCanisterId();
8286
const request = await resolvedAgent.createReadStateRequest({ paths: [path] });
83-
const response = await resolvedAgent.readState(
84-
canisterId,
85-
{
86-
paths: [path],
87-
},
88-
undefined,
89-
request,
90-
);
87+
const response = await resolvedAgent.readState(canisterId, { paths: [path] }, undefined, request);
9188
if (resolvedAgent.rootKey == null) throw new Error(`The agent doesn't have a root key yet`);
9289
const cert = await Certificate.create({
9390
certificate: response.certificate,
9491
rootKey: resolvedAgent.rootKey,
9592
canisterId: canisterId,
9693
});
97-
expect(cert.lookup([new TextEncoder().encode('Time')])).toEqual({
94+
expect(cert.lookup([strToUtf8('Time')])).toEqual({
9895
status: LookupStatus.Unknown,
9996
});
10097

@@ -108,10 +105,12 @@ test('read_state with passed request', async () => {
108105

109106
const decoded = IDL.decode(
110107
[IDL.Nat],
111-
new Uint8Array([
112-
...new TextEncoder().encode('DIDL\x00\x01\x7d'),
113-
...(new Uint8Array(rawTime.value) || []),
114-
]),
108+
bufFromBufLike(
109+
new Uint8Array([
110+
...new TextEncoder().encode('DIDL\x00\x01\x7d'),
111+
...(new Uint8Array(rawTime.value) || []),
112+
]),
113+
),
115114
)[0];
116115
// eslint-disable-next-line @typescript-eslint/no-explicit-any
117116
const time = Number(decoded as any) / 1e9;

e2e/node/basic/counter.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Actor, HttpAgent } from '@dfinity/agent';
1+
import { Actor, HttpAgent, ActorMethod } from '@dfinity/agent';
22
import counterCanister, { idl } from '../canisters/counter';
33
import { it, expect, describe, vi } from 'vitest';
44

@@ -34,7 +34,71 @@ describe('counter', () => {
3434
expected += 1;
3535
}
3636
}, 40000);
37+
38+
it('should work with preSignReadStateRequest enabled', async () => {
39+
const { canisterId } = await counterCanister();
40+
41+
// Create counter actor with preSignReadStateRequest enabled
42+
const counter = await Actor.createActor(idl, {
43+
canisterId,
44+
agent: await HttpAgent.create({
45+
host: 'http://localhost:4943',
46+
shouldFetchRootKey: true,
47+
}),
48+
pollingOptions: {
49+
preSignReadStateRequest: true,
50+
},
51+
});
52+
53+
// Reset counter to 0
54+
await counter.write(0);
55+
expect(Number(await counter.read())).toEqual(0);
56+
57+
// Call inc_read which will trigger polling and use preSignReadStateRequest
58+
const result = Number(await counter.inc_read());
59+
60+
// Verify the operation was successful
61+
expect(result).toEqual(1);
62+
63+
// Verify the state was actually updated
64+
expect(Number(await counter.read())).toEqual(1);
65+
}, 40000);
66+
67+
it('should allow method-specific pollingOptions override', async () => {
68+
const { canisterId } = await counterCanister();
69+
70+
// Create counter actor without preSignReadStateRequest
71+
const counter = await Actor.createActor(idl, {
72+
canisterId,
73+
agent: await HttpAgent.create({
74+
host: 'http://localhost:4943',
75+
shouldFetchRootKey: true,
76+
}),
77+
});
78+
79+
// Reset counter to 0
80+
await counter.write(0);
81+
expect(Number(await counter.read())).toEqual(0);
82+
83+
// Use withOptions to set preSignReadStateRequest for this specific call
84+
const result = Number(
85+
await (counter.inc_read as ActorMethod).withOptions({
86+
pollingOptions: {
87+
preSignReadStateRequest: true,
88+
// Custom polling strategy with simple 1-second delay
89+
strategy: () => new Promise(resolve => setTimeout(resolve, 1000)),
90+
},
91+
})(),
92+
);
93+
94+
// Verify the operation was successful
95+
expect(result).toEqual(1);
96+
97+
// Verify the state was actually updated
98+
expect(Number(await counter.read())).toEqual(1);
99+
}, 40000);
37100
});
101+
38102
describe('retrytimes', () => {
39103
it('should retry after a failure', async () => {
40104
let count = 0;

e2e/node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"size-limit": "^8.2.6",
3838
"text-encoding": "^0.7.0",
3939
"ts-node": "^10.8.2",
40-
"typescript": "^5.2.2",
40+
"typescript": "^5.7.2",
4141
"vitest": "^3.0.8"
4242
},
4343
"optionalDependencies": {

0 commit comments

Comments
 (0)