@leofcoin/chain 1.10.8 → 1.10.9

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.
package/exports/chain.js CHANGED
@@ -3,7 +3,7 @@ import { Codec } from '@leofcoin/codec-format-interface';
3
3
  import { jsonStringifyBigInt, jsonParseBigInt, formatBytes, parseUnits, formatUnits } from '@leofcoin/utils';
4
4
  import { TransactionMessage, BlockMessage, ContractMessage, LastBlockMessage, PrevoteMessage, PrecommitMessage, ProposalMessage, BWMessage, StateMessage } from '@leofcoin/messages';
5
5
  import addresses, { contractFactory } from '@leofcoin/addresses';
6
- import { createTransactionHash, calculateFee, createContractMessage, signTransaction, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage } from '@leofcoin/lib';
6
+ import { createTransactionHash, calculateFee, createContractMessage, signTransaction, distributeTransactionFee, supportsTransactionFees, aggregateValidatorFees, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage } from '@leofcoin/lib';
7
7
  import MultiWallet from '@leofcoin/multi-wallet';
8
8
  import { fromBase58 } from '@vandeurenglenn/typed-array-utils';
9
9
  import semver from 'semver';
@@ -896,7 +896,7 @@ class Machine {
896
896
  * @param {Array} parameters
897
897
  * @returns Promise<message>
898
898
  */
899
- async execute(contract, method, parameters) {
899
+ async execute(contract, method, parameters, sender) {
900
900
  try {
901
901
  if (contract === contractFactory && method === "registerContract") {
902
902
  if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
@@ -937,11 +937,15 @@ ${error.message}`);
937
937
  to: contract,
938
938
  contract,
939
939
  method,
940
- params: parameters
940
+ params: parameters,
941
+ sender
941
942
  }
942
943
  });
943
944
  });
944
945
  }
946
+ collectFee(from, payments, burned) {
947
+ return this.#askWorker("collectFee", { from, payments, burned });
948
+ }
945
949
  get(contract, method, parameters) {
946
950
  return new Promise((resolve, reject) => {
947
951
  const id = randombytes(20).toString();
@@ -1785,9 +1789,20 @@ class State extends Contract {
1785
1789
  #loadBlockTransactions;
1786
1790
  #getLastTransactions;
1787
1791
  // todo throw error
1788
- async #_executeTransaction(transaction) {
1792
+ async #_executeTransaction(transaction, validators, feesEnabled) {
1789
1793
  try {
1790
- await this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params);
1794
+ const hash = await transaction.hash();
1795
+ if (feesEnabled) {
1796
+ const fee = BigInt(await calculateFee(transaction.decoded));
1797
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators);
1798
+ await this.#machine.collectFee(transaction.decoded.from, payments, burned);
1799
+ }
1800
+ await this.#machine.execute(
1801
+ transaction.decoded.to,
1802
+ transaction.decoded.method,
1803
+ transaction.decoded.params,
1804
+ transaction.decoded.from
1805
+ );
1791
1806
  } catch (error) {
1792
1807
  console.log(error);
1793
1808
  await globalThis.transactionPoolStore.delete(await transaction.hash());
@@ -1816,23 +1831,21 @@ class State extends Contract {
1816
1831
  try {
1817
1832
  debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
1818
1833
  let transactions = await this.#loadBlockTransactions(block.transactions || []);
1834
+ const validators = block.validators.map(({ address }) => address);
1819
1835
  debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
1820
- let priority = [];
1821
1836
  for (const transaction of transactions) {
1822
1837
  const hash = await transaction.hash();
1823
- if (transaction.decoded.priority) priority.push(transaction);
1824
1838
  if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
1825
1839
  }
1826
- if (priority.length > 0) {
1827
- debug$1(`executing ${priority.length} priority transactions for block ${block.index}`);
1828
- priority = priority.sort((a, b) => a.nonce - b.nonce);
1829
- for (const transaction of priority) {
1830
- await this.#_executeTransaction(transaction);
1831
- }
1832
- }
1833
- transactions = transactions.filter((transaction) => !transaction.decoded.priority);
1840
+ transactions = transactions.sort((a, b) => {
1841
+ if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
1842
+ const left = BigInt(a.decoded.nonce);
1843
+ const right = BigInt(b.decoded.nonce);
1844
+ return left < right ? -1 : left > right ? 1 : 0;
1845
+ });
1834
1846
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
1835
- await Promise.all(transactions.map((transaction) => this.#_executeTransaction(transaction)));
1847
+ const feesEnabled = supportsTransactionFees(block.protocolVersion);
1848
+ for (const transaction of transactions) await this.#_executeTransaction(transaction, validators, feesEnabled);
1836
1849
  this.#blocks[block.index].loaded = true;
1837
1850
  debug$1(`executed transactions for block ${block.index}`);
1838
1851
  if (Number(block.index) === 0) this.#loaded = true;
@@ -2286,7 +2299,7 @@ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validator
2286
2299
  );
2287
2300
  }
2288
2301
  };
2289
- const validateBlockEconomics = (block, calculatedFees) => {
2302
+ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map()) => {
2290
2303
  if (block.validators.length === 0) {
2291
2304
  throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
2292
2305
  }
@@ -2297,8 +2310,9 @@ const validateBlockEconomics = (block, calculatedFees) => {
2297
2310
  throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
2298
2311
  }
2299
2312
  const validatorCount = BigInt(block.validators.length);
2300
- const expectedReward = calculatedFees / validatorCount + BLOCK_REWARD / validatorCount;
2313
+ const baseReward = BLOCK_REWARD / validatorCount;
2301
2314
  for (const validator of block.validators) {
2315
+ const expectedReward = baseReward + (validatorFees.get(validator.address) || 0n);
2302
2316
  if (validator.reward !== expectedReward) {
2303
2317
  throw new Error(
2304
2318
  `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
@@ -2766,10 +2780,42 @@ class Chain extends VersionControl {
2766
2780
  return transaction;
2767
2781
  })
2768
2782
  );
2769
- const calculatedFees = (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n);
2770
- validateBlockEconomics(blockMessage.decoded, calculatedFees);
2783
+ const feesEnabled = supportsTransactionFees(blockMessage.decoded.protocolVersion);
2784
+ if (feesEnabled) await this.#assertFeeBurnSupported();
2785
+ const calculatedFees = feesEnabled ? (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n) : 0n;
2786
+ const feeEntries = feesEnabled ? await Promise.all(
2787
+ transactions.map(async (transaction) => ({
2788
+ fee: BigInt(await calculateFee(transaction.decoded)),
2789
+ transactionHash: await transaction.hash()
2790
+ }))
2791
+ ) : [];
2792
+ const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
2793
+ const validatorFees = feesEnabled ? aggregateValidatorFees(feeEntries, validatorAddresses) : new Map(validatorAddresses.map((address) => [address, 0n]));
2794
+ validateBlockEconomics(blockMessage.decoded, calculatedFees, validatorFees);
2795
+ if (feesEnabled) {
2796
+ const feesBySender = /* @__PURE__ */ new Map();
2797
+ for (let index = 0; index < transactions.length; index += 1) {
2798
+ const sender = transactions[index].decoded.from;
2799
+ feesBySender.set(sender, (feesBySender.get(sender) || 0n) + feeEntries[index].fee);
2800
+ }
2801
+ await Promise.all(
2802
+ [...feesBySender].map(async ([sender, required]) => {
2803
+ const balance = BigInt(await this.balanceOf(sender) || 0n);
2804
+ if (balance < required) {
2805
+ throw new Error(`insufficient balance for transaction fees from ${sender}: need ${required}, got ${balance}`);
2806
+ }
2807
+ })
2808
+ );
2809
+ }
2771
2810
  return transactions;
2772
2811
  }
2812
+ async #assertFeeBurnSupported() {
2813
+ const creator = await this.staticCall(addresses.nativeToken, "creator");
2814
+ const canBurn = await this.staticCall(addresses.nativeToken, "hasRole", [creator, "BURN"]);
2815
+ if (!canBurn) {
2816
+ throw new Error("native token genesis does not support protocol fee burning");
2817
+ }
2818
+ }
2773
2819
  /** Check if the next block will cross an epoch boundary (block-based timing) */
2774
2820
  #isEpochBoundary(blockHeight) {
2775
2821
  return (blockHeight + 1) % this.#epochLength === 0;
@@ -3218,9 +3264,12 @@ class Chain extends VersionControl {
3218
3264
  async #versionHandler() {
3219
3265
  return new globalThis.peernet.protos["peernet-response"]({ response: this.version });
3220
3266
  }
3221
- async #executeTransaction({ hash, from, to, method, params, nonce }) {
3267
+ async #executeTransaction({ hash, from, to, method, params, nonce, feePayments = { payments: [], burned: 0n } }) {
3222
3268
  try {
3223
- let result = await this.machine.execute(to, method, params);
3269
+ if (feePayments.payments.length > 0 || feePayments.burned > 0n) {
3270
+ await this.machine.collectFee(from, feePayments.payments, feePayments.burned);
3271
+ }
3272
+ let result = await this.machine.execute(to, method, params, from);
3224
3273
  globalThis.pubsub.publish(`transaction.completed.${hash}`, { status: "fulfilled", hash });
3225
3274
  return result || "no state change";
3226
3275
  } catch (error) {
@@ -3292,7 +3341,10 @@ class Chain extends VersionControl {
3292
3341
  for (const transaction of allTransactions) {
3293
3342
  if (!contracts.includes(transaction.decoded.to)) contracts.push(transaction.decoded.to);
3294
3343
  this.removePendingNonce(transaction.decoded.from, transaction.decoded.nonce);
3295
- await this.#handleTransaction(transaction, []);
3344
+ const transactionHash = await transaction.hash();
3345
+ const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
3346
+ const feeDistribution = supportsTransactionFees(blockMessage.decoded.protocolVersion) ? distributeTransactionFee(BigInt(await calculateFee(transaction.decoded)), transactionHash, validatorAddresses) : { payments: [], burned: 0n };
3347
+ await this.#handleTransaction(transaction, [], void 0, feeDistribution);
3296
3348
  }
3297
3349
  try {
3298
3350
  promises = await Promise.allSettled(promises);
@@ -3349,7 +3401,7 @@ class Chain extends VersionControl {
3349
3401
  if (await this.hasTransactionToHandle() && !this.#runningEpoch && this.#participating) await this.#runEpoch();
3350
3402
  return true;
3351
3403
  }
3352
- async #handleTransaction(transaction, latestTransactions, block) {
3404
+ async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
3353
3405
  await this.validateTransactionSignature(transaction);
3354
3406
  const hash = await transaction.hash();
3355
3407
  const doubleTransactions = [];
@@ -3362,7 +3414,7 @@ class Chain extends VersionControl {
3362
3414
  return;
3363
3415
  }
3364
3416
  try {
3365
- const result = await this.#executeTransaction({ ...transaction.decoded, hash });
3417
+ const result = await this.#executeTransaction({ ...transaction.decoded, hash, feePayments });
3366
3418
  if (block) {
3367
3419
  block.transactions.push(hash);
3368
3420
  block.fees = block.fees += await calculateFee(transaction.decoded);
@@ -3418,7 +3470,7 @@ class Chain extends VersionControl {
3418
3470
  for (const { transaction, hash } of allTransactions) {
3419
3471
  await this.validateTransactionSignature(transaction);
3420
3472
  block.transactions.push(hash);
3421
- block.fees += BigInt(await calculateFee(transaction.decoded));
3473
+ if (supportsTransactionFees(this.version)) block.fees += BigInt(await calculateFee(transaction.decoded));
3422
3474
  await globalThis.peernet.put(hash, transaction.encoded, "transaction");
3423
3475
  }
3424
3476
  if (block.transactions.length === 0) return;
@@ -3428,9 +3480,17 @@ class Chain extends VersionControl {
3428
3480
  const canonicalValidators = await this.staticCall(addresses.validators, "validators");
3429
3481
  const sortedValidators = [...canonicalValidators].sort();
3430
3482
  if (sortedValidators.length === 0) throw new Error("cannot produce a block without validators");
3483
+ if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported();
3484
+ const feeEntries = supportsTransactionFees(this.version) ? await Promise.all(
3485
+ allTransactions.map(async ({ transaction, hash }) => ({
3486
+ fee: BigInt(await calculateFee(transaction.decoded)),
3487
+ transactionHash: hash
3488
+ }))
3489
+ ) : [];
3490
+ const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators) : new Map(sortedValidators.map((address) => [address, 0n]));
3431
3491
  block.validators = sortedValidators.map((validatorAddress) => ({
3432
3492
  address: validatorAddress,
3433
- reward: block.fees / BigInt(sortedValidators.length) + block.reward / BigInt(sortedValidators.length)
3493
+ reward: (validatorFees.get(validatorAddress) || 0n) + block.reward / BigInt(sortedValidators.length)
3434
3494
  }));
3435
3495
  try {
3436
3496
  block.producer = globalThis.peernet.selectedAccount || "";
@@ -3555,7 +3615,7 @@ class Chain extends VersionControl {
3555
3615
  * @returns
3556
3616
  */
3557
3617
  internalCall(sender, contract, method, parameters) {
3558
- return this.machine.execute(contract, method, parameters);
3618
+ return this.machine.execute(contract, method, parameters, sender);
3559
3619
  }
3560
3620
  /**
3561
3621
  *
@@ -3565,7 +3625,7 @@ class Chain extends VersionControl {
3565
3625
  * @returns
3566
3626
  */
3567
3627
  call(contract, method, parameters) {
3568
- return this.machine.execute(contract, method, parameters);
3628
+ return this.machine.execute(contract, method, parameters, globalThis.peernet.selectedAccount);
3569
3629
  }
3570
3630
  staticCall(contract, method, parameters) {
3571
3631
  return this.machine.get(contract, method, parameters);