@cardano-sdk/e2e 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.env.example +2 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +5 -1
  4. package/dist/cjs/environment.d.ts +8 -2
  5. package/dist/cjs/environment.d.ts.map +1 -1
  6. package/dist/cjs/environment.js +21 -1
  7. package/dist/cjs/environment.js.map +1 -1
  8. package/dist/cjs/factories.d.ts +7 -3
  9. package/dist/cjs/factories.d.ts.map +1 -1
  10. package/dist/cjs/factories.js +26 -4
  11. package/dist/cjs/factories.js.map +1 -1
  12. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  13. package/dist/esm/environment.d.ts +8 -2
  14. package/dist/esm/environment.d.ts.map +1 -1
  15. package/dist/esm/environment.js +21 -1
  16. package/dist/esm/environment.js.map +1 -1
  17. package/dist/esm/factories.d.ts +7 -3
  18. package/dist/esm/factories.d.ts.map +1 -1
  19. package/dist/esm/factories.js +25 -3
  20. package/dist/esm/factories.js.map +1 -1
  21. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  22. package/jest.config.js +1 -1
  23. package/package.json +17 -16
  24. package/src/environment.ts +29 -1
  25. package/src/factories.ts +63 -13
  26. package/test/artillery/wallet-restoration/types.ts +2 -2
  27. package/test/k6/endpoints/asset/get-asset.test.js +22 -0
  28. package/test/k6/endpoints/asset/get-assets.test.js +278 -0
  29. package/test/k6/endpoints/chain-history/blocks/by-hashes.test.js +19 -0
  30. package/test/k6/endpoints/chain-history/txs/by-addresses.test.js +23 -0
  31. package/test/k6/endpoints/chain-history/txs/by-hashes.test.js +19 -0
  32. package/test/k6/endpoints/network-info/era-summaries.test.js +18 -0
  33. package/test/k6/endpoints/network-info/genesis-parameters.test.js +18 -0
  34. package/test/k6/endpoints/network-info/ledger-tip.test.js +18 -0
  35. package/test/k6/endpoints/network-info/lovelace-supply.test.js +18 -0
  36. package/test/k6/endpoints/network-info/protocol-parameters.test.js +18 -0
  37. package/test/k6/endpoints/network-info/stake.test.js +18 -0
  38. package/test/k6/endpoints/rewards/account-balance.test.js +19 -0
  39. package/test/k6/endpoints/stake-pool/stats.test.js +18 -0
  40. package/test/k6/endpoints/utxo/utxo-by-addresses.test.js +19 -0
  41. package/test/k6/scenarios/tx-submission.test.js +64 -0
  42. package/test/k6/scenarios/wallet-creation.test.js +211 -0
  43. package/test/k6/scenarios/wallet-restoration.test.js +192 -0
  44. package/test/load-test-custom/wallet-init/wallet-init.test.ts +4 -4
  45. package/test/load-test-custom/wallet-restoration/wallet-restoration.test.ts +2 -2
  46. package/test/long-running/delegation-rewards.test.ts +3 -3
  47. package/test/pg-boss/stake-pool-metrics.test.ts +44 -0
  48. package/test/util.ts +3 -3
  49. package/test/wallet/{SingleAddressWallet → PersonalWallet}/byron.test.ts +3 -3
  50. package/test/wallet/{SingleAddressWallet → PersonalWallet}/delegation.test.ts +1 -1
  51. package/test/wallet/{SingleAddressWallet → PersonalWallet}/metadata.test.ts +3 -3
  52. package/test/wallet/{SingleAddressWallet → PersonalWallet}/mint.test.ts +3 -3
  53. package/test/wallet/PersonalWallet/multiAddress.test.ts +158 -0
  54. package/test/wallet/{SingleAddressWallet → PersonalWallet}/multisignature.test.ts +3 -3
  55. package/test/wallet/{SingleAddressWallet → PersonalWallet}/nft.test.ts +6 -4
  56. package/test/wallet/{SingleAddressWallet → PersonalWallet}/phase2validation.test.ts +4 -4
  57. package/test/wallet/{SingleAddressWallet → PersonalWallet}/pouchDbWalletStores.test.ts +2 -2
  58. package/test/wallet/{SingleAddressWallet → PersonalWallet}/txChainHistory.test.ts +3 -3
  59. package/test/wallet/{SingleAddressWallet → PersonalWallet}/txChaining.test.ts +1 -1
  60. package/test/wallet/{SingleAddressWallet → PersonalWallet}/unspendableUtxos.test.ts +4 -4
  61. package/test/web-extension/.env.example +2 -0
@@ -0,0 +1,19 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ const body = JSON.stringify({ids:["ae57a7127813a9a00116fa137cf4e0757cb77dc72bdae1e09a843e5f57873957"]})
14
+ http.post(`${PROVIDER_SERVER_URL}/chain-history/txs/by-hashes`, body, {
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/era-summaries`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/genesis-parameters`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/ledger-tip`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/lovelace-supply`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/protocol-parameters`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/network-info/stake`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,19 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ const body = JSON.stringify({rewardAccount: "stake1uygxsnx9gxhn3pkq2e9k97wza02c6vl7njuk5x7fs0tpjgc9qf8ag"})
14
+ http.post(`${PROVIDER_SERVER_URL}/rewards/account-balance`, body, {
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,18 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ http.post(`${PROVIDER_SERVER_URL}/stake-pool/stats`, '{}', {
14
+ headers: {
15
+ 'content-type': 'application/json',
16
+ },
17
+ });
18
+ }
@@ -0,0 +1,19 @@
1
+ import http from 'k6/http';
2
+
3
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
4
+
5
+ export const options = {
6
+ thresholds: {
7
+ http_req_failed: ['rate<0.01'],
8
+ http_req_duration: ['p(95)<500'],
9
+ },
10
+ };
11
+
12
+ export default function () {
13
+ const body = JSON.stringify({addresses:["addr1q9tz7a36xj7yueynqykp99897qrvpwveef4z6l8z09uuujd5wnak0wyqqhaw7s4c5wpwfkf26u9adszzdp39p2kdf73q3ygrvy"]})
14
+ http.post(`${PROVIDER_SERVER_URL}/utxo/utxo-by-addresses`, body, {
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,64 @@
1
+ import http from 'k6/http';
2
+ import { Counter } from 'k6/metrics';
3
+
4
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
5
+
6
+ http.setResponseCallback(
7
+ http.expectedStatuses(400)
8
+ );
9
+
10
+ const INVALID_TX =
11
+ '84a400818258201145fe128b5bc6d5b369ab06eae8b3d85705273184e1c420cbe1bc112af886cf02018282583901dbff800b7afbd55960e261a5988c30063602d4be2ab347eeb4b42b47e2ea03ca80cfecf85c4839a05db1c55d5f5b70773409d0f6895c51881a0292a60482583901d020e5ab3faa7e90daff03c780c07c210fda6e4990d7c169c421398fe2ea03ca80cfecf85c4839a05db1c55d5f5b70773409d0f6895c51881a000f4240021a0002917d031a0549823fa1008182582099280dfe10ded05fa081bb298c5b99150951bd231b98120bd406d6155461642458405ff5e145bf054ebae5b9fc2f7712b57f0c68d490edc5c2d16c6cc4633b7a05f677e996048121fd5275fc22575cdc3317bf9dc107d679ed2cfd528c90fda30f00f5f6';
12
+
13
+ const submissionCount = new Counter('submission_count');
14
+
15
+ export const options = {
16
+ ext: {
17
+ loadimpact: {
18
+ distribution: { 'amazon:de:frankfurt': { loadZone: 'amazon:de:frankfurt', percent: 100 } },
19
+ apm: []
20
+ }
21
+ },
22
+ scenarios: {
23
+ TenPerMin: {
24
+ duration: '10m',
25
+ exec: 'submitTx',
26
+ executor: 'constant-arrival-rate',
27
+ gracefulStop: '0s',
28
+ preAllocatedVUs: 1,
29
+ rate: 10,
30
+ timeUnit: '1m'
31
+ },
32
+ SixtyPerMin: {
33
+ duration: '10m',
34
+ exec: 'submitTx',
35
+ executor: 'constant-arrival-rate',
36
+ gracefulStop: '0s',
37
+ preAllocatedVUs: 1,
38
+ rate: 60,
39
+ startTime: '10m',
40
+ timeUnit: '1m'
41
+ },
42
+ TwoHundredPerMin: {
43
+ duration: '10m',
44
+ exec: 'submitTx',
45
+ executor: 'constant-arrival-rate',
46
+ gracefulStop: '0s',
47
+ preAllocatedVUs: 1,
48
+ rate: 200,
49
+ startTime: '20m',
50
+ timeUnit: '1m'
51
+ }
52
+ }
53
+ };
54
+
55
+ /** Util functions for sending the http post requests to cardano-sdk services */
56
+ const cardanoHttpPost = (url, body = {}) => {
57
+ const options = { headers: { 'content-type': 'application/json' } };
58
+ return http.post(`${PROVIDER_SERVER_URL}/${url}`, JSON.stringify(body), options);
59
+ };
60
+
61
+ export default function() {
62
+ cardanoHttpPost('tx-submit/submit', {signedTransaction: INVALID_TX});
63
+ submissionCount.add(1);
64
+ }
@@ -0,0 +1,211 @@
1
+ import http from 'k6/http';
2
+ import { check, sleep } from 'k6';
3
+ import { Counter, Trend } from 'k6/metrics';
4
+
5
+ /**
6
+ * Script overall description:
7
+ * One wallet == one VU
8
+ * Configure RUN_MODE to have the test run with empty wallets (onboarding) or wallets with history (restoring)
9
+ * Configure MAX_VU: number of wallets to be synced (e.g. 100 wallets)
10
+ * Setup RAMP_UP_DURATION: ramp-up time to synced these wallets (e.g. 30s)
11
+ * - During this time, wallets start evenly distributed (e.g. ~3 wallets every second)
12
+ * Each wallet performs as many iterations as possible
13
+ * - 1st iteration: restoration steps .
14
+ * - Subsequent iterations will only query the tip
15
+ * ITERATION_SLEEP: Every iteration has a sleep of to simulate Lace tip polling interval
16
+ * During the STEADY_STATE_DURATION:
17
+ * - Synced wallets do tip queries.
18
+ * - Wallets not synced yet will wait for restoration to complete, then will also start tip queries
19
+ * No ramp-down is needed. We can simply stop the test as there is no point in sending fewer and fewer tip queries during ramp-down.
20
+ * wallet_sync: is a custom trend metric measuring the trend of wallet sync calls.
21
+ * wallet_sync_count: is a custom count metric measuring the number of wallets that were successfully synced.
22
+ */
23
+
24
+ const RunMode = {
25
+ Onboard: 'Onboard',
26
+ Restore: 'Restore'
27
+ };
28
+ /** Determines run mode: Restore or Onboard */
29
+ const RUN_MODE = RunMode.Onboard;
30
+
31
+ /**
32
+ * True: Keep spinning on the restoring part
33
+ * False: Once restored, just loop over ledger tip
34
+ */
35
+ const SPIN_ON_NEW_WALLETS = true;
36
+
37
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
38
+
39
+ /** URL of the JSON file containing the wallets */
40
+ const WALLET_ADDRESSES_URL =
41
+ RUN_MODE === RunMode.Restore
42
+ ? 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/master/packages/e2e/test/dump/addresses/mainnet.json'
43
+ : 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/master/packages/e2e/test/dump/addresses/no-history-mainnet.json';
44
+
45
+ /** URL of the JSON file containing the stake pool addresses */
46
+ const POOL_ADDRESSES_URL =
47
+ 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/master/packages/e2e/test/dump/pool_addresses/mainnet.json';
48
+
49
+ /**
50
+ * Define the maximum number of virtual users to simulate
51
+ * The mainnet.json file contains wallets in chunks of 100, each chunk having the required distribution
52
+ * For this reason, it's a good practice to configure MAX_VUs in multiples of 100 in order to maintain the desired distribution
53
+ */
54
+ const MAX_VU = 1;
55
+
56
+ /** Time span during which all virtual users are started in a linear fashion */
57
+ const RAMP_UP_DURATION = '10s';
58
+ /** Time span during which synced wallets do tip queries */
59
+ const STEADY_STATE_DURATION = '1m';
60
+
61
+ /** Time to sleep between iterations. Simulates polling tip to keep wallet in sync */
62
+ const ITERATION_SLEEP = 5;
63
+
64
+ /** Custom trend statistic to measure trend to sync wallets */
65
+ const walletSyncTrend = new Trend('wallet_sync', true);
66
+ /** Custom count statistic to measure how many wallets were successfully syncd */
67
+ const walletSyncCount = new Counter('wallet_sync_count');
68
+
69
+ /** Grab the wallets json file to be used by the scenario */
70
+ export function setup() {
71
+ console.log(`Running in ${RUN_MODE} mode`);
72
+ let res = http.batch([WALLET_ADDRESSES_URL, POOL_ADDRESSES_URL]);
73
+ check(res, { 'get wallets and pools files': (r) => r.every(({ status }) => status >= 200 && status < 300) });
74
+
75
+ const [{ body: resBodyWallets }, { body: resBodyPools }] = res;
76
+ const wallets = JSON.parse(resBodyWallets);
77
+ const walletCount = wallets ? wallets.length : 0;
78
+ check(walletCount, {
79
+ 'At least one wallet is required to run the test': (count) => count > 0
80
+ });
81
+ console.log(`Wallet addresses configuration file contains ${walletCount} wallets`);
82
+ if (walletCount < MAX_VU) {
83
+ console.warn(
84
+ `Requested VU count: (${MAX_VU}), is greater than the available walled addresses: ${walletCount}. Some addresses will be reused`
85
+ );
86
+ }
87
+
88
+ const poolAddresses = JSON.parse(resBodyPools);
89
+ check(poolAddresses, {
90
+ 'At least one stake pool address is required to run the test': (p) => p && p.length
91
+ });
92
+ return { wallets: wallets.slice(0, MAX_VU), poolAddresses };
93
+ }
94
+
95
+ /** Keeps track of wallets that were successfully syncd to avoid restoring twice */
96
+ const syncedWallets = new Set();
97
+
98
+ export let options = {
99
+ ext: {
100
+ loadimpact: {
101
+ distribution: { 'amazon:us:portland': { loadZone: 'amazon:us:portland', percent: 100 } },
102
+ apm: []
103
+ }
104
+ },
105
+ thresholds: {
106
+ wallet_sync_count: ['count >= ' + MAX_VU], // All wallets should have syncd
107
+ // Use https://k6.io/docs/using-k6/thresholds/ to set more thresholds. E.g.:
108
+ // wallet_sync: ['p(95)<5000'], // 95% of wallets should sync in under 5 seconds
109
+ wallet_sync: [{ threshold: 'p(95) < 30000', delayAbortEval: '5s' }] // We get a nice graph if we enable thresholds. See this stat on a graph
110
+ },
111
+ scenarios: {
112
+ SyncDifferentSizeWallets: {
113
+ executor: 'ramping-vus',
114
+ startVUs: 1,
115
+ stages: [
116
+ { duration: RAMP_UP_DURATION, target: MAX_VU },
117
+ { duration: STEADY_STATE_DURATION, target: MAX_VU }
118
+ ],
119
+ gracefulRampDown: '0s',
120
+ gracefulStop: '0s'
121
+ }
122
+ }
123
+ };
124
+
125
+ /** Util functions for sending the http post requests to cardano-sdk services */
126
+ const cardanoHttpPost = (url, body = {}) => {
127
+ const options = { headers: { 'content-type': 'application/json' } };
128
+ return http.post(`${PROVIDER_SERVER_URL}/${url}`, JSON.stringify(body), options);
129
+ };
130
+ const txsByAddress = (address) => {
131
+ let startAt = 0;
132
+ const pageSize = 25;
133
+ let txCount = 0;
134
+ do {
135
+ const resp = cardanoHttpPost('chain-history/txs/by-addresses', {
136
+ addresses: [address],
137
+ blockRange: { lowerBound: { __type: 'undefined' } },
138
+ pagination: { limit: pageSize, startAt }
139
+ });
140
+
141
+ if (resp.status !== 200) {
142
+ // No point in trying to get the other pages.
143
+ // Should we log this? it will show up as if the restoration was quicker since this wallet did not fetch all the pages
144
+ break;
145
+ }
146
+
147
+ const { pageResults } = JSON.parse(resp.body);
148
+ startAt += pageSize;
149
+ txCount = pageResults.length;
150
+ } while (txCount === pageSize);
151
+ };
152
+ const utxosByAddresses = (address) => cardanoHttpPost('utxo/utxo-by-addresses', { addresses: [address] });
153
+ const rewardsAccBalance = (rewardAccount) =>
154
+ cardanoHttpPost('rewards/account-balance', { rewardAccount: rewardAccount });
155
+ const stakePoolSearch = (poolAddress) =>
156
+ cardanoHttpPost('stake-pool/search', {
157
+ filters: { identifier: { values: [{ id: poolAddress }] } },
158
+ pagination: { limit: 1, startAt: 0 }
159
+ });
160
+
161
+ /** Simulation of requests performed by a wallet while restoring */
162
+ const syncWallet = ({ wallet, poolAddress }) => {
163
+ const startTime = Date.now();
164
+ const { address, stake_address: rewardAccount } = wallet;
165
+ cardanoHttpPost('network-info/era-summaries');
166
+ cardanoHttpPost('network-info/ledger-tip');
167
+ txsByAddress(address);
168
+ utxosByAddresses(address);
169
+ cardanoHttpPost('network-info/era-summaries');
170
+ cardanoHttpPost('network-info/genesis-parameters');
171
+ cardanoHttpPost('network-info/protocol-parameters');
172
+ rewardsAccBalance(rewardAccount);
173
+ cardanoHttpPost('network-info/ledger-tip');
174
+ cardanoHttpPost('network-info/lovelace-supply');
175
+ cardanoHttpPost('network-info/stake');
176
+ if (RUN_MODE === RunMode.Restore) {
177
+ stakePoolSearch(poolAddress);
178
+ }
179
+ cardanoHttpPost('stake-pool/stats');
180
+
181
+ syncedWallets.add(wallet.address);
182
+ walletSyncTrend.add(Date.now() - startTime);
183
+ walletSyncCount.add(1);
184
+ };
185
+
186
+ function getRandomInt(max) {
187
+ return Math.floor(Math.random() * max);
188
+ }
189
+
190
+ /**
191
+ * Simulate keeping wallet in sync
192
+ * For now, just polling the tip
193
+ */
194
+ const emulateIdleClient = () => cardanoHttpPost('network-info/ledger-tip');
195
+
196
+ export default function ({ wallets, poolAddresses }) {
197
+
198
+ // Pick a new wallet or reuse the same one?
199
+ const walletIdx = SPIN_ON_NEW_WALLETS ? getRandomInt(wallets.length) : __VU;
200
+
201
+ // Get the wallet for the current virtual user
202
+ let wallet = wallets[walletIdx % wallets.length];
203
+ let poolAddress = poolAddresses[walletIdx % poolAddresses.length];
204
+
205
+ if (SPIN_ON_NEW_WALLETS || !syncedWallets.has(wallet.address)) {
206
+ syncWallet({ wallet, poolAddress });
207
+ } else {
208
+ emulateIdleClient();
209
+ sleep(ITERATION_SLEEP);
210
+ }
211
+ }
@@ -0,0 +1,192 @@
1
+ import http from 'k6/http';
2
+ import { check, sleep } from 'k6';
3
+ import { Counter, Trend } from 'k6/metrics';
4
+
5
+ /**
6
+ * Script overall description:
7
+ * One wallet == one VU
8
+ * Configure RUN_MODE to have the test run with empty wallets (onboarding) or wallets with history (restoring)
9
+ * Configure MAX_VU: number of wallets to be synced (e.g. 100 wallets)
10
+ * Setup RAMP_UP_DURATION: ramp-up time to synced these wallets (e.g. 30s)
11
+ * - During this time, wallets start evenly distributed (e.g. ~3 wallets every second)
12
+ * Each wallet performs as many iterations as possible
13
+ * - 1st iteration: restoration steps .
14
+ * - Subsequent iterations will only query the tip
15
+ * ITERATION_SLEEP: Every iteration has a sleep of to simulate Lace tip polling interval
16
+ * During the STEADY_STATE_DURATION:
17
+ * - Synced wallets do tip queries.
18
+ * - Wallets not synced yet will wait for restoration to complete, then will also start tip queries
19
+ * No ramp-down is needed. We can simply stop the test as there is no point in sending fewer and fewer tip queries during ramp-down.
20
+ * wallet_sync: is a custom trend metric measuring the trend of wallet sync calls.
21
+ * wallet_sync_count: is a custom count metric measuring the number of wallets that were successfully synced.
22
+ */
23
+
24
+ const RunMode = {
25
+ Onboard: 'Onboard',
26
+ Restore: 'Restore'
27
+ };
28
+ /** Determines run mode: Restore or Onboard */
29
+ const RUN_MODE = RunMode.Restore;
30
+ const PROVIDER_SERVER_URL = __ENV.PROVIDER_SERVER_URL;
31
+
32
+ /** URL of the JSON file containing the wallets */
33
+ const WALLET_ADDRESSES_URL =
34
+ RUN_MODE === RunMode.Restore
35
+ ? 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/master/packages/e2e/test/dump/addresses/mainnet.json'
36
+ : 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/feat/address-generator/packages/e2e/test/dump/addresses/no-history-mainnet.json';
37
+
38
+ /** URL of the JSON file containing the stake pool addresses */
39
+ const POOL_ADDRESSES_URL =
40
+ 'https://raw.githubusercontent.com/input-output-hk/cardano-js-sdk/master/packages/e2e/test/dump/pool_addresses/mainnet.json';
41
+
42
+ /**
43
+ * Define the maximum number of virtual users to simulate
44
+ * The mainnet.json file contains wallets in chunks of 100, each chunk having the required distribution
45
+ * For this reason, it's a good practice to configure MAX_VUs in multiples of 100 in order to maintain the desired distribution
46
+ */
47
+ const MAX_VU = 1;
48
+
49
+ /** Time span during which all virtual users are started in a linear fashion */
50
+ const RAMP_UP_DURATION = '5s';
51
+ /** Time span during which synced wallets do tip queries */
52
+ const STEADY_STATE_DURATION = '5s';
53
+
54
+ /** Time to sleep between iterations. Simulates polling tip to keep wallet in sync */
55
+ const ITERATION_SLEEP = 5;
56
+
57
+ /** Custom trend statistic to measure trend to sync wallets */
58
+ const walletSyncTrend = new Trend('wallet_sync', true);
59
+ /** Custom count statistic to measure how many wallets were successfully syncd */
60
+ const walletSyncCount = new Counter('wallet_sync_count');
61
+
62
+ /** Grab the wallets json file to be used by the scenario */
63
+ export function setup() {
64
+ console.log(`Running in ${RUN_MODE} mode`);
65
+ let res = http.batch([WALLET_ADDRESSES_URL, POOL_ADDRESSES_URL]);
66
+ check(res, { 'get wallets and pools files': (r) => r.every(({ status }) => status >= 200 && status < 300) });
67
+
68
+ const [{ body: resBodyWallets }, { body: resBodyPools }] = res;
69
+ const wallets = JSON.parse(resBodyWallets);
70
+ const walletCount = wallets ? wallets.length : 0;
71
+ check(walletCount, {
72
+ 'At least one wallet is required to run the test': (count) => count > 0
73
+ });
74
+ console.log(`Wallet addresses configuration file contains ${walletCount} wallets`);
75
+ if (walletCount < MAX_VU) {
76
+ console.warn(
77
+ `Requested VU count: (${MAX_VU}), is greater than the available walled addresses: ${walletCount}. Some addresses will be reused`
78
+ );
79
+ }
80
+
81
+ const poolAddresses = JSON.parse(resBodyPools);
82
+ check(poolAddresses, {
83
+ 'At least one stake pool address is required to run the test': (p) => p && p.length
84
+ });
85
+ return { wallets: wallets.slice(0, MAX_VU), poolAddresses };
86
+ }
87
+
88
+ /** Keeps track of wallets that were successfully syncd to avoid restoring twice */
89
+ const syncedWallets = new Set();
90
+
91
+ export let options = {
92
+ ext: {
93
+ loadimpact: {
94
+ distribution: { 'amazon:us:portland': { loadZone: 'amazon:us:portland', percent: 100 } },
95
+ apm: []
96
+ }
97
+ },
98
+ thresholds: {
99
+ wallet_sync_count: ['count >= ' + MAX_VU], // All wallets should have syncd
100
+ // Use https://k6.io/docs/using-k6/thresholds/ to set more thresholds. E.g.:
101
+ // wallet_sync: ['p(95)<5000'], // 95% of wallets should sync in under 5 seconds
102
+ wallet_sync: [{ threshold: 'p(95) < 30000', delayAbortEval: '5s' }] // We get a nice graph if we enable thresholds. See this stat on a graph
103
+ },
104
+ scenarios: {
105
+ SyncDifferentSizeWallets: {
106
+ executor: 'ramping-vus',
107
+ startVUs: 1,
108
+ stages: [
109
+ { duration: RAMP_UP_DURATION, target: MAX_VU },
110
+ { duration: STEADY_STATE_DURATION, target: MAX_VU }
111
+ ],
112
+ gracefulRampDown: '0s',
113
+ gracefulStop: '0s'
114
+ }
115
+ }
116
+ };
117
+
118
+ /** Util functions for sending the http post requests to cardano-sdk services */
119
+ const cardanoHttpPost = (url, body = {}) => {
120
+ const options = { headers: { 'content-type': 'application/json' } };
121
+ return http.post(`${PROVIDER_SERVER_URL}/${url}`, JSON.stringify(body), options);
122
+ };
123
+ const txsByAddress = (address) => {
124
+ let startAt = 0;
125
+ const pageSize = 25;
126
+ let txCount = 0;
127
+ do {
128
+ const resp = cardanoHttpPost('chain-history/txs/by-addresses', {
129
+ addresses: [address],
130
+ blockRange: { lowerBound: { __type: 'undefined' } },
131
+ pagination: { limit: pageSize, startAt }
132
+ });
133
+
134
+ if (resp.status !== 200) {
135
+ // No point in trying to get the other pages.
136
+ // Should we log this? it will show up as if the restoration was quicker since this wallet did not fetch all the pages
137
+ break;
138
+ }
139
+
140
+ const { pageResults } = JSON.parse(resp.body);
141
+ startAt += pageSize;
142
+ txCount = pageResults.length;
143
+ } while (txCount === pageSize);
144
+ };
145
+ const utxosByAddresses = (address) => cardanoHttpPost('utxo/utxo-by-addresses', { addresses: [address] });
146
+ const rewardsAccBalance = (rewardAccount) =>
147
+ cardanoHttpPost('rewards/account-balance', { rewardAccount: rewardAccount });
148
+ const stakePoolSearch = (poolAddress) =>
149
+ cardanoHttpPost('stake-pool/search', {
150
+ filters: { identifier: { values: [{ id: poolAddress }] } },
151
+ pagination: { limit: 1, startAt: 0 }
152
+ });
153
+
154
+ /** Simulation of requests performed by a wallet while restoring */
155
+ const syncWallet = ({ wallet, poolAddress }) => {
156
+ const startTime = Date.now();
157
+ const { address, stake_address: rewardAccount } = wallet;
158
+ cardanoHttpPost('network-info/era-summaries');
159
+ cardanoHttpPost('network-info/ledger-tip');
160
+ txsByAddress(address);
161
+ utxosByAddresses(address);
162
+ cardanoHttpPost('network-info/era-summaries');
163
+ cardanoHttpPost('network-info/genesis-parameters');
164
+ cardanoHttpPost('network-info/protocol-parameters');
165
+ rewardsAccBalance(rewardAccount);
166
+ cardanoHttpPost('network-info/ledger-tip');
167
+ cardanoHttpPost('network-info/lovelace-supply');
168
+ cardanoHttpPost('network-info/stake');
169
+ if (RUN_MODE === RunMode.Restore) {
170
+ stakePoolSearch(poolAddress);
171
+ }
172
+ cardanoHttpPost('stake-pool/stats');
173
+
174
+ syncedWallets.add(wallet.address);
175
+ walletSyncTrend.add(Date.now() - startTime);
176
+ walletSyncCount.add(1);
177
+ };
178
+
179
+ /**
180
+ * Simulate keeping wallet in sync
181
+ * For now, just polling the tip
182
+ */
183
+ const emulateIdleClient = () => cardanoHttpPost('network-info/ledger-tip');
184
+
185
+ export default function ({ wallets, poolAddresses }) {
186
+ // Get the wallet for the current virtual user
187
+ let wallet = wallets[__VU % wallets.length];
188
+ let poolAddress = poolAddresses[__VU % poolAddresses.length];
189
+
190
+ syncedWallets.has(wallet.address) ? emulateIdleClient() : syncWallet({ wallet, poolAddress });
191
+ sleep(ITERATION_SLEEP);
192
+ }