@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.
@@ -1056,6 +1056,61 @@ const contractFactoryMessage = bytecodes.contractFactory;
1056
1056
  const nativeTokenMessage = bytecodes.nativeToken;
1057
1057
  const nameServiceMessage = bytecodes.nameService;
1058
1058
  const validatorsMessage = bytecodes.validators;
1059
+ const TRANSACTION_FEE_BYTES = 1024n;
1060
+ const TRANSACTION_FEE_UNIT = 10n;
1061
+ const FEE_BURN_BASIS_POINTS = 1000n;
1062
+ const FEE_BASIS_POINTS = 10000n;
1063
+ const FEE_PROTOCOL_VERSION = "1.10.9";
1064
+ const parseProtocolVersion = (version) => {
1065
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
1066
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
1067
+ };
1068
+ const supportsTransactionFees = (version) => {
1069
+ const actual = parseProtocolVersion(version);
1070
+ const required = parseProtocolVersion(FEE_PROTOCOL_VERSION);
1071
+ if (!actual)
1072
+ return false;
1073
+ for (let index = 0; index < required.length; index += 1) {
1074
+ if (actual[index] !== required[index])
1075
+ return actual[index] > required[index];
1076
+ }
1077
+ return true;
1078
+ };
1079
+ const feeRotationIndex = (transactionHash, validatorCount) => {
1080
+ let value = 0n;
1081
+ for (const character of transactionHash)
1082
+ value = (value * 31n + BigInt(character.charCodeAt(0))) % 4294967291n;
1083
+ return Number(value % BigInt(validatorCount));
1084
+ };
1085
+ const distributeTransactionFee = (fee, transactionHash, validatorAddresses) => {
1086
+ const canonicalValidators = [...new Set(validatorAddresses)].sort();
1087
+ if (canonicalValidators.length === 0)
1088
+ throw new Error("cannot distribute transaction fee without validators");
1089
+ if (fee < 0n)
1090
+ throw new Error("transaction fee cannot be negative");
1091
+ const burned = fee * FEE_BURN_BASIS_POINTS / FEE_BASIS_POINTS;
1092
+ const validatorPool = fee - burned;
1093
+ const count = BigInt(canonicalValidators.length);
1094
+ const base = validatorPool / count;
1095
+ const remainder = Number(validatorPool % count);
1096
+ const start = feeRotationIndex(transactionHash, canonicalValidators.length);
1097
+ const validatorFees = new Map(canonicalValidators.map((validator) => [validator, base]));
1098
+ for (let index = 0; index < remainder; index += 1) {
1099
+ const validator = canonicalValidators[(start + index) % canonicalValidators.length];
1100
+ validatorFees.set(validator, validatorFees.get(validator) + 1n);
1101
+ }
1102
+ const payments = [...validatorFees.entries()].filter(([, amount]) => amount > 0n).map(([to, amount]) => ({ to, amount }));
1103
+ return { burned, validatorFees, payments };
1104
+ };
1105
+ const aggregateValidatorFees = (fees, validatorAddresses) => {
1106
+ const totals = new Map([...new Set(validatorAddresses)].sort().map((validator) => [validator, 0n]));
1107
+ for (const entry of fees) {
1108
+ const { validatorFees } = distributeTransactionFee(entry.fee, entry.transactionHash, validatorAddresses);
1109
+ for (const [validator, amount] of validatorFees)
1110
+ totals.set(validator, totals.get(validator) + amount);
1111
+ }
1112
+ return totals;
1113
+ };
1059
1114
  const createContractMessage = async (creator, contract, constructorParameters = []) => {
1060
1115
  return new ContractMessage({
1061
1116
  creator,
@@ -1064,11 +1119,10 @@ const createContractMessage = async (creator, contract, constructorParameters =
1064
1119
  });
1065
1120
  };
1066
1121
  const calculateFee = async (transaction, format = false) => {
1067
- if (transaction.to === validators$2)
1068
- return 0;
1069
1122
  transaction = await new TransactionMessage(transaction);
1070
- let fee = toBigInt(String(transaction.encoded.length));
1071
- fee /= 1073741824n;
1123
+ const encodedBytes = BigInt(transaction.encoded.length);
1124
+ const units = (encodedBytes + TRANSACTION_FEE_BYTES - 1n) / TRANSACTION_FEE_BYTES;
1125
+ const fee = units * TRANSACTION_FEE_UNIT;
1072
1126
  return format ? formatUnits(fee.toString()) : fee;
1073
1127
  };
1074
1128
  const createTransactionHash = async (transaction) => {
@@ -8801,7 +8855,7 @@ class Machine {
8801
8855
  * @param {Array} parameters
8802
8856
  * @returns Promise<message>
8803
8857
  */
8804
- async execute(contract, method, parameters) {
8858
+ async execute(contract, method, parameters, sender) {
8805
8859
  try {
8806
8860
  if (contract === contractFactory$2 && method === "registerContract") {
8807
8861
  if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
@@ -8842,11 +8896,15 @@ ${error.message}`);
8842
8896
  to: contract,
8843
8897
  contract,
8844
8898
  method,
8845
- params: parameters
8899
+ params: parameters,
8900
+ sender
8846
8901
  }
8847
8902
  });
8848
8903
  });
8849
8904
  }
8905
+ collectFee(from, payments, burned) {
8906
+ return this.#askWorker("collectFee", { from, payments, burned });
8907
+ }
8850
8908
  get(contract, method, parameters) {
8851
8909
  return new Promise((resolve, reject) => {
8852
8910
  const id = randombytes(20).toString();
@@ -9690,9 +9748,20 @@ class State extends Contract {
9690
9748
  #loadBlockTransactions;
9691
9749
  #getLastTransactions;
9692
9750
  // todo throw error
9693
- async #_executeTransaction(transaction) {
9751
+ async #_executeTransaction(transaction, validators, feesEnabled) {
9694
9752
  try {
9695
- await this.#machine.execute(transaction.decoded.to, transaction.decoded.method, transaction.decoded.params);
9753
+ const hash = await transaction.hash();
9754
+ if (feesEnabled) {
9755
+ const fee = BigInt(await calculateFee(transaction.decoded));
9756
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators);
9757
+ await this.#machine.collectFee(transaction.decoded.from, payments, burned);
9758
+ }
9759
+ await this.#machine.execute(
9760
+ transaction.decoded.to,
9761
+ transaction.decoded.method,
9762
+ transaction.decoded.params,
9763
+ transaction.decoded.from
9764
+ );
9696
9765
  } catch (error) {
9697
9766
  console.log(error);
9698
9767
  await globalThis.transactionPoolStore.delete(await transaction.hash());
@@ -9721,23 +9790,21 @@ class State extends Contract {
9721
9790
  try {
9722
9791
  debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
9723
9792
  let transactions = await this.#loadBlockTransactions(block.transactions || []);
9793
+ const validators = block.validators.map(({ address }) => address);
9724
9794
  debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
9725
- let priority = [];
9726
9795
  for (const transaction of transactions) {
9727
9796
  const hash = await transaction.hash();
9728
- if (transaction.decoded.priority) priority.push(transaction);
9729
9797
  if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
9730
9798
  }
9731
- if (priority.length > 0) {
9732
- debug$1(`executing ${priority.length} priority transactions for block ${block.index}`);
9733
- priority = priority.sort((a, b) => a.nonce - b.nonce);
9734
- for (const transaction of priority) {
9735
- await this.#_executeTransaction(transaction);
9736
- }
9737
- }
9738
- transactions = transactions.filter((transaction) => !transaction.decoded.priority);
9799
+ transactions = transactions.sort((a, b) => {
9800
+ if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
9801
+ const left = BigInt(a.decoded.nonce);
9802
+ const right = BigInt(b.decoded.nonce);
9803
+ return left < right ? -1 : left > right ? 1 : 0;
9804
+ });
9739
9805
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
9740
- await Promise.all(transactions.map((transaction) => this.#_executeTransaction(transaction)));
9806
+ const feesEnabled = supportsTransactionFees(block.protocolVersion);
9807
+ for (const transaction of transactions) await this.#_executeTransaction(transaction, validators, feesEnabled);
9741
9808
  this.#blocks[block.index].loaded = true;
9742
9809
  debug$1(`executed transactions for block ${block.index}`);
9743
9810
  if (Number(block.index) === 0) this.#loaded = true;
@@ -10191,7 +10258,7 @@ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validator
10191
10258
  );
10192
10259
  }
10193
10260
  };
10194
- const validateBlockEconomics = (block, calculatedFees) => {
10261
+ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map()) => {
10195
10262
  if (block.validators.length === 0) {
10196
10263
  throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
10197
10264
  }
@@ -10202,8 +10269,9 @@ const validateBlockEconomics = (block, calculatedFees) => {
10202
10269
  throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
10203
10270
  }
10204
10271
  const validatorCount = BigInt(block.validators.length);
10205
- const expectedReward = calculatedFees / validatorCount + BLOCK_REWARD / validatorCount;
10272
+ const baseReward = BLOCK_REWARD / validatorCount;
10206
10273
  for (const validator of block.validators) {
10274
+ const expectedReward = baseReward + (validatorFees.get(validator.address) || 0n);
10207
10275
  if (validator.reward !== expectedReward) {
10208
10276
  throw new Error(
10209
10277
  `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
@@ -10671,10 +10739,42 @@ class Chain extends VersionControl {
10671
10739
  return transaction;
10672
10740
  })
10673
10741
  );
10674
- const calculatedFees = (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n);
10675
- validateBlockEconomics(blockMessage.decoded, calculatedFees);
10742
+ const feesEnabled = supportsTransactionFees(blockMessage.decoded.protocolVersion);
10743
+ if (feesEnabled) await this.#assertFeeBurnSupported();
10744
+ const calculatedFees = feesEnabled ? (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n) : 0n;
10745
+ const feeEntries = feesEnabled ? await Promise.all(
10746
+ transactions.map(async (transaction) => ({
10747
+ fee: BigInt(await calculateFee(transaction.decoded)),
10748
+ transactionHash: await transaction.hash()
10749
+ }))
10750
+ ) : [];
10751
+ const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
10752
+ const validatorFees = feesEnabled ? aggregateValidatorFees(feeEntries, validatorAddresses) : new Map(validatorAddresses.map((address) => [address, 0n]));
10753
+ validateBlockEconomics(blockMessage.decoded, calculatedFees, validatorFees);
10754
+ if (feesEnabled) {
10755
+ const feesBySender = /* @__PURE__ */ new Map();
10756
+ for (let index = 0; index < transactions.length; index += 1) {
10757
+ const sender = transactions[index].decoded.from;
10758
+ feesBySender.set(sender, (feesBySender.get(sender) || 0n) + feeEntries[index].fee);
10759
+ }
10760
+ await Promise.all(
10761
+ [...feesBySender].map(async ([sender, required]) => {
10762
+ const balance = BigInt(await this.balanceOf(sender) || 0n);
10763
+ if (balance < required) {
10764
+ throw new Error(`insufficient balance for transaction fees from ${sender}: need ${required}, got ${balance}`);
10765
+ }
10766
+ })
10767
+ );
10768
+ }
10676
10769
  return transactions;
10677
10770
  }
10771
+ async #assertFeeBurnSupported() {
10772
+ const creator = await this.staticCall(addresses.nativeToken, "creator");
10773
+ const canBurn = await this.staticCall(addresses.nativeToken, "hasRole", [creator, "BURN"]);
10774
+ if (!canBurn) {
10775
+ throw new Error("native token genesis does not support protocol fee burning");
10776
+ }
10777
+ }
10678
10778
  /** Check if the next block will cross an epoch boundary (block-based timing) */
10679
10779
  #isEpochBoundary(blockHeight) {
10680
10780
  return (blockHeight + 1) % this.#epochLength === 0;
@@ -11123,9 +11223,12 @@ class Chain extends VersionControl {
11123
11223
  async #versionHandler() {
11124
11224
  return new globalThis.peernet.protos["peernet-response"]({ response: this.version });
11125
11225
  }
11126
- async #executeTransaction({ hash, from, to, method, params, nonce }) {
11226
+ async #executeTransaction({ hash, from, to, method, params, nonce, feePayments = { payments: [], burned: 0n } }) {
11127
11227
  try {
11128
- let result = await this.machine.execute(to, method, params);
11228
+ if (feePayments.payments.length > 0 || feePayments.burned > 0n) {
11229
+ await this.machine.collectFee(from, feePayments.payments, feePayments.burned);
11230
+ }
11231
+ let result = await this.machine.execute(to, method, params, from);
11129
11232
  globalThis.pubsub.publish(`transaction.completed.${hash}`, { status: "fulfilled", hash });
11130
11233
  return result || "no state change";
11131
11234
  } catch (error) {
@@ -11197,7 +11300,10 @@ class Chain extends VersionControl {
11197
11300
  for (const transaction of allTransactions) {
11198
11301
  if (!contracts.includes(transaction.decoded.to)) contracts.push(transaction.decoded.to);
11199
11302
  this.removePendingNonce(transaction.decoded.from, transaction.decoded.nonce);
11200
- await this.#handleTransaction(transaction, []);
11303
+ const transactionHash = await transaction.hash();
11304
+ const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
11305
+ const feeDistribution = supportsTransactionFees(blockMessage.decoded.protocolVersion) ? distributeTransactionFee(BigInt(await calculateFee(transaction.decoded)), transactionHash, validatorAddresses) : { payments: [], burned: 0n };
11306
+ await this.#handleTransaction(transaction, [], void 0, feeDistribution);
11201
11307
  }
11202
11308
  try {
11203
11309
  promises = await Promise.allSettled(promises);
@@ -11254,7 +11360,7 @@ class Chain extends VersionControl {
11254
11360
  if (await this.hasTransactionToHandle() && !this.#runningEpoch && this.#participating) await this.#runEpoch();
11255
11361
  return true;
11256
11362
  }
11257
- async #handleTransaction(transaction, latestTransactions, block) {
11363
+ async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
11258
11364
  await this.validateTransactionSignature(transaction);
11259
11365
  const hash = await transaction.hash();
11260
11366
  const doubleTransactions = [];
@@ -11267,7 +11373,7 @@ class Chain extends VersionControl {
11267
11373
  return;
11268
11374
  }
11269
11375
  try {
11270
- const result = await this.#executeTransaction({ ...transaction.decoded, hash });
11376
+ const result = await this.#executeTransaction({ ...transaction.decoded, hash, feePayments });
11271
11377
  if (block) {
11272
11378
  block.transactions.push(hash);
11273
11379
  block.fees = block.fees += await calculateFee(transaction.decoded);
@@ -11323,7 +11429,7 @@ class Chain extends VersionControl {
11323
11429
  for (const { transaction, hash } of allTransactions) {
11324
11430
  await this.validateTransactionSignature(transaction);
11325
11431
  block.transactions.push(hash);
11326
- block.fees += BigInt(await calculateFee(transaction.decoded));
11432
+ if (supportsTransactionFees(this.version)) block.fees += BigInt(await calculateFee(transaction.decoded));
11327
11433
  await globalThis.peernet.put(hash, transaction.encoded, "transaction");
11328
11434
  }
11329
11435
  if (block.transactions.length === 0) return;
@@ -11333,9 +11439,17 @@ class Chain extends VersionControl {
11333
11439
  const canonicalValidators = await this.staticCall(addresses.validators, "validators");
11334
11440
  const sortedValidators = [...canonicalValidators].sort();
11335
11441
  if (sortedValidators.length === 0) throw new Error("cannot produce a block without validators");
11442
+ if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported();
11443
+ const feeEntries = supportsTransactionFees(this.version) ? await Promise.all(
11444
+ allTransactions.map(async ({ transaction, hash }) => ({
11445
+ fee: BigInt(await calculateFee(transaction.decoded)),
11446
+ transactionHash: hash
11447
+ }))
11448
+ ) : [];
11449
+ const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators) : new Map(sortedValidators.map((address) => [address, 0n]));
11336
11450
  block.validators = sortedValidators.map((validatorAddress) => ({
11337
11451
  address: validatorAddress,
11338
- reward: block.fees / BigInt(sortedValidators.length) + block.reward / BigInt(sortedValidators.length)
11452
+ reward: (validatorFees.get(validatorAddress) || 0n) + block.reward / BigInt(sortedValidators.length)
11339
11453
  }));
11340
11454
  try {
11341
11455
  block.producer = globalThis.peernet.selectedAccount || "";
@@ -11460,7 +11574,7 @@ class Chain extends VersionControl {
11460
11574
  * @returns
11461
11575
  */
11462
11576
  internalCall(sender, contract, method, parameters) {
11463
- return this.machine.execute(contract, method, parameters);
11577
+ return this.machine.execute(contract, method, parameters, sender);
11464
11578
  }
11465
11579
  /**
11466
11580
  *
@@ -11470,7 +11584,7 @@ class Chain extends VersionControl {
11470
11584
  * @returns
11471
11585
  */
11472
11586
  call(contract, method, parameters) {
11473
- return this.machine.execute(contract, method, parameters);
11587
+ return this.machine.execute(contract, method, parameters, globalThis.peernet.selectedAccount);
11474
11588
  }
11475
11589
  staticCall(contract, method, parameters) {
11476
11590
  return this.machine.get(contract, method, parameters);