@aeternity/aepp-sdk 14.1.1 → 15.0.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 (84) hide show
  1. package/dist/aepp-sdk.browser-script.cjs +1 -1
  2. package/dist/aepp-sdk.browser-script.cjs.map +1 -1
  3. package/dist/aepp-sdk.browser.cjs +1 -1
  4. package/dist/aepp-sdk.browser.cjs.map +1 -1
  5. package/dist/aepp-sdk.cjs +3112 -506
  6. package/dist/aepp-sdk.cjs.map +1 -1
  7. package/es/account/Base.d.ts +6 -1
  8. package/es/account/Base.js.map +1 -1
  9. package/es/account/Generalized.d.ts +1 -1
  10. package/es/account/Generalized.js +7 -1
  11. package/es/account/Generalized.js.map +1 -1
  12. package/es/apis/node/models/index.d.ts +405 -79
  13. package/es/apis/node/models/index.js.map +1 -1
  14. package/es/apis/node/models/mappers.d.ts +54 -4
  15. package/es/apis/node/models/mappers.js +1910 -500
  16. package/es/apis/node/models/mappers.js.map +1 -1
  17. package/es/apis/node/node.d.ts +28 -6
  18. package/es/apis/node/node.js +276 -5
  19. package/es/apis/node/node.js.map +1 -1
  20. package/es/channel/Contract.d.ts +5 -0
  21. package/es/channel/Contract.js +8 -2
  22. package/es/channel/Contract.js.map +1 -1
  23. package/es/channel/handlers.js +3 -3
  24. package/es/channel/handlers.js.map +1 -1
  25. package/es/contract/ga.d.ts +5 -2
  26. package/es/contract/ga.js +53 -21
  27. package/es/contract/ga.js.map +1 -1
  28. package/es/index-browser.d.ts +3 -1
  29. package/es/index-browser.js +2 -1
  30. package/es/index-browser.js.map +1 -1
  31. package/es/tx/builder/constants.d.ts +13 -2
  32. package/es/tx/builder/constants.js +12 -1
  33. package/es/tx/builder/constants.js.map +1 -1
  34. package/es/tx/builder/entry/index.js +6 -0
  35. package/es/tx/builder/entry/index.js.map +1 -1
  36. package/es/tx/builder/entry/schema.d.ts +10 -10
  37. package/es/tx/builder/field-types/abi-version.js +6 -4
  38. package/es/tx/builder/field-types/abi-version.js.map +1 -1
  39. package/es/tx/builder/field-types/ct-version.d.ts +19 -0
  40. package/es/tx/builder/field-types/ct-version.js +27 -1
  41. package/es/tx/builder/field-types/ct-version.js.map +1 -1
  42. package/es/tx/builder/field-types/fee.d.ts +9 -7
  43. package/es/tx/builder/field-types/fee.js +75 -66
  44. package/es/tx/builder/field-types/fee.js.map +1 -1
  45. package/es/tx/builder/field-types/gas-limit.d.ts +5 -3
  46. package/es/tx/builder/field-types/gas-limit.js +25 -14
  47. package/es/tx/builder/field-types/gas-limit.js.map +1 -1
  48. package/es/tx/builder/field-types/gas-price.d.ts +6 -4
  49. package/es/tx/builder/field-types/gas-price.js +91 -21
  50. package/es/tx/builder/field-types/gas-price.js.map +1 -1
  51. package/es/tx/builder/field-types/interface.d.ts +20 -0
  52. package/es/tx/builder/field-types/interface.js +17 -1
  53. package/es/tx/builder/field-types/interface.js.map +1 -1
  54. package/es/tx/builder/field-types/name-fee.d.ts +3 -2
  55. package/es/tx/builder/field-types/name-fee.js +6 -0
  56. package/es/tx/builder/field-types/name-fee.js.map +1 -1
  57. package/es/tx/builder/field-types/pointers.d.ts +2 -1
  58. package/es/tx/builder/field-types/pointers.js +6 -2
  59. package/es/tx/builder/field-types/pointers.js.map +1 -1
  60. package/es/tx/builder/field-types/transaction.d.ts +4 -1
  61. package/es/tx/builder/field-types/transaction.js +18 -3
  62. package/es/tx/builder/field-types/transaction.js.map +1 -1
  63. package/es/tx/builder/field-types/with-formatting.d.ts +1 -1
  64. package/es/tx/builder/field-types/with-formatting.js +1 -1
  65. package/es/tx/builder/field-types/with-formatting.js.map +1 -1
  66. package/es/tx/builder/index.d.ts +13 -0
  67. package/es/tx/builder/index.js +92 -5
  68. package/es/tx/builder/index.js.map +1 -1
  69. package/es/tx/builder/protocol-parameters.d.ts +134 -0
  70. package/es/tx/builder/protocol-parameters.js +539 -0
  71. package/es/tx/builder/protocol-parameters.js.map +1 -0
  72. package/es/tx/builder/schema.d.ts +156 -155
  73. package/es/tx/builder/schema.js +5 -1
  74. package/es/tx/builder/schema.js.map +1 -1
  75. package/es/tx/execution-cost.js +5 -5
  76. package/es/tx/execution-cost.js.map +1 -1
  77. package/es/tx/transaction-signer.js +2 -2
  78. package/es/tx/transaction-signer.js.map +1 -1
  79. package/es/tx/validator.js +4 -4
  80. package/es/tx/validator.js.map +1 -1
  81. package/es/utils/wrap-proxy.d.ts +10 -0
  82. package/es/utils/wrap-proxy.js +18 -0
  83. package/es/utils/wrap-proxy.js.map +1 -1
  84. package/package.json +15 -3
@@ -0,0 +1,539 @@
1
+ import { AbiVersion, MAX_AUTH_FUN_GAS, MIN_GAS_PRICE, Tag } from './constants.js';
2
+ import { isKeyOfObject } from '../../utils/other.js';
3
+ import { ArgumentError, InternalError, NodeError } from '../../utils/errors.js';
4
+ import { unwrapProxy } from '../../utils/wrap-proxy.js';
5
+
6
+ /**
7
+ * Consensus parameters and node policy settings the transaction builder needs to produce a
8
+ * transaction the node would accept — see {@link getCachedProtocolParameters}.
9
+ * @category transaction builder
10
+ */
11
+
12
+ /**
13
+ * Transaction option to build a transaction against specific consensus parameters. `buildTxAsync`
14
+ * sets it to the parameters it requests from node, provide it to build a transaction offline for a
15
+ * node that doesn't run the protocol version the current SDK release was made for.
16
+ * @category transaction builder
17
+ */
18
+
19
+ const BASE_GAS = 15000;
20
+ const defaultStateGasPerBlock = {
21
+ part: 32000,
22
+ whole: Math.floor(60 * 24 * 365 / 3)
23
+ };
24
+
25
+ /**
26
+ * Protocol parameters are shared between every transaction built against them, and
27
+ * {@link defaultProtocolParameters} is shared process-wide. `readonly` guards them at compile time
28
+ * only, so freeze them to make sure a transaction can't be mispriced by an unrelated component
29
+ * mutating parameters it doesn't own.
30
+ */
31
+ function freezeParameters(parameters) {
32
+ Object.values(parameters.contractTxBaseGas).forEach(Object.freeze);
33
+ Object.values(parameters.stateGasPerBlock).forEach(Object.freeze);
34
+ Object.freeze(parameters.txBaseGas);
35
+ Object.freeze(parameters.contractTxBaseGas);
36
+ Object.freeze(parameters.stateGasPerBlock);
37
+ return Object.freeze(parameters);
38
+ }
39
+
40
+ /**
41
+ * Protocol parameters of Ceres as they were at the moment of the SDK release. Used to build
42
+ * transactions without a node connection, and on nodes that don't provide the protocol parameters
43
+ * endpoint yet.
44
+ * @category transaction builder
45
+ * @see {@link https://github.com/aeternity/protocol/blob/master/consensus/README.md#gas}
46
+ */
47
+ export const defaultProtocolParameters = freezeParameters({
48
+ minGasPrice: BigInt(MIN_GAS_PRICE),
49
+ minMinerGasPrice: BigInt(MIN_GAS_PRICE),
50
+ gasPerByte: 20,
51
+ blockGasLimit: 6e6,
52
+ maxAuthFunGas: MAX_AUTH_FUN_GAS,
53
+ txBaseGas: {
54
+ ...Object.fromEntries(Object.values(Tag).filter(tag => typeof tag === 'number')
55
+ // node reports the base gas of contract-executing types in `contract_tx_base_gas`,
56
+ // `SignedTx` is not a chargeable transaction type
57
+ .filter(tag => ![Tag.SignedTx, Tag.ContractCreateTx, Tag.ContractCallTx, Tag.GaAttachTx, Tag.GaMetaTx].includes(tag)).map(tag => [tag, BASE_GAS])),
58
+ [Tag.ChannelForceProgressTx]: 30 * BASE_GAS,
59
+ [Tag.ChannelOffChainTx]: 0,
60
+ [Tag.PayingForTx]: BASE_GAS / 5
61
+ },
62
+ contractTxBaseGas: {
63
+ [Tag.ContractCreateTx]: {
64
+ [AbiVersion.Sophia]: 5 * BASE_GAS,
65
+ [AbiVersion.Fate]: 5 * BASE_GAS
66
+ },
67
+ [Tag.ContractCallTx]: {
68
+ [AbiVersion.Sophia]: 30 * BASE_GAS,
69
+ [AbiVersion.Fate]: 12 * BASE_GAS
70
+ },
71
+ [Tag.GaAttachTx]: {
72
+ [AbiVersion.Sophia]: 5 * BASE_GAS,
73
+ [AbiVersion.Fate]: 5 * BASE_GAS
74
+ },
75
+ [Tag.GaMetaTx]: {
76
+ [AbiVersion.Sophia]: 5 * BASE_GAS,
77
+ [AbiVersion.Fate]: 5 * BASE_GAS
78
+ }
79
+ },
80
+ stateGasPerBlock: Object.fromEntries([Tag.OracleRegisterTx, Tag.OracleExtendTx, Tag.OracleQueryTx, Tag.OracleRespondTx].map(tag => [tag, {
81
+ ...defaultStateGasPerBlock
82
+ }]))
83
+ });
84
+
85
+ // node names transaction types after `aetx:type_to_swagger_name/1`, it matches `Tag` except of these
86
+ const txTypeToTag = {
87
+ GAAttachTx: Tag.GaAttachTx,
88
+ GAMetaTx: Tag.GaMetaTx
89
+ };
90
+ function getTag(txType) {
91
+ // node controls these keys, `isKeyOfObject` also accepts anything inherited from
92
+ // `Object.prototype` (`constructor`, `toString`) — a name of a transaction type is neither
93
+ const isOwnKeyOf = object => Object.prototype.hasOwnProperty.call(object, txType);
94
+ if (isOwnKeyOf(txTypeToTag) && isKeyOfObject(txType, txTypeToTag)) return txTypeToTag[txType];
95
+ if (!isOwnKeyOf(Tag) || !isKeyOfObject(txType, Tag)) return undefined;
96
+ // `Tag` is a numeric enum, so it is also indexable by a member value (`Tag['12'] === 'SpendTx'`)
97
+ const tag = Tag[txType];
98
+ return typeof tag === 'number' ? tag : undefined;
99
+ }
100
+
101
+ // Node decides these values and they multiply into the fee the user pays, so a node that is
102
+ // compromised, misconfigured, or impersonated could make the SDK build a transaction with an
103
+ // extreme fee. A hard fork may legitimately raise them, but not by orders of magnitude — refuse to
104
+ // build against a value far above the one the SDK was released with rather than silently overpay.
105
+ const maxRaiseFactor = 1000;
106
+ function excessiveError(name, value, limit) {
107
+ return new NodeError(`Node reports ${name} as ${String(value)}, which is outside of the range the SDK considers` + ` plausible (0..${limit}). Provide \`protocolParameters\` in options to build a transaction` + ' against these parameters anyway.');
108
+ }
109
+
110
+ /**
111
+ * A value that doesn't match the schema node declares for it — the api client passes `null`,
112
+ * booleans, strings, and fractions through unchanged. Not a {@link NodeError}: the parameters are
113
+ * unreadable rather than implausible, so {@link getCachedProtocolParameters} falls back to
114
+ * {@link defaultProtocolParameters}, as for a node without the endpoint (which a node can force
115
+ * with a 404 anyway).
116
+ */
117
+ function malformedError(name, value) {
118
+ return new InternalError(`Node reports ${name} as ${String(value)}, which doesn't match the schema it declares for it`);
119
+ }
120
+
121
+ /**
122
+ * Checks a value node reports against the one the SDK was released with, returns the factor it is
123
+ * raised by.
124
+ */
125
+ function checkNotExcessive(name, value, sdkValue) {
126
+ // a parameter the SDK release has at 0 can only stay at 0, it raises the fee by nothing
127
+ const raise = number => sdkValue === 0 ? 1 : number / sdkValue;
128
+ const limit = sdkValue * maxRaiseFactor;
129
+ // a bigint matches the schema whatever its magnitude, so one out of range is an implausible
130
+ // value rather than an unreadable one. Compared as a bigint: a value far enough above the limit
131
+ // can't be counted in a number, and that is what makes it implausible in the first place
132
+ if (typeof value === 'bigint') {
133
+ if (value < 0n || value > BigInt(limit)) throw excessiveError(name, value, limit);
134
+ return raise(Number(value));
135
+ }
136
+ if (!Number.isSafeInteger(value)) throw malformedError(name, value);
137
+ if (value < 0 || value > limit) throw excessiveError(name, value, limit);
138
+ return raise(value);
139
+ }
140
+
141
+ /**
142
+ * `Math.max(...array)` passes every element as an argument and overflows the stack on an array
143
+ * node can make arbitrarily long — it reports one entry per transaction type and abi version.
144
+ */
145
+ export function maxOf(numbers) {
146
+ return numbers.reduce((max, number) => number > max ? number : max, -Infinity);
147
+ }
148
+
149
+ // TODO: express the bound in aettos instead — a defaulted value may not commit the user to more
150
+ // than a fixed coin amount — rather than as a ratio against the parameters of the SDK release.
151
+ // That states the actual requirement directly and holds on any network, but it defaults a higher
152
+ // gas limit on a network that raised the block gas limit, so it is a behavior change, not a
153
+ // refactor [behavior change on networks running other parameters]
154
+ /**
155
+ * How much node's parameters raise each product a transaction costs, against the SDK release.
156
+ * Kept out of {@link ProtocolParameters} because they are not consensus parameters but a property
157
+ * of where the parameters came from — the ones a caller provides in options don't go through here.
158
+ *
159
+ * One per gas amount the SDK picks itself, and not one per parameter: each is charged to the value
160
+ * it bounds, so that a raise of one doesn't shrink a value it has nothing to do with.
161
+ */
162
+
163
+ const parameterRaises = new WeakMap();
164
+
165
+ // parameters this process built itself are checked where they are built — the ones of the SDK
166
+ // release by construction, the ones node reports by `checkParametersNotExcessive`. This remembers
167
+ // the caller-provided ones already walked, a build reads them once per field and per rebuild pass
168
+ const usableParameters = new WeakSet();
169
+ function checkUsableGas(name, value) {
170
+ if (!Number.isSafeInteger(value) || value < 0) {
171
+ throw new ArgumentError(`protocolParameters.${name}`, 'a non-negative safe integer', value);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Checks that parameters a caller provided in options can price a transaction at all — only values
177
+ * that would produce `NaN`, `Infinity`, or a division by zero, not their magnitude: options are the
178
+ * way out of the bounds {@link convertProtocolParameters} applies, bounding them here would leave
179
+ * no way to build against a network the SDK release doesn't know. They are as trusted as `fee` and
180
+ * `gasLimit`: must not come from untrusted input, this only turns an unusable value into an error
181
+ * naming the field. See `serializeAsIsParam`.
182
+ * @param parameters - Parameters provided in options
183
+ */
184
+ export function checkParametersUsable(parameters) {
185
+ // built by this process, or already walked
186
+ if (parameterRaises.has(parameters) || usableParameters.has(parameters)) return;
187
+ if (typeof parameters.minGasPrice !== 'bigint' || parameters.minGasPrice < 1n) {
188
+ throw new ArgumentError('protocolParameters.minGasPrice', 'a positive bigint (a transaction can’t be priced against 0)', parameters.minGasPrice);
189
+ }
190
+ if (typeof parameters.minMinerGasPrice !== 'bigint' || parameters.minMinerGasPrice < 0n) {
191
+ throw new ArgumentError('protocolParameters.minMinerGasPrice', 'a non-negative bigint', parameters.minMinerGasPrice);
192
+ }
193
+ checkUsableGas('gasPerByte', parameters.gasPerByte);
194
+ checkUsableGas('blockGasLimit', parameters.blockGasLimit);
195
+ checkUsableGas('maxAuthFunGas', parameters.maxAuthFunGas);
196
+ Object.entries(parameters.txBaseGas).forEach(([tag, gas]) => {
197
+ var _Tag$tag;
198
+ return checkUsableGas(`txBaseGas.${(_Tag$tag = Tag[+tag]) !== null && _Tag$tag !== void 0 ? _Tag$tag : tag}`, gas);
199
+ });
200
+ Object.entries(parameters.contractTxBaseGas).forEach(([tag, byAbiVersion]) => Object.entries(byAbiVersion !== null && byAbiVersion !== void 0 ? byAbiVersion : {}).forEach(([abiVersion, gas]) => {
201
+ var _Tag$tag2;
202
+ return checkUsableGas(`contractTxBaseGas.${(_Tag$tag2 = Tag[+tag]) !== null && _Tag$tag2 !== void 0 ? _Tag$tag2 : tag}.${abiVersion}`, gas);
203
+ }));
204
+ Object.entries(parameters.stateGasPerBlock).forEach(([tag, fraction]) => {
205
+ var _Tag$tag3, _fraction$whole;
206
+ const name = `stateGasPerBlock.${(_Tag$tag3 = Tag[+tag]) !== null && _Tag$tag3 !== void 0 ? _Tag$tag3 : tag}`;
207
+ checkUsableGas(`${name}.part`, fraction?.part);
208
+ // a `whole` of 0 makes the state gas — and so the fee — infinite
209
+ if (!Number.isSafeInteger(fraction?.whole) || ((_fraction$whole = fraction?.whole) !== null && _fraction$whole !== void 0 ? _fraction$whole : 0) < 1) {
210
+ throw new ArgumentError(`${name}.whole`, 'a positive safe integer', fraction?.whole);
211
+ }
212
+ });
213
+ usableParameters.add(parameters);
214
+ }
215
+ function checkRatioNotExcessive(name, {
216
+ part,
217
+ whole
218
+ }, sdkRatio) {
219
+ if (!Number.isSafeInteger(part) || !Number.isSafeInteger(whole)) {
220
+ throw malformedError(name, `${String(part)}/${String(whole)}`);
221
+ }
222
+ const limit = sdkRatio * maxRaiseFactor;
223
+ // a `whole` of 0 would make the fee infinite, `0 / 0` is NaN — both fail the check below
224
+ const ratio = part >= 0 && whole > 0 ? part / whole : NaN;
225
+ if (!(ratio >= 0) || ratio > limit) throw excessiveError(name, `${part}/${whole}`, limit);
226
+ return ratio / sdkRatio;
227
+ }
228
+
229
+ /**
230
+ * The base gas the SDK release charges for a transaction type, whichever of the two tables it is
231
+ * in. `getTxBaseGas` reads `contractTxBaseGas` before `txBaseGas`, so an entry node adds to
232
+ * `contractTxBaseGas` overrides the `txBaseGas` of that very type — checking it against a
233
+ * type-independent constant would let node raise, say, a `PayingForTx` (3000) to 15000000 and
234
+ * still be counted as a raise of 1000.
235
+ */
236
+ function defaultBaseGasOf(tag) {
237
+ var _d$contractTxBaseGas$;
238
+ const d = defaultProtocolParameters;
239
+ const values = [d.txBaseGas[tag], ...Object.values((_d$contractTxBaseGas$ = d.contractTxBaseGas[tag]) !== null && _d$contractTxBaseGas$ !== void 0 ? _d$contractTxBaseGas$ : {})].filter(value => value != null);
240
+ const max = values.length !== 0 ? maxOf(values) : BASE_GAS;
241
+ // a type charged 0 (`ChannelOffChainTx`) would get a budget of 0, and node reporting any gas for
242
+ // it would refuse the whole response. Give it an ordinary type's budget instead — a node charging
243
+ // more than `maxRaiseFactor` base gas units for it is still refused
244
+ return max !== 0 ? max : BASE_GAS;
245
+ }
246
+ function checkRaiseNotExcessive(what, raise) {
247
+ if (raise <= maxRaiseFactor) return;
248
+ throw new NodeError(`Node reports parameters raising ${what} ${Math.round(raise)} times above the one of the SDK` + ` release, more than the ${maxRaiseFactor} times the SDK considers plausible. Provide` + ' `protocolParameters` in options to build a transaction against these parameters anyway.');
249
+ }
250
+
251
+ /**
252
+ * Refuses the parameters if node reports values far above the ones of the SDK release, and returns
253
+ * the factors the two products a transaction costs may be raised by — see {@link ParameterRaises}.
254
+ */
255
+ function checkParametersNotExcessive(parameters) {
256
+ const d = defaultProtocolParameters;
257
+ const defaultMinGasPrice = Number(d.minGasPrice);
258
+ // no network runs a consensus minimum of 0 (node reports 1e6 since Minerva), and with a miner
259
+ // minimum of 0 as well it makes every fee 0 and the `minFee / getFloorGasPrice` of the
260
+ // demand-based fee a division by zero
261
+ if (parameters.minGasPrice < 1n) {
262
+ throw new NodeError(`Node reports the minimum gas price as ${parameters.minGasPrice}, a transaction can't be` + ' priced against it. Provide `protocolParameters` in options to build a transaction' + ' against these parameters anyway.');
263
+ }
264
+ // both raise the gas price the fee is counted from: the consensus minimum directly, the miner
265
+ // minimum as the floor `getCachedIncreasedGasPrice` lifts the gas price to. Bounding the miner
266
+ // minimum on its own — as this used to — leaves node the product of the two to raise the fee by.
267
+ const gasPriceRaise = maxOf([checkNotExcessive('the minimum gas price', parameters.minGasPrice, defaultMinGasPrice), checkNotExcessive('the miner minimum gas price', parameters.minMinerGasPrice, defaultMinGasPrice)]);
268
+
269
+ // every one of these is a gas amount the fee is counted from, the biggest raise among them is
270
+ // the most a transaction's gas can grow by
271
+ const gasRaises = [checkNotExcessive('the gas per byte', parameters.gasPerByte, d.gasPerByte)];
272
+ Object.entries(parameters.txBaseGas).forEach(([tag, gas]) => {
273
+ // against the default of this very type and not against the biggest default of all types: a
274
+ // `ChannelForceProgressTx` costs 30 times a `SpendTx`, that is not a budget for a `SpendTx`
275
+ gasRaises.push(checkNotExcessive(`the base gas of ${Tag[+tag]}`, gas, defaultBaseGasOf(+tag)));
276
+ });
277
+ Object.entries(parameters.contractTxBaseGas).forEach(([tag, byAbiVersion]) => {
278
+ Object.entries(byAbiVersion).forEach(([abiVersion, gas]) => {
279
+ const name = `the base gas of ${Tag[+tag]} at abi version ${abiVersion}`;
280
+ // an abi version the SDK release prices is bounded by its own value — the budget of the
281
+ // priciest one is not a budget for the others. An abi version it doesn't price is charged
282
+ // the maximum of the ones it does, see `getTxBaseGas`
283
+ const sdkValue = d.contractTxBaseGas[+tag]?.[+abiVersion];
284
+ gasRaises.push(checkNotExcessive(name, gas, sdkValue !== null && sdkValue !== void 0 ? sdkValue : defaultBaseGasOf(+tag)));
285
+ });
286
+ });
287
+ const defaultStateGasRatio = defaultStateGasPerBlock.part / defaultStateGasPerBlock.whole;
288
+ Object.entries(parameters.stateGasPerBlock).forEach(([tag, fraction]) => {
289
+ const name = `the state gas per block of ${Tag[+tag]}`;
290
+ gasRaises.push(checkRatioNotExcessive(name, fraction, defaultStateGasRatio));
291
+ });
292
+
293
+ // the minimum fee is `gasPrice * (baseGas + size * gasPerByte + stateGas)`, so each product is
294
+ // bounded rather than each parameter — otherwise node could combine them into a fee
295
+ // `maxRaiseFactor` squared above the one of the SDK release
296
+ const feeGasRaise = maxOf(gasRaises);
297
+ checkRaiseNotExcessive('the minimum transaction fee', gasPriceRaise * feeGasRaise);
298
+
299
+ // the two bound different transaction types, but both are a gas limit multiplied by the same
300
+ // gas price — the bigger of them is what the worst case costs
301
+ const blockGasLimitRaise = checkNotExcessive('the block gas limit', parameters.blockGasLimit, d.blockGasLimit);
302
+ const maxAuthFunGasRaise = checkNotExcessive('the max auth fun gas', parameters.maxAuthFunGas, d.maxAuthFunGas);
303
+ const gasLimitRaise = maxOf([blockGasLimitRaise, maxAuthFunGasRaise]);
304
+ checkRaiseNotExcessive('the cost of a contract transaction', gasPriceRaise * gasLimitRaise);
305
+
306
+ // the gas above is multiplied by a gas price that doesn't come from these parameters, and that
307
+ // is bounded on its own — so the raises are reported to lower those bounds by the same factors,
308
+ // see `getGasPriceDivisor` and `getGasLimitDivisor`
309
+ return {
310
+ fee: maxOf([1, feeGasRaise]),
311
+ blockGasLimit: maxOf([1, blockGasLimitRaise]),
312
+ maxAuthFunGas: maxOf([1, maxAuthFunGasRaise])
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Factor the gas price ceiling of `gas-price.ts` is lowered by, so that the worst case fee doesn't
318
+ * become the product of that ceiling and the fee gas bound of {@link convertProtocolParameters}.
319
+ * The gas limit raise is charged to the gas limit default instead ({@link getGasLimitDivisor}).
320
+ * It is 1 unless the parameters came from node.
321
+ *
322
+ * The biggest raise among all transaction types, not the one of the type being built — so a node
323
+ * repricing one type lowers the ceiling for every type. The ceiling is 100000 times the minimum
324
+ * gas price, far above the demand-based price this bounds in practice.
325
+ * @param parameters - Parameters a transaction is built against
326
+ */
327
+ export function getGasPriceDivisor(parameters) {
328
+ var _parameterRaises$get$;
329
+ return (_parameterRaises$get$ = parameterRaises.get(parameters)?.fee) !== null && _parameterRaises$get$ !== void 0 ? _parameterRaises$get$ : 1;
330
+ }
331
+
332
+ /**
333
+ * The cost of a contract transaction is `gasPrice * gasLimit`, and the gas limit the SDK defaults
334
+ * to comes from `blockGasLimit` — from `maxAuthFunGas` on a `GaMetaTx`. This is the factor node
335
+ * raises the one `tag` reads by, so that lowering the default by it keeps the product within the
336
+ * one of the SDK release. It is 1 unless the parameters came from node.
337
+ *
338
+ * Only the limit `tag` reads: charging the other one would shrink a gas limit because node raised
339
+ * something that transaction never reads. The fee gas raise is charged to the gas price ceiling
340
+ * ({@link getGasPriceDivisor}) instead.
341
+ * @param parameters - Parameters a transaction is built against
342
+ * @param tag - Type of the transaction the gas limit is defaulted for
343
+ */
344
+ export function getGasLimitDivisor(parameters, tag) {
345
+ const raises = parameterRaises.get(parameters);
346
+ if (raises == null) return 1;
347
+ return tag === Tag.GaMetaTx ? raises.maxAuthFunGas : raises.blockGasLimit;
348
+ }
349
+
350
+ /**
351
+ * Gas price the minimum fee is counted at and the `gasPrice` of a contract transaction defaults
352
+ * to: the miner minimum when it is above the consensus one. Node reads the price a transaction
353
+ * pays as the lower of its `gasPrice` and its fee over its fee gas, so pricing either below the
354
+ * miner minimum gets it refused with `too_low_gas_price_for_miner`.
355
+ * @category transaction builder
356
+ * @param parameters - Parameters a transaction is built against
357
+ */
358
+ export function getFloorGasPrice(parameters) {
359
+ const {
360
+ minGasPrice,
361
+ minMinerGasPrice
362
+ } = parameters;
363
+ return minMinerGasPrice > minGasPrice ? minMinerGasPrice : minGasPrice;
364
+ }
365
+ function mapByTxType(entries, map) {
366
+ return Object.fromEntries(entries.map(([txType, value]) => [getTag(txType), map(value)])
367
+ // a transaction type the SDK doesn't implement, or won't ever build (`ChannelClientReconnectTx`)
368
+ .filter(entry => entry[0] != null));
369
+ }
370
+
371
+ /**
372
+ * Converts the node responses to the shape the transaction builder uses, rejecting values far
373
+ * above the ones the SDK was released with. Not a part of the public api, it is exported for
374
+ * tests — use {@link getCachedProtocolParameters}.
375
+ * @param response - Response of the `/v3/protocol-parameters` endpoint
376
+ * @param nodeSettings - Response of the `/v3/node-settings` endpoint
377
+ */
378
+ export function convertProtocolParameters(response, nodeSettings) {
379
+ const {
380
+ currentProtocolVersion,
381
+ protocols
382
+ } = response;
383
+ const protocol = protocols.find(({
384
+ version
385
+ }) => version === currentProtocolVersion);
386
+ if (protocol == null) {
387
+ throw new InternalError(`Node doesn't provide parameters of the current protocol ${currentProtocolVersion}`);
388
+ }
389
+
390
+ // starts from the values of the SDK release so that a type or an abi version node doesn't report
391
+ // keeps building against them, as the SDK did before this endpoint existed, instead of failing
392
+ const contractTxBaseGas = Object.fromEntries(Object.entries(defaultProtocolParameters.contractTxBaseGas).map(([tag, byAbiVersion]) => [tag, {
393
+ ...byAbiVersion
394
+ }]));
395
+ protocol.contractTxBaseGas.forEach(({
396
+ txType,
397
+ abiVersion,
398
+ txBaseGas
399
+ }) => {
400
+ var _contractTxBaseGas$ta;
401
+ const tag = getTag(txType);
402
+ // node controls `abiVersion` and the api client passes a value that doesn't match the schema
403
+ // node declares for it through unchanged. A `__proto__` key would be written into the
404
+ // prototype: invisible to the `Object.values` of the check below, and still returned by the
405
+ // `byAbiVersion[abiVersion]` lookup the fee is calculated from
406
+ if (tag == null || !Number.isInteger(abiVersion)) return;
407
+ const byAbiVersion = (_contractTxBaseGas$ta = contractTxBaseGas[tag]) !== null && _contractTxBaseGas$ta !== void 0 ? _contractTxBaseGas$ta : contractTxBaseGas[tag] = {};
408
+ byAbiVersion[abiVersion] = txBaseGas;
409
+ });
410
+ const reportedTxBaseGas = mapByTxType(Object.entries(protocol.txBaseGas), gas => gas);
411
+ const parameters = freezeParameters({
412
+ minGasPrice: protocol.minimumGasPrice,
413
+ minMinerGasPrice: nodeSettings.minMinerGasPrice,
414
+ gasPerByte: protocol.gasPerByte,
415
+ blockGasLimit: nodeSettings.blockGasLimit,
416
+ maxAuthFunGas: nodeSettings.maxAuthFunGas,
417
+ txBaseGas: {
418
+ // see the note on `contractTxBaseGas` above
419
+ ...defaultProtocolParameters.txBaseGas,
420
+ ...reportedTxBaseGas
421
+ },
422
+ contractTxBaseGas,
423
+ stateGasPerBlock: {
424
+ // see the note on `contractTxBaseGas` above
425
+ ...defaultProtocolParameters.stateGasPerBlock,
426
+ ...mapByTxType(Object.entries(protocol.stateGasPerBlock), fraction => ({
427
+ part: fraction.part,
428
+ whole: fraction.whole
429
+ }))
430
+ }
431
+ });
432
+ parameterRaises.set(parameters, checkParametersNotExcessive(parameters));
433
+ return parameters;
434
+ }
435
+ const cache = new WeakMap();
436
+ // protocol parameters change on a hard fork, or when the node operator edits the node config,
437
+ // neither happens often enough to justify a request per transaction built
438
+ const cacheTtl = 10 * 60 * 1000;
439
+ // a node that can't be reached, or that reports parameters the SDK refuses to build against, may
440
+ // be reachable or reconfigured in a moment — don't keep the whole `cacheTtl` worth of transactions
441
+ // building against the parameters of the SDK release, or failing, because of one bad response.
442
+ // Still cached rather than retried per transaction: both outcomes cost a round trip to reproduce.
443
+ const transientCacheTtl = 30 * 1000;
444
+
445
+ /**
446
+ * Requests protocol parameters from node, the result is cached per node instance.
447
+ *
448
+ * Falls back to {@link defaultProtocolParameters} for a node that doesn't provide the endpoints,
449
+ * can't be reached, or answers something this SDK release can't read. Rejects only when node
450
+ * reports parameters that would price a transaction far above the SDK release — see
451
+ * {@link ProtocolParametersOption} for the way out of that.
452
+ *
453
+ * The cache is keyed by the `Node` object, so code that constructs a `Node` per request (a
454
+ * serverless handler, an edge worker) requests the parameters again for each one. Keep one `Node`
455
+ * instance per endpoint to get the benefit of the cache.
456
+ * @category transaction builder
457
+ * @param nodeOrProxy - Node to request the parameters from
458
+ */
459
+ export async function getCachedProtocolParameters(nodeOrProxy) {
460
+ const node = unwrapProxy(nodeOrProxy);
461
+ const entry = cache.get(node);
462
+ if (entry != null && entry.expiresAt > Date.now()) return entry.parameters;
463
+
464
+ // node is expected to respond with 404 until it gets the endpoints, and a node whose operator
465
+ // disabled the `node_info`/`node_settings` endpoint groups with 403 — neither changes within
466
+ // `cacheTtl`, while anything else is worth asking again about sooner
467
+ let ttl = cacheTtl;
468
+ // the request and the conversion both run in here so that only a deliberate refusal — a
469
+ // `NodeError` on implausible parameters — comes out as a rejection; anything unreadable falls
470
+ // back to the parameters of the SDK release instead of making every transaction unbuildable
471
+ const parameters = (async () => {
472
+ const fallBack = reason => {
473
+ ttl = transientCacheTtl;
474
+ console.warn("Can't get protocol parameters from node, using the ones of the SDK release instead." + ' A transaction may be rejected if this node runs other parameters.' + ` Reason: ${reason instanceof Error ? reason.message : String(reason)}`);
475
+ return defaultProtocolParameters;
476
+ };
477
+
478
+ // a node object without the methods (a test double, another SDK copy's `Node`) won't grow
479
+ // them within `transientCacheTtl` — treat like the 404 of an old node: full-interval cache,
480
+ // and no warning per 30 seconds for the life of the process
481
+ if (typeof node.getProtocolParameters !== 'function' || typeof node.getNodeSettings !== 'function') {
482
+ return defaultProtocolParameters;
483
+ }
484
+
485
+ // retrying the 404 on every transaction built would only add latency
486
+ const noRetry = {
487
+ requestOptions: {
488
+ customHeaders: {
489
+ '__no-retry': 'true'
490
+ }
491
+ }
492
+ };
493
+ let response;
494
+ let nodeSettings;
495
+ try {
496
+ // consensus parameters and this node's own policy are two endpoints, and a transaction is
497
+ // priced by both — requested together so that a build waits for one round trip and not two,
498
+ // and taken as a set: parameters half from node and half from the SDK release would be
499
+ // neither ones node accepts nor ones the SDK was tested against
500
+ [response, nodeSettings] = await Promise.all([node.getProtocolParameters(noRetry), node.getNodeSettings(noRetry)]);
501
+ } catch (error) {
502
+ // the 404/403 of the note above — neither is worth a warning, or an earlier retry.
503
+ // Read off the error rather than through `instanceof RestError`: that would import
504
+ // `@azure/core-rest-pipeline` as a value into the transaction builder, which is otherwise
505
+ // free of it, and pull the http stack into the bundle of an application that only packs
506
+ // transactions. The api client is the only thing that rejects here, and it sets this
507
+ const {
508
+ statusCode
509
+ } = error;
510
+ if (statusCode === 404 || statusCode === 403) return defaultProtocolParameters;
511
+ // node is syncing (503), a proxy in between is down, the request timed out
512
+ return fallBack(error);
513
+ }
514
+ try {
515
+ return convertProtocolParameters(response, nodeSettings);
516
+ } catch (error) {
517
+ // node reports parameters that would price a transaction far above the SDK release — falling
518
+ // back would build a transaction this node rejects, so the caller is told to decide instead
519
+ if (error instanceof NodeError) throw error;
520
+ return fallBack(error);
521
+ }
522
+ })();
523
+
524
+ // until it settles the entry keeps concurrent builds on this one request
525
+ const cacheEntry = {
526
+ expiresAt: Date.now() + cacheTtl,
527
+ parameters
528
+ };
529
+ parameters.then(() => {
530
+ cacheEntry.expiresAt = Date.now() + ttl;
531
+ },
532
+ // see `transientCacheTtl`
533
+ () => {
534
+ cacheEntry.expiresAt = Date.now() + transientCacheTtl;
535
+ });
536
+ cache.set(node, cacheEntry);
537
+ return parameters;
538
+ }
539
+ //# sourceMappingURL=protocol-parameters.js.map