@leofcoin/chain 1.10.9 → 1.10.10

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.
@@ -1058,9 +1058,17 @@ const nameServiceMessage = bytecodes.nameService;
1058
1058
  const validatorsMessage = bytecodes.validators;
1059
1059
  const TRANSACTION_FEE_BYTES = 1024n;
1060
1060
  const TRANSACTION_FEE_UNIT = 10n;
1061
- const FEE_BURN_BASIS_POINTS = 1000n;
1061
+ const MAX_TRANSACTION_BYTES = 32 * 1024;
1062
+ const MAX_BLOCK_TRANSACTION_BYTES = 128 * 1024;
1063
+ const MAX_BLOCK_TRANSACTIONS = 256;
1064
+ BigInt(MAX_TRANSACTION_BYTES) / TRANSACTION_FEE_BYTES * TRANSACTION_FEE_UNIT;
1062
1065
  const FEE_BASIS_POINTS = 10000n;
1063
1066
  const FEE_PROTOCOL_VERSION = "1.10.9";
1067
+ const MONETARY_POLICY_PROTOCOL_VERSION = "1.10.10";
1068
+ const BLOCKS_PER_YEAR = 5256000n;
1069
+ const ANNUAL_ISSUANCE_BASIS_POINTS = 200n;
1070
+ const SUPPLY_FLOOR_BASIS_POINTS = 9500n;
1071
+ const MONETARY_FEE_BURN_BASIS_POINTS = 1000n;
1064
1072
  const parseProtocolVersion = (version) => {
1065
1073
  const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
1066
1074
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
@@ -1076,19 +1084,61 @@ const supportsTransactionFees = (version) => {
1076
1084
  }
1077
1085
  return true;
1078
1086
  };
1087
+ const supportsMonetaryPolicy = (version) => {
1088
+ const actual = parseProtocolVersion(version);
1089
+ const required = parseProtocolVersion(MONETARY_POLICY_PROTOCOL_VERSION);
1090
+ if (!actual)
1091
+ return false;
1092
+ for (let index = 0; index < required.length; index += 1) {
1093
+ if (actual[index] !== required[index])
1094
+ return actual[index] > required[index];
1095
+ }
1096
+ return true;
1097
+ };
1098
+ const calculateMonetaryPolicy = (totalSupply, targetSupply) => {
1099
+ if (totalSupply < 0n || targetSupply <= 0n)
1100
+ throw new Error("invalid monetary policy supply");
1101
+ const floorSupply = targetSupply * SUPPLY_FLOOR_BASIS_POINTS / FEE_BASIS_POINTS;
1102
+ if (totalSupply < floorSupply) {
1103
+ const annualIssuance = targetSupply * ANNUAL_ISSUANCE_BASIS_POINTS / FEE_BASIS_POINTS;
1104
+ const scheduled = annualIssuance / BLOCKS_PER_YEAR || 1n;
1105
+ return { subsidy: scheduled < floorSupply - totalSupply ? scheduled : floorSupply - totalSupply, burnBasisPoints: 0n, floorSupply };
1106
+ }
1107
+ return {
1108
+ subsidy: 0n,
1109
+ burnBasisPoints: totalSupply >= targetSupply ? MONETARY_FEE_BURN_BASIS_POINTS : 0n,
1110
+ floorSupply
1111
+ };
1112
+ };
1113
+ const distributeAmount = (amount, addresses, rotation = 0) => {
1114
+ const canonical = [...new Set(addresses)].sort();
1115
+ if (canonical.length === 0)
1116
+ throw new Error("cannot distribute without validators");
1117
+ const count = BigInt(canonical.length);
1118
+ const base = amount / count;
1119
+ const remainder = Number(amount % count);
1120
+ const result = new Map(canonical.map((address) => [address, base]));
1121
+ for (let index = 0; index < remainder; index += 1) {
1122
+ const address = canonical[(rotation + index) % canonical.length];
1123
+ result.set(address, result.get(address) + 1n);
1124
+ }
1125
+ return result;
1126
+ };
1079
1127
  const feeRotationIndex = (transactionHash, validatorCount) => {
1080
1128
  let value = 0n;
1081
1129
  for (const character of transactionHash)
1082
1130
  value = (value * 31n + BigInt(character.charCodeAt(0))) % 4294967291n;
1083
1131
  return Number(value % BigInt(validatorCount));
1084
1132
  };
1085
- const distributeTransactionFee = (fee, transactionHash, validatorAddresses) => {
1133
+ const distributeTransactionFee = (fee, transactionHash, validatorAddresses, burnBasisPoints = 1000n) => {
1086
1134
  const canonicalValidators = [...new Set(validatorAddresses)].sort();
1087
1135
  if (canonicalValidators.length === 0)
1088
1136
  throw new Error("cannot distribute transaction fee without validators");
1089
1137
  if (fee < 0n)
1090
1138
  throw new Error("transaction fee cannot be negative");
1091
- const burned = fee * FEE_BURN_BASIS_POINTS / FEE_BASIS_POINTS;
1139
+ if (burnBasisPoints < 0n || burnBasisPoints > FEE_BASIS_POINTS)
1140
+ throw new Error("invalid fee burn rate");
1141
+ const burned = fee * burnBasisPoints / FEE_BASIS_POINTS;
1092
1142
  const validatorPool = fee - burned;
1093
1143
  const count = BigInt(canonicalValidators.length);
1094
1144
  const base = validatorPool / count;
@@ -1102,10 +1152,10 @@ const distributeTransactionFee = (fee, transactionHash, validatorAddresses) => {
1102
1152
  const payments = [...validatorFees.entries()].filter(([, amount]) => amount > 0n).map(([to, amount]) => ({ to, amount }));
1103
1153
  return { burned, validatorFees, payments };
1104
1154
  };
1105
- const aggregateValidatorFees = (fees, validatorAddresses) => {
1155
+ const aggregateValidatorFees = (fees, validatorAddresses, burnBasisPoints = 1000n) => {
1106
1156
  const totals = new Map([...new Set(validatorAddresses)].sort().map((validator) => [validator, 0n]));
1107
1157
  for (const entry of fees) {
1108
- const { validatorFees } = distributeTransactionFee(entry.fee, entry.transactionHash, validatorAddresses);
1158
+ const { validatorFees } = distributeTransactionFee(entry.fee, entry.transactionHash, validatorAddresses, burnBasisPoints);
1109
1159
  for (const [validator, amount] of validatorFees)
1110
1160
  totals.set(validator, totals.get(validator) + amount);
1111
1161
  }
@@ -1125,6 +1175,26 @@ const calculateFee = async (transaction, format = false) => {
1125
1175
  const fee = units * TRANSACTION_FEE_UNIT;
1126
1176
  return format ? formatUnits(fee.toString()) : fee;
1127
1177
  };
1178
+ const validateTransactionResourceLimits = async (transaction) => {
1179
+ const message = await new TransactionMessage(transaction);
1180
+ const size = message.encoded.length;
1181
+ if (size > MAX_TRANSACTION_BYTES) {
1182
+ throw new Error(`transaction exceeds ${MAX_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1183
+ }
1184
+ return size;
1185
+ };
1186
+ const validateBlockResourceLimits = async (transactions) => {
1187
+ if (transactions.length > MAX_BLOCK_TRANSACTIONS) {
1188
+ throw new Error(`block exceeds ${MAX_BLOCK_TRANSACTIONS} transaction protocol limit`);
1189
+ }
1190
+ let size = 0;
1191
+ for (const transaction of transactions)
1192
+ size += await validateTransactionResourceLimits(transaction);
1193
+ if (size > MAX_BLOCK_TRANSACTION_BYTES) {
1194
+ throw new Error(`block transactions exceed ${MAX_BLOCK_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1195
+ }
1196
+ return size;
1197
+ };
1128
1198
  const createTransactionHash = async (transaction) => {
1129
1199
  const isRawTransactionMessage = transaction instanceof RawTransactionMessage;
1130
1200
  let message;
@@ -7899,19 +7969,14 @@ class Transaction extends Protocol {
7899
7969
  return new Promise(async (resolve, reject) => {
7900
7970
  let size = 0;
7901
7971
  const _transactions = [];
7902
- const MAX_BLOCK_TX_BYTES = 786432;
7903
- await Promise.all(
7904
- transactions.map(async (tx) => {
7905
- tx = await new TransactionMessage(tx);
7906
- const newSize = size + tx.encoded.length;
7907
- if (newSize <= MAX_BLOCK_TX_BYTES) {
7908
- size = newSize;
7909
- _transactions.push({ ...tx.decoded, hash: await tx.hash() });
7910
- } else {
7911
- resolve(_transactions);
7912
- }
7913
- })
7914
- );
7972
+ for (const rawTransaction of transactions) {
7973
+ const tx = await new TransactionMessage(rawTransaction);
7974
+ if (tx.encoded.length > MAX_TRANSACTION_BYTES) continue;
7975
+ const newSize = size + tx.encoded.length;
7976
+ if (newSize > MAX_BLOCK_TRANSACTION_BYTES || _transactions.length >= MAX_BLOCK_TRANSACTIONS) break;
7977
+ size = newSize;
7978
+ _transactions.push({ ...tx.decoded, hash: await tx.hash() });
7979
+ }
7915
7980
  return resolve(_transactions);
7916
7981
  });
7917
7982
  }
@@ -8905,6 +8970,9 @@ ${error.message}`);
8905
8970
  collectFee(from, payments, burned) {
8906
8971
  return this.#askWorker("collectFee", { from, payments, burned });
8907
8972
  }
8973
+ settleRewards(rewards) {
8974
+ return this.#askWorker("settleRewards", { rewards });
8975
+ }
8908
8976
  get(contract, method, parameters) {
8909
8977
  return new Promise((resolve, reject) => {
8910
8978
  const id = randombytes(20).toString();
@@ -9748,12 +9816,12 @@ class State extends Contract {
9748
9816
  #loadBlockTransactions;
9749
9817
  #getLastTransactions;
9750
9818
  // todo throw error
9751
- async #_executeTransaction(transaction, validators, feesEnabled) {
9819
+ async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints) {
9752
9820
  try {
9753
9821
  const hash = await transaction.hash();
9754
9822
  if (feesEnabled) {
9755
9823
  const fee = BigInt(await calculateFee(transaction.decoded));
9756
- const { payments, burned } = distributeTransactionFee(fee, hash, validators);
9824
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
9757
9825
  await this.#machine.collectFee(transaction.decoded.from, payments, burned);
9758
9826
  }
9759
9827
  await this.#machine.execute(
@@ -9804,7 +9872,20 @@ class State extends Contract {
9804
9872
  });
9805
9873
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
9806
9874
  const feesEnabled = supportsTransactionFees(block.protocolVersion);
9807
- for (const transaction of transactions) await this.#_executeTransaction(transaction, validators, feesEnabled);
9875
+ const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
9876
+ if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
9877
+ const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
9878
+ BigInt(await this.#machine.get(nativeToken$2, "totalSupply")),
9879
+ BigInt(await this.#machine.get(nativeToken$2, "targetSupply"))
9880
+ ) : { subsidy: 0n, burnBasisPoints: 1000n };
9881
+ for (const transaction of transactions) {
9882
+ await this.#_executeTransaction(transaction, validators, feesEnabled, policy.burnBasisPoints);
9883
+ }
9884
+ if (monetaryPolicyEnabled && policy.subsidy > 0n) {
9885
+ await this.#machine.settleRewards([
9886
+ ...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
9887
+ ]);
9888
+ }
9808
9889
  this.#blocks[block.index].loaded = true;
9809
9890
  debug$1(`executed transactions for block ${block.index}`);
9810
9891
  if (Number(block.index) === 0) this.#loaded = true;
@@ -10248,7 +10329,6 @@ const validateChainLink = (localTip, incoming) => {
10248
10329
  return "append";
10249
10330
  };
10250
10331
 
10251
- const BLOCK_REWARD = 150n;
10252
10332
  const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validators) => {
10253
10333
  const actualValidators = validators.map(({ address }) => address);
10254
10334
  const canonicalExpected = [...new Set(expectedValidators)].sort();
@@ -10258,20 +10338,18 @@ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validator
10258
10338
  );
10259
10339
  }
10260
10340
  };
10261
- const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map()) => {
10341
+ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map(), subsidyRewards = /* @__PURE__ */ new Map(), expectedSubsidy = [...subsidyRewards.values()].reduce((sum, reward) => sum + reward, 0n)) => {
10262
10342
  if (block.validators.length === 0) {
10263
10343
  throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
10264
10344
  }
10265
- if (block.reward !== BLOCK_REWARD) {
10266
- throw new Error(`Block ${block.index} has invalid base reward: expected ${BLOCK_REWARD}, got ${block.reward}`);
10345
+ if (block.reward !== expectedSubsidy) {
10346
+ throw new Error(`Block ${block.index} has invalid base reward: expected ${expectedSubsidy}, got ${block.reward}`);
10267
10347
  }
10268
10348
  if (block.fees !== calculatedFees) {
10269
10349
  throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
10270
10350
  }
10271
- const validatorCount = BigInt(block.validators.length);
10272
- const baseReward = BLOCK_REWARD / validatorCount;
10273
10351
  for (const validator of block.validators) {
10274
- const expectedReward = baseReward + (validatorFees.get(validator.address) || 0n);
10352
+ const expectedReward = (subsidyRewards.get(validator.address) || 0n) + (validatorFees.get(validator.address) || 0n);
10275
10353
  if (validator.reward !== expectedReward) {
10276
10354
  throw new Error(
10277
10355
  `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
@@ -10740,7 +10818,10 @@ class Chain extends VersionControl {
10740
10818
  })
10741
10819
  );
10742
10820
  const feesEnabled = supportsTransactionFees(blockMessage.decoded.protocolVersion);
10743
- if (feesEnabled) await this.#assertFeeBurnSupported();
10821
+ if (feesEnabled) await this.#assertFeeBurnSupported(blockMessage.decoded.protocolVersion);
10822
+ const adaptivePolicy = supportsMonetaryPolicy(blockMessage.decoded.protocolVersion);
10823
+ if (adaptivePolicy) await validateBlockResourceLimits(transactions);
10824
+ const policy = await this.#monetaryPolicy(blockMessage.decoded.protocolVersion);
10744
10825
  const calculatedFees = feesEnabled ? (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n) : 0n;
10745
10826
  const feeEntries = feesEnabled ? await Promise.all(
10746
10827
  transactions.map(async (transaction) => ({
@@ -10749,8 +10830,9 @@ class Chain extends VersionControl {
10749
10830
  }))
10750
10831
  ) : [];
10751
10832
  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);
10833
+ const validatorFees = feesEnabled ? aggregateValidatorFees(feeEntries, validatorAddresses, policy.burnBasisPoints) : new Map(validatorAddresses.map((address) => [address, 0n]));
10834
+ const subsidyRewards = adaptivePolicy ? distributeAmount(policy.subsidy, validatorAddresses, Number(blockMessage.decoded.index) % validatorAddresses.length) : new Map(validatorAddresses.map((address) => [address, policy.subsidy / BigInt(validatorAddresses.length)]));
10835
+ validateBlockEconomics(blockMessage.decoded, calculatedFees, validatorFees, subsidyRewards, policy.subsidy);
10754
10836
  if (feesEnabled) {
10755
10837
  const feesBySender = /* @__PURE__ */ new Map();
10756
10838
  for (let index = 0; index < transactions.length; index += 1) {
@@ -10768,13 +10850,24 @@ class Chain extends VersionControl {
10768
10850
  }
10769
10851
  return transactions;
10770
10852
  }
10771
- async #assertFeeBurnSupported() {
10853
+ async #assertFeeBurnSupported(protocolVersion) {
10772
10854
  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");
10855
+ const [canBurn, canMint] = await Promise.all([
10856
+ this.staticCall(addresses.nativeToken, "hasRole", [creator, "BURN"]),
10857
+ this.staticCall(addresses.nativeToken, "hasRole", [creator, "MINT"])
10858
+ ]);
10859
+ if (!canBurn || supportsMonetaryPolicy(protocolVersion) && !canMint) {
10860
+ throw new Error("native token genesis does not support protocol monetary policy");
10776
10861
  }
10777
10862
  }
10863
+ async #monetaryPolicy(protocolVersion) {
10864
+ if (!supportsMonetaryPolicy(protocolVersion)) return { subsidy: 150n, burnBasisPoints: 1000n, floorSupply: 0n };
10865
+ const [totalSupply, targetSupply] = await Promise.all([
10866
+ this.staticCall(addresses.nativeToken, "totalSupply"),
10867
+ this.staticCall(addresses.nativeToken, "targetSupply")
10868
+ ]);
10869
+ return calculateMonetaryPolicy(BigInt(totalSupply), BigInt(targetSupply));
10870
+ }
10778
10871
  /** Check if the next block will cross an epoch boundary (block-based timing) */
10779
10872
  #isEpochBoundary(blockHeight) {
10780
10873
  return (blockHeight + 1) % this.#epochLength === 0;
@@ -11297,14 +11390,19 @@ class Chain extends VersionControl {
11297
11390
  if (nonceDiff !== 0) return nonceDiff;
11298
11391
  return 0;
11299
11392
  });
11393
+ const monetaryPolicy = await this.#monetaryPolicy(blockMessage.decoded.protocolVersion);
11300
11394
  for (const transaction of allTransactions) {
11301
11395
  if (!contracts.includes(transaction.decoded.to)) contracts.push(transaction.decoded.to);
11302
11396
  this.removePendingNonce(transaction.decoded.from, transaction.decoded.nonce);
11303
11397
  const transactionHash = await transaction.hash();
11304
11398
  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 };
11399
+ const feeDistribution = supportsTransactionFees(blockMessage.decoded.protocolVersion) ? distributeTransactionFee(BigInt(await calculateFee(transaction.decoded)), transactionHash, validatorAddresses, monetaryPolicy.burnBasisPoints) : { payments: [], burned: 0n };
11306
11400
  await this.#handleTransaction(transaction, [], void 0, feeDistribution);
11307
11401
  }
11402
+ if (supportsMonetaryPolicy(blockMessage.decoded.protocolVersion) && monetaryPolicy.subsidy > 0n) {
11403
+ const validators = blockMessage.decoded.validators.map(({ address }) => address);
11404
+ await this.machine.settleRewards([...distributeAmount(monetaryPolicy.subsidy, validators, blockIndex % validators.length)]);
11405
+ }
11308
11406
  try {
11309
11407
  promises = await Promise.allSettled(promises);
11310
11408
  const noncesByAddress = {};
@@ -11404,7 +11502,7 @@ class Chain extends VersionControl {
11404
11502
  fees: BigInt(0),
11405
11503
  timestamp,
11406
11504
  previousHash: "",
11407
- reward: BLOCK_REWARD,
11505
+ reward: 0n,
11408
11506
  index: 0,
11409
11507
  producer: "",
11410
11508
  producerProof: "",
@@ -11426,9 +11524,17 @@ class Chain extends VersionControl {
11426
11524
  if (nonceDiff !== 0) return nonceDiff;
11427
11525
  return 0;
11428
11526
  });
11527
+ let blockTransactionBytes = 0;
11429
11528
  for (const { transaction, hash } of allTransactions) {
11529
+ const transactionBytes = transaction.encoded.length;
11530
+ if (transactionBytes > MAX_TRANSACTION_BYTES) {
11531
+ await globalThis.transactionPoolStore.delete(hash);
11532
+ continue;
11533
+ }
11534
+ if (block.transactions.length >= MAX_BLOCK_TRANSACTIONS || blockTransactionBytes + transactionBytes > MAX_BLOCK_TRANSACTION_BYTES) break;
11430
11535
  await this.validateTransactionSignature(transaction);
11431
11536
  block.transactions.push(hash);
11537
+ blockTransactionBytes += transactionBytes;
11432
11538
  if (supportsTransactionFees(this.version)) block.fees += BigInt(await calculateFee(transaction.decoded));
11433
11539
  await globalThis.peernet.put(hash, transaction.encoded, "transaction");
11434
11540
  }
@@ -11439,17 +11545,20 @@ class Chain extends VersionControl {
11439
11545
  const canonicalValidators = await this.staticCall(addresses.validators, "validators");
11440
11546
  const sortedValidators = [...canonicalValidators].sort();
11441
11547
  if (sortedValidators.length === 0) throw new Error("cannot produce a block without validators");
11442
- if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported();
11548
+ if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported(this.version);
11549
+ const monetaryPolicy = await this.#monetaryPolicy(this.version);
11550
+ block.reward = monetaryPolicy.subsidy;
11443
11551
  const feeEntries = supportsTransactionFees(this.version) ? await Promise.all(
11444
11552
  allTransactions.map(async ({ transaction, hash }) => ({
11445
11553
  fee: BigInt(await calculateFee(transaction.decoded)),
11446
11554
  transactionHash: hash
11447
11555
  }))
11448
11556
  ) : [];
11449
- const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators) : new Map(sortedValidators.map((address) => [address, 0n]));
11557
+ const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators, monetaryPolicy.burnBasisPoints) : new Map(sortedValidators.map((address) => [address, 0n]));
11558
+ const subsidyRewards = supportsMonetaryPolicy(this.version) ? distributeAmount(monetaryPolicy.subsidy, sortedValidators, Number(block.index) % sortedValidators.length) : new Map(sortedValidators.map((address) => [address, monetaryPolicy.subsidy / BigInt(sortedValidators.length)]));
11450
11559
  block.validators = sortedValidators.map((validatorAddress) => ({
11451
11560
  address: validatorAddress,
11452
- reward: (validatorFees.get(validatorAddress) || 0n) + block.reward / BigInt(sortedValidators.length)
11561
+ reward: (validatorFees.get(validatorAddress) || 0n) + (subsidyRewards.get(validatorAddress) || 0n)
11453
11562
  }));
11454
11563
  try {
11455
11564
  block.producer = globalThis.peernet.selectedAccount || "";
@@ -977,9 +977,17 @@ var bytecodes = {
977
977
 
978
978
  const TRANSACTION_FEE_BYTES = 1024n;
979
979
  const TRANSACTION_FEE_UNIT = 10n;
980
- const FEE_BURN_BASIS_POINTS = 1000n;
980
+ const MAX_TRANSACTION_BYTES = 32 * 1024;
981
+ const MAX_BLOCK_TRANSACTION_BYTES = 128 * 1024;
982
+ const MAX_BLOCK_TRANSACTIONS = 256;
983
+ (BigInt(MAX_TRANSACTION_BYTES) / TRANSACTION_FEE_BYTES) * TRANSACTION_FEE_UNIT;
981
984
  const FEE_BASIS_POINTS = 10000n;
982
985
  const FEE_PROTOCOL_VERSION = '1.10.9';
986
+ const MONETARY_POLICY_PROTOCOL_VERSION = '1.10.10';
987
+ const BLOCKS_PER_YEAR = 5256000n;
988
+ const ANNUAL_ISSUANCE_BASIS_POINTS = 200n;
989
+ const SUPPLY_FLOOR_BASIS_POINTS = 9500n;
990
+ const MONETARY_FEE_BURN_BASIS_POINTS = 1000n;
983
991
  const parseProtocolVersion = (version) => {
984
992
  const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
985
993
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
@@ -995,19 +1003,61 @@ const supportsTransactionFees = (version) => {
995
1003
  }
996
1004
  return true;
997
1005
  };
1006
+ const supportsMonetaryPolicy = (version) => {
1007
+ const actual = parseProtocolVersion(version);
1008
+ const required = parseProtocolVersion(MONETARY_POLICY_PROTOCOL_VERSION);
1009
+ if (!actual)
1010
+ return false;
1011
+ for (let index = 0; index < required.length; index += 1) {
1012
+ if (actual[index] !== required[index])
1013
+ return actual[index] > required[index];
1014
+ }
1015
+ return true;
1016
+ };
1017
+ const calculateMonetaryPolicy = (totalSupply, targetSupply) => {
1018
+ if (totalSupply < 0n || targetSupply <= 0n)
1019
+ throw new Error('invalid monetary policy supply');
1020
+ const floorSupply = (targetSupply * SUPPLY_FLOOR_BASIS_POINTS) / FEE_BASIS_POINTS;
1021
+ if (totalSupply < floorSupply) {
1022
+ const annualIssuance = (targetSupply * ANNUAL_ISSUANCE_BASIS_POINTS) / FEE_BASIS_POINTS;
1023
+ const scheduled = annualIssuance / BLOCKS_PER_YEAR || 1n;
1024
+ return { subsidy: scheduled < floorSupply - totalSupply ? scheduled : floorSupply - totalSupply, burnBasisPoints: 0n, floorSupply };
1025
+ }
1026
+ return {
1027
+ subsidy: 0n,
1028
+ burnBasisPoints: totalSupply >= targetSupply ? MONETARY_FEE_BURN_BASIS_POINTS : 0n,
1029
+ floorSupply
1030
+ };
1031
+ };
1032
+ const distributeAmount = (amount, addresses, rotation = 0) => {
1033
+ const canonical = [...new Set(addresses)].sort();
1034
+ if (canonical.length === 0)
1035
+ throw new Error('cannot distribute without validators');
1036
+ const count = BigInt(canonical.length);
1037
+ const base = amount / count;
1038
+ const remainder = Number(amount % count);
1039
+ const result = new Map(canonical.map((address) => [address, base]));
1040
+ for (let index = 0; index < remainder; index += 1) {
1041
+ const address = canonical[(rotation + index) % canonical.length];
1042
+ result.set(address, result.get(address) + 1n);
1043
+ }
1044
+ return result;
1045
+ };
998
1046
  const feeRotationIndex = (transactionHash, validatorCount) => {
999
1047
  let value = 0n;
1000
1048
  for (const character of transactionHash)
1001
1049
  value = (value * 31n + BigInt(character.charCodeAt(0))) % 4294967291n;
1002
1050
  return Number(value % BigInt(validatorCount));
1003
1051
  };
1004
- const distributeTransactionFee = (fee, transactionHash, validatorAddresses) => {
1052
+ const distributeTransactionFee = (fee, transactionHash, validatorAddresses, burnBasisPoints = 1000n) => {
1005
1053
  const canonicalValidators = [...new Set(validatorAddresses)].sort();
1006
1054
  if (canonicalValidators.length === 0)
1007
1055
  throw new Error('cannot distribute transaction fee without validators');
1008
1056
  if (fee < 0n)
1009
1057
  throw new Error('transaction fee cannot be negative');
1010
- const burned = (fee * FEE_BURN_BASIS_POINTS) / FEE_BASIS_POINTS;
1058
+ if (burnBasisPoints < 0n || burnBasisPoints > FEE_BASIS_POINTS)
1059
+ throw new Error('invalid fee burn rate');
1060
+ const burned = (fee * burnBasisPoints) / FEE_BASIS_POINTS;
1011
1061
  const validatorPool = fee - burned;
1012
1062
  const count = BigInt(canonicalValidators.length);
1013
1063
  const base = validatorPool / count;
@@ -1030,6 +1080,26 @@ const calculateFee = async (transaction, format = false) => {
1030
1080
  const fee = units * TRANSACTION_FEE_UNIT;
1031
1081
  return format ? formatUnits(fee.toString()) : fee;
1032
1082
  };
1083
+ const validateTransactionResourceLimits = async (transaction) => {
1084
+ const message = await new TransactionMessage(transaction);
1085
+ const size = message.encoded.length;
1086
+ if (size > MAX_TRANSACTION_BYTES) {
1087
+ throw new Error(`transaction exceeds ${MAX_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1088
+ }
1089
+ return size;
1090
+ };
1091
+ const validateBlockResourceLimits = async (transactions) => {
1092
+ if (transactions.length > MAX_BLOCK_TRANSACTIONS) {
1093
+ throw new Error(`block exceeds ${MAX_BLOCK_TRANSACTIONS} transaction protocol limit`);
1094
+ }
1095
+ let size = 0;
1096
+ for (const transaction of transactions)
1097
+ size += await validateTransactionResourceLimits(transaction);
1098
+ if (size > MAX_BLOCK_TRANSACTION_BYTES) {
1099
+ throw new Error(`block transactions exceed ${MAX_BLOCK_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1100
+ }
1101
+ return size;
1102
+ };
1033
1103
 
1034
1104
  class LittlePubSub {
1035
1105
  subscribers = new Map();
@@ -1211,7 +1281,7 @@ const runTask = async (id, taskName, input) => {
1211
1281
  });
1212
1282
  }
1213
1283
  };
1214
- const _executeTransaction = async (transaction, validators, feesEnabled) => {
1284
+ const _executeTransaction = async (transaction, validators, feesEnabled, burnBasisPoints) => {
1215
1285
  const hash = await new TransactionMessage(transaction).hash();
1216
1286
  if (latestTransactions.includes(hash)) {
1217
1287
  throw new Error(`double transaction found: ${hash}`);
@@ -1223,7 +1293,7 @@ const _executeTransaction = async (transaction, validators, feesEnabled) => {
1223
1293
  globalThis.state = await createState();
1224
1294
  if (feesEnabled) {
1225
1295
  const fee = BigInt(await calculateFee(transaction));
1226
- const { payments, burned } = distributeTransactionFee(fee, hash, validators);
1296
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
1227
1297
  await _.collectFee({ from, payments, burned });
1228
1298
  }
1229
1299
  await _.execute({ contract: to, method, params });
@@ -1327,6 +1397,17 @@ const _ = {
1327
1397
  }
1328
1398
  return total;
1329
1399
  },
1400
+ settleRewards: ({ rewards }) => {
1401
+ globalThis.msg = createMessage(contracts[nativeToken$2].creator, nativeToken$2);
1402
+ let minted = 0n;
1403
+ for (const [validator, rawAmount] of rewards) {
1404
+ const amount = BigInt(rawAmount);
1405
+ if (amount > 0n)
1406
+ contracts[nativeToken$2].mint(validator, amount);
1407
+ minted += amount;
1408
+ }
1409
+ return minted;
1410
+ },
1330
1411
  init: async (message) => {
1331
1412
  let { peerid, fromState, state, info } = message;
1332
1413
  if (info)
@@ -1438,8 +1519,20 @@ const _ = {
1438
1519
  return left < right ? -1 : left > right ? 1 : 0;
1439
1520
  });
1440
1521
  const feesEnabled = supportsTransactionFees(block.protocolVersion);
1441
- for (const transaction of transactions)
1442
- await _executeTransaction(transaction, validators, feesEnabled);
1522
+ const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
1523
+ if (monetaryPolicyEnabled)
1524
+ await validateBlockResourceLimits(transactions);
1525
+ const policy = monetaryPolicyEnabled
1526
+ ? calculateMonetaryPolicy(BigInt(contracts[nativeToken$2].totalSupply), BigInt(contracts[nativeToken$2].targetSupply))
1527
+ : { subsidy: 0n, burnBasisPoints: 1000n };
1528
+ for (const transaction of transactions) {
1529
+ await _executeTransaction(transaction, validators, feesEnabled, policy.burnBasisPoints);
1530
+ }
1531
+ if (monetaryPolicyEnabled && policy.subsidy > 0n) {
1532
+ _.settleRewards({
1533
+ rewards: [...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)]
1534
+ });
1535
+ }
1443
1536
  block.loaded = true;
1444
1537
  worker.postMessage({
1445
1538
  type: 'debug',
@@ -1507,6 +1600,9 @@ worker.onmessage(({ id, type, input }) => {
1507
1600
  case 'collectFee':
1508
1601
  runTask(id, 'collectFee', input);
1509
1602
  break;
1603
+ case 'settleRewards':
1604
+ runTask(id, 'settleRewards', input);
1605
+ break;
1510
1606
  case 'contracts':
1511
1607
  respond(id, contracts);
1512
1608
  break;
package/exports/chain.js CHANGED
@@ -2,8 +2,8 @@ import { createDebugger } from '@vandeurenglenn/debug';
2
2
  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
- import addresses, { contractFactory } from '@leofcoin/addresses';
6
- import { createTransactionHash, calculateFee, createContractMessage, signTransaction, distributeTransactionFee, supportsTransactionFees, aggregateValidatorFees, contractFactoryMessage, nativeTokenMessage, validatorsMessage, nameServiceMessage } from '@leofcoin/lib';
5
+ import addresses, { contractFactory, nativeToken } from '@leofcoin/addresses';
6
+ import { MAX_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTION_BYTES, MAX_BLOCK_TRANSACTIONS, createTransactionHash, calculateFee, createContractMessage, signTransaction, distributeTransactionFee, supportsTransactionFees, supportsMonetaryPolicy, validateBlockResourceLimits, calculateMonetaryPolicy, distributeAmount, 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';
@@ -78,19 +78,14 @@ class Transaction extends Protocol {
78
78
  return new Promise(async (resolve, reject) => {
79
79
  let size = 0;
80
80
  const _transactions = [];
81
- const MAX_BLOCK_TX_BYTES = 786432;
82
- await Promise.all(
83
- transactions.map(async (tx) => {
84
- tx = await new TransactionMessage(tx);
85
- const newSize = size + tx.encoded.length;
86
- if (newSize <= MAX_BLOCK_TX_BYTES) {
87
- size = newSize;
88
- _transactions.push({ ...tx.decoded, hash: await tx.hash() });
89
- } else {
90
- resolve(_transactions);
91
- }
92
- })
93
- );
81
+ for (const rawTransaction of transactions) {
82
+ const tx = await new TransactionMessage(rawTransaction);
83
+ if (tx.encoded.length > MAX_TRANSACTION_BYTES) continue;
84
+ const newSize = size + tx.encoded.length;
85
+ if (newSize > MAX_BLOCK_TRANSACTION_BYTES || _transactions.length >= MAX_BLOCK_TRANSACTIONS) break;
86
+ size = newSize;
87
+ _transactions.push({ ...tx.decoded, hash: await tx.hash() });
88
+ }
94
89
  return resolve(_transactions);
95
90
  });
96
91
  }
@@ -946,6 +941,9 @@ ${error.message}`);
946
941
  collectFee(from, payments, burned) {
947
942
  return this.#askWorker("collectFee", { from, payments, burned });
948
943
  }
944
+ settleRewards(rewards) {
945
+ return this.#askWorker("settleRewards", { rewards });
946
+ }
949
947
  get(contract, method, parameters) {
950
948
  return new Promise((resolve, reject) => {
951
949
  const id = randombytes(20).toString();
@@ -1789,12 +1787,12 @@ class State extends Contract {
1789
1787
  #loadBlockTransactions;
1790
1788
  #getLastTransactions;
1791
1789
  // todo throw error
1792
- async #_executeTransaction(transaction, validators, feesEnabled) {
1790
+ async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints) {
1793
1791
  try {
1794
1792
  const hash = await transaction.hash();
1795
1793
  if (feesEnabled) {
1796
1794
  const fee = BigInt(await calculateFee(transaction.decoded));
1797
- const { payments, burned } = distributeTransactionFee(fee, hash, validators);
1795
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
1798
1796
  await this.#machine.collectFee(transaction.decoded.from, payments, burned);
1799
1797
  }
1800
1798
  await this.#machine.execute(
@@ -1845,7 +1843,20 @@ class State extends Contract {
1845
1843
  });
1846
1844
  debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
1847
1845
  const feesEnabled = supportsTransactionFees(block.protocolVersion);
1848
- for (const transaction of transactions) await this.#_executeTransaction(transaction, validators, feesEnabled);
1846
+ const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
1847
+ if (monetaryPolicyEnabled) await validateBlockResourceLimits(transactions);
1848
+ const policy = monetaryPolicyEnabled ? calculateMonetaryPolicy(
1849
+ BigInt(await this.#machine.get(nativeToken, "totalSupply")),
1850
+ BigInt(await this.#machine.get(nativeToken, "targetSupply"))
1851
+ ) : { subsidy: 0n, burnBasisPoints: 1000n };
1852
+ for (const transaction of transactions) {
1853
+ await this.#_executeTransaction(transaction, validators, feesEnabled, policy.burnBasisPoints);
1854
+ }
1855
+ if (monetaryPolicyEnabled && policy.subsidy > 0n) {
1856
+ await this.#machine.settleRewards([
1857
+ ...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)
1858
+ ]);
1859
+ }
1849
1860
  this.#blocks[block.index].loaded = true;
1850
1861
  debug$1(`executed transactions for block ${block.index}`);
1851
1862
  if (Number(block.index) === 0) this.#loaded = true;
@@ -2289,7 +2300,6 @@ const validateChainLink = (localTip, incoming) => {
2289
2300
  return "append";
2290
2301
  };
2291
2302
 
2292
- const BLOCK_REWARD = 150n;
2293
2303
  const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validators) => {
2294
2304
  const actualValidators = validators.map(({ address }) => address);
2295
2305
  const canonicalExpected = [...new Set(expectedValidators)].sort();
@@ -2299,20 +2309,18 @@ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validator
2299
2309
  );
2300
2310
  }
2301
2311
  };
2302
- const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map()) => {
2312
+ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map(), subsidyRewards = /* @__PURE__ */ new Map(), expectedSubsidy = [...subsidyRewards.values()].reduce((sum, reward) => sum + reward, 0n)) => {
2303
2313
  if (block.validators.length === 0) {
2304
2314
  throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
2305
2315
  }
2306
- if (block.reward !== BLOCK_REWARD) {
2307
- throw new Error(`Block ${block.index} has invalid base reward: expected ${BLOCK_REWARD}, got ${block.reward}`);
2316
+ if (block.reward !== expectedSubsidy) {
2317
+ throw new Error(`Block ${block.index} has invalid base reward: expected ${expectedSubsidy}, got ${block.reward}`);
2308
2318
  }
2309
2319
  if (block.fees !== calculatedFees) {
2310
2320
  throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
2311
2321
  }
2312
- const validatorCount = BigInt(block.validators.length);
2313
- const baseReward = BLOCK_REWARD / validatorCount;
2314
2322
  for (const validator of block.validators) {
2315
- const expectedReward = baseReward + (validatorFees.get(validator.address) || 0n);
2323
+ const expectedReward = (subsidyRewards.get(validator.address) || 0n) + (validatorFees.get(validator.address) || 0n);
2316
2324
  if (validator.reward !== expectedReward) {
2317
2325
  throw new Error(
2318
2326
  `Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
@@ -2781,7 +2789,10 @@ class Chain extends VersionControl {
2781
2789
  })
2782
2790
  );
2783
2791
  const feesEnabled = supportsTransactionFees(blockMessage.decoded.protocolVersion);
2784
- if (feesEnabled) await this.#assertFeeBurnSupported();
2792
+ if (feesEnabled) await this.#assertFeeBurnSupported(blockMessage.decoded.protocolVersion);
2793
+ const adaptivePolicy = supportsMonetaryPolicy(blockMessage.decoded.protocolVersion);
2794
+ if (adaptivePolicy) await validateBlockResourceLimits(transactions);
2795
+ const policy = await this.#monetaryPolicy(blockMessage.decoded.protocolVersion);
2785
2796
  const calculatedFees = feesEnabled ? (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n) : 0n;
2786
2797
  const feeEntries = feesEnabled ? await Promise.all(
2787
2798
  transactions.map(async (transaction) => ({
@@ -2790,8 +2801,9 @@ class Chain extends VersionControl {
2790
2801
  }))
2791
2802
  ) : [];
2792
2803
  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);
2804
+ const validatorFees = feesEnabled ? aggregateValidatorFees(feeEntries, validatorAddresses, policy.burnBasisPoints) : new Map(validatorAddresses.map((address) => [address, 0n]));
2805
+ const subsidyRewards = adaptivePolicy ? distributeAmount(policy.subsidy, validatorAddresses, Number(blockMessage.decoded.index) % validatorAddresses.length) : new Map(validatorAddresses.map((address) => [address, policy.subsidy / BigInt(validatorAddresses.length)]));
2806
+ validateBlockEconomics(blockMessage.decoded, calculatedFees, validatorFees, subsidyRewards, policy.subsidy);
2795
2807
  if (feesEnabled) {
2796
2808
  const feesBySender = /* @__PURE__ */ new Map();
2797
2809
  for (let index = 0; index < transactions.length; index += 1) {
@@ -2809,13 +2821,24 @@ class Chain extends VersionControl {
2809
2821
  }
2810
2822
  return transactions;
2811
2823
  }
2812
- async #assertFeeBurnSupported() {
2824
+ async #assertFeeBurnSupported(protocolVersion) {
2813
2825
  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");
2826
+ const [canBurn, canMint] = await Promise.all([
2827
+ this.staticCall(addresses.nativeToken, "hasRole", [creator, "BURN"]),
2828
+ this.staticCall(addresses.nativeToken, "hasRole", [creator, "MINT"])
2829
+ ]);
2830
+ if (!canBurn || supportsMonetaryPolicy(protocolVersion) && !canMint) {
2831
+ throw new Error("native token genesis does not support protocol monetary policy");
2817
2832
  }
2818
2833
  }
2834
+ async #monetaryPolicy(protocolVersion) {
2835
+ if (!supportsMonetaryPolicy(protocolVersion)) return { subsidy: 150n, burnBasisPoints: 1000n, floorSupply: 0n };
2836
+ const [totalSupply, targetSupply] = await Promise.all([
2837
+ this.staticCall(addresses.nativeToken, "totalSupply"),
2838
+ this.staticCall(addresses.nativeToken, "targetSupply")
2839
+ ]);
2840
+ return calculateMonetaryPolicy(BigInt(totalSupply), BigInt(targetSupply));
2841
+ }
2819
2842
  /** Check if the next block will cross an epoch boundary (block-based timing) */
2820
2843
  #isEpochBoundary(blockHeight) {
2821
2844
  return (blockHeight + 1) % this.#epochLength === 0;
@@ -3338,14 +3361,19 @@ class Chain extends VersionControl {
3338
3361
  if (nonceDiff !== 0) return nonceDiff;
3339
3362
  return 0;
3340
3363
  });
3364
+ const monetaryPolicy = await this.#monetaryPolicy(blockMessage.decoded.protocolVersion);
3341
3365
  for (const transaction of allTransactions) {
3342
3366
  if (!contracts.includes(transaction.decoded.to)) contracts.push(transaction.decoded.to);
3343
3367
  this.removePendingNonce(transaction.decoded.from, transaction.decoded.nonce);
3344
3368
  const transactionHash = await transaction.hash();
3345
3369
  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 };
3370
+ const feeDistribution = supportsTransactionFees(blockMessage.decoded.protocolVersion) ? distributeTransactionFee(BigInt(await calculateFee(transaction.decoded)), transactionHash, validatorAddresses, monetaryPolicy.burnBasisPoints) : { payments: [], burned: 0n };
3347
3371
  await this.#handleTransaction(transaction, [], void 0, feeDistribution);
3348
3372
  }
3373
+ if (supportsMonetaryPolicy(blockMessage.decoded.protocolVersion) && monetaryPolicy.subsidy > 0n) {
3374
+ const validators = blockMessage.decoded.validators.map(({ address }) => address);
3375
+ await this.machine.settleRewards([...distributeAmount(monetaryPolicy.subsidy, validators, blockIndex % validators.length)]);
3376
+ }
3349
3377
  try {
3350
3378
  promises = await Promise.allSettled(promises);
3351
3379
  const noncesByAddress = {};
@@ -3445,7 +3473,7 @@ class Chain extends VersionControl {
3445
3473
  fees: BigInt(0),
3446
3474
  timestamp,
3447
3475
  previousHash: "",
3448
- reward: BLOCK_REWARD,
3476
+ reward: 0n,
3449
3477
  index: 0,
3450
3478
  producer: "",
3451
3479
  producerProof: "",
@@ -3467,9 +3495,17 @@ class Chain extends VersionControl {
3467
3495
  if (nonceDiff !== 0) return nonceDiff;
3468
3496
  return 0;
3469
3497
  });
3498
+ let blockTransactionBytes = 0;
3470
3499
  for (const { transaction, hash } of allTransactions) {
3500
+ const transactionBytes = transaction.encoded.length;
3501
+ if (transactionBytes > MAX_TRANSACTION_BYTES) {
3502
+ await globalThis.transactionPoolStore.delete(hash);
3503
+ continue;
3504
+ }
3505
+ if (block.transactions.length >= MAX_BLOCK_TRANSACTIONS || blockTransactionBytes + transactionBytes > MAX_BLOCK_TRANSACTION_BYTES) break;
3471
3506
  await this.validateTransactionSignature(transaction);
3472
3507
  block.transactions.push(hash);
3508
+ blockTransactionBytes += transactionBytes;
3473
3509
  if (supportsTransactionFees(this.version)) block.fees += BigInt(await calculateFee(transaction.decoded));
3474
3510
  await globalThis.peernet.put(hash, transaction.encoded, "transaction");
3475
3511
  }
@@ -3480,17 +3516,20 @@ class Chain extends VersionControl {
3480
3516
  const canonicalValidators = await this.staticCall(addresses.validators, "validators");
3481
3517
  const sortedValidators = [...canonicalValidators].sort();
3482
3518
  if (sortedValidators.length === 0) throw new Error("cannot produce a block without validators");
3483
- if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported();
3519
+ if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported(this.version);
3520
+ const monetaryPolicy = await this.#monetaryPolicy(this.version);
3521
+ block.reward = monetaryPolicy.subsidy;
3484
3522
  const feeEntries = supportsTransactionFees(this.version) ? await Promise.all(
3485
3523
  allTransactions.map(async ({ transaction, hash }) => ({
3486
3524
  fee: BigInt(await calculateFee(transaction.decoded)),
3487
3525
  transactionHash: hash
3488
3526
  }))
3489
3527
  ) : [];
3490
- const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators) : new Map(sortedValidators.map((address) => [address, 0n]));
3528
+ const validatorFees = supportsTransactionFees(this.version) ? aggregateValidatorFees(feeEntries, sortedValidators, monetaryPolicy.burnBasisPoints) : new Map(sortedValidators.map((address) => [address, 0n]));
3529
+ const subsidyRewards = supportsMonetaryPolicy(this.version) ? distributeAmount(monetaryPolicy.subsidy, sortedValidators, Number(block.index) % sortedValidators.length) : new Map(sortedValidators.map((address) => [address, monetaryPolicy.subsidy / BigInt(sortedValidators.length)]));
3491
3530
  block.validators = sortedValidators.map((validatorAddress) => ({
3492
3531
  address: validatorAddress,
3493
- reward: (validatorFees.get(validatorAddress) || 0n) + block.reward / BigInt(sortedValidators.length)
3532
+ reward: (validatorFees.get(validatorAddress) || 0n) + (subsidyRewards.get(validatorAddress) || 0n)
3494
3533
  }));
3495
3534
  try {
3496
3535
  block.producer = globalThis.peernet.selectedAccount || "";
@@ -977,9 +977,17 @@ var bytecodes = {
977
977
 
978
978
  const TRANSACTION_FEE_BYTES = 1024n;
979
979
  const TRANSACTION_FEE_UNIT = 10n;
980
- const FEE_BURN_BASIS_POINTS = 1000n;
980
+ const MAX_TRANSACTION_BYTES = 32 * 1024;
981
+ const MAX_BLOCK_TRANSACTION_BYTES = 128 * 1024;
982
+ const MAX_BLOCK_TRANSACTIONS = 256;
983
+ (BigInt(MAX_TRANSACTION_BYTES) / TRANSACTION_FEE_BYTES) * TRANSACTION_FEE_UNIT;
981
984
  const FEE_BASIS_POINTS = 10000n;
982
985
  const FEE_PROTOCOL_VERSION = '1.10.9';
986
+ const MONETARY_POLICY_PROTOCOL_VERSION = '1.10.10';
987
+ const BLOCKS_PER_YEAR = 5256000n;
988
+ const ANNUAL_ISSUANCE_BASIS_POINTS = 200n;
989
+ const SUPPLY_FLOOR_BASIS_POINTS = 9500n;
990
+ const MONETARY_FEE_BURN_BASIS_POINTS = 1000n;
983
991
  const parseProtocolVersion = (version) => {
984
992
  const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
985
993
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
@@ -995,19 +1003,61 @@ const supportsTransactionFees = (version) => {
995
1003
  }
996
1004
  return true;
997
1005
  };
1006
+ const supportsMonetaryPolicy = (version) => {
1007
+ const actual = parseProtocolVersion(version);
1008
+ const required = parseProtocolVersion(MONETARY_POLICY_PROTOCOL_VERSION);
1009
+ if (!actual)
1010
+ return false;
1011
+ for (let index = 0; index < required.length; index += 1) {
1012
+ if (actual[index] !== required[index])
1013
+ return actual[index] > required[index];
1014
+ }
1015
+ return true;
1016
+ };
1017
+ const calculateMonetaryPolicy = (totalSupply, targetSupply) => {
1018
+ if (totalSupply < 0n || targetSupply <= 0n)
1019
+ throw new Error('invalid monetary policy supply');
1020
+ const floorSupply = (targetSupply * SUPPLY_FLOOR_BASIS_POINTS) / FEE_BASIS_POINTS;
1021
+ if (totalSupply < floorSupply) {
1022
+ const annualIssuance = (targetSupply * ANNUAL_ISSUANCE_BASIS_POINTS) / FEE_BASIS_POINTS;
1023
+ const scheduled = annualIssuance / BLOCKS_PER_YEAR || 1n;
1024
+ return { subsidy: scheduled < floorSupply - totalSupply ? scheduled : floorSupply - totalSupply, burnBasisPoints: 0n, floorSupply };
1025
+ }
1026
+ return {
1027
+ subsidy: 0n,
1028
+ burnBasisPoints: totalSupply >= targetSupply ? MONETARY_FEE_BURN_BASIS_POINTS : 0n,
1029
+ floorSupply
1030
+ };
1031
+ };
1032
+ const distributeAmount = (amount, addresses, rotation = 0) => {
1033
+ const canonical = [...new Set(addresses)].sort();
1034
+ if (canonical.length === 0)
1035
+ throw new Error('cannot distribute without validators');
1036
+ const count = BigInt(canonical.length);
1037
+ const base = amount / count;
1038
+ const remainder = Number(amount % count);
1039
+ const result = new Map(canonical.map((address) => [address, base]));
1040
+ for (let index = 0; index < remainder; index += 1) {
1041
+ const address = canonical[(rotation + index) % canonical.length];
1042
+ result.set(address, result.get(address) + 1n);
1043
+ }
1044
+ return result;
1045
+ };
998
1046
  const feeRotationIndex = (transactionHash, validatorCount) => {
999
1047
  let value = 0n;
1000
1048
  for (const character of transactionHash)
1001
1049
  value = (value * 31n + BigInt(character.charCodeAt(0))) % 4294967291n;
1002
1050
  return Number(value % BigInt(validatorCount));
1003
1051
  };
1004
- const distributeTransactionFee = (fee, transactionHash, validatorAddresses) => {
1052
+ const distributeTransactionFee = (fee, transactionHash, validatorAddresses, burnBasisPoints = 1000n) => {
1005
1053
  const canonicalValidators = [...new Set(validatorAddresses)].sort();
1006
1054
  if (canonicalValidators.length === 0)
1007
1055
  throw new Error('cannot distribute transaction fee without validators');
1008
1056
  if (fee < 0n)
1009
1057
  throw new Error('transaction fee cannot be negative');
1010
- const burned = (fee * FEE_BURN_BASIS_POINTS) / FEE_BASIS_POINTS;
1058
+ if (burnBasisPoints < 0n || burnBasisPoints > FEE_BASIS_POINTS)
1059
+ throw new Error('invalid fee burn rate');
1060
+ const burned = (fee * burnBasisPoints) / FEE_BASIS_POINTS;
1011
1061
  const validatorPool = fee - burned;
1012
1062
  const count = BigInt(canonicalValidators.length);
1013
1063
  const base = validatorPool / count;
@@ -1030,6 +1080,26 @@ const calculateFee = async (transaction, format = false) => {
1030
1080
  const fee = units * TRANSACTION_FEE_UNIT;
1031
1081
  return format ? formatUnits(fee.toString()) : fee;
1032
1082
  };
1083
+ const validateTransactionResourceLimits = async (transaction) => {
1084
+ const message = await new TransactionMessage(transaction);
1085
+ const size = message.encoded.length;
1086
+ if (size > MAX_TRANSACTION_BYTES) {
1087
+ throw new Error(`transaction exceeds ${MAX_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1088
+ }
1089
+ return size;
1090
+ };
1091
+ const validateBlockResourceLimits = async (transactions) => {
1092
+ if (transactions.length > MAX_BLOCK_TRANSACTIONS) {
1093
+ throw new Error(`block exceeds ${MAX_BLOCK_TRANSACTIONS} transaction protocol limit`);
1094
+ }
1095
+ let size = 0;
1096
+ for (const transaction of transactions)
1097
+ size += await validateTransactionResourceLimits(transaction);
1098
+ if (size > MAX_BLOCK_TRANSACTION_BYTES) {
1099
+ throw new Error(`block transactions exceed ${MAX_BLOCK_TRANSACTION_BYTES} byte protocol limit: ${size}`);
1100
+ }
1101
+ return size;
1102
+ };
1033
1103
 
1034
1104
  class LittlePubSub {
1035
1105
  subscribers = new Map();
@@ -1211,7 +1281,7 @@ const runTask = async (id, taskName, input) => {
1211
1281
  });
1212
1282
  }
1213
1283
  };
1214
- const _executeTransaction = async (transaction, validators, feesEnabled) => {
1284
+ const _executeTransaction = async (transaction, validators, feesEnabled, burnBasisPoints) => {
1215
1285
  const hash = await new TransactionMessage(transaction).hash();
1216
1286
  if (latestTransactions.includes(hash)) {
1217
1287
  throw new Error(`double transaction found: ${hash}`);
@@ -1223,7 +1293,7 @@ const _executeTransaction = async (transaction, validators, feesEnabled) => {
1223
1293
  globalThis.state = await createState();
1224
1294
  if (feesEnabled) {
1225
1295
  const fee = BigInt(await calculateFee(transaction));
1226
- const { payments, burned } = distributeTransactionFee(fee, hash, validators);
1296
+ const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
1227
1297
  await _.collectFee({ from, payments, burned });
1228
1298
  }
1229
1299
  await _.execute({ contract: to, method, params });
@@ -1327,6 +1397,17 @@ const _ = {
1327
1397
  }
1328
1398
  return total;
1329
1399
  },
1400
+ settleRewards: ({ rewards }) => {
1401
+ globalThis.msg = createMessage(contracts[nativeToken$2].creator, nativeToken$2);
1402
+ let minted = 0n;
1403
+ for (const [validator, rawAmount] of rewards) {
1404
+ const amount = BigInt(rawAmount);
1405
+ if (amount > 0n)
1406
+ contracts[nativeToken$2].mint(validator, amount);
1407
+ minted += amount;
1408
+ }
1409
+ return minted;
1410
+ },
1330
1411
  init: async (message) => {
1331
1412
  let { peerid, fromState, state, info } = message;
1332
1413
  if (info)
@@ -1438,8 +1519,20 @@ const _ = {
1438
1519
  return left < right ? -1 : left > right ? 1 : 0;
1439
1520
  });
1440
1521
  const feesEnabled = supportsTransactionFees(block.protocolVersion);
1441
- for (const transaction of transactions)
1442
- await _executeTransaction(transaction, validators, feesEnabled);
1522
+ const monetaryPolicyEnabled = supportsMonetaryPolicy(block.protocolVersion);
1523
+ if (monetaryPolicyEnabled)
1524
+ await validateBlockResourceLimits(transactions);
1525
+ const policy = monetaryPolicyEnabled
1526
+ ? calculateMonetaryPolicy(BigInt(contracts[nativeToken$2].totalSupply), BigInt(contracts[nativeToken$2].targetSupply))
1527
+ : { subsidy: 0n, burnBasisPoints: 1000n };
1528
+ for (const transaction of transactions) {
1529
+ await _executeTransaction(transaction, validators, feesEnabled, policy.burnBasisPoints);
1530
+ }
1531
+ if (monetaryPolicyEnabled && policy.subsidy > 0n) {
1532
+ _.settleRewards({
1533
+ rewards: [...distributeAmount(policy.subsidy, validators, Number(block.index) % validators.length)]
1534
+ });
1535
+ }
1443
1536
  block.loaded = true;
1444
1537
  worker.postMessage({
1445
1538
  type: 'debug',
@@ -1507,6 +1600,9 @@ worker.onmessage(({ id, type, input }) => {
1507
1600
  case 'collectFee':
1508
1601
  runTask(id, 'collectFee', input);
1509
1602
  break;
1603
+ case 'settleRewards':
1604
+ runTask(id, 'settleRewards', input);
1605
+ break;
1510
1606
  case 'contracts':
1511
1607
  respond(id, contracts);
1512
1608
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/chain",
3
- "version": "1.10.9",
3
+ "version": "1.10.10",
4
4
  "description": "Official javascript implementation",
5
5
  "private": false,
6
6
  "exports": {