@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/browser/chain.js +146 -32
- package/exports/browser/workers/machine-worker.js +998 -12
- package/exports/chain.js +89 -29
- package/exports/workers/machine-worker.js +998 -12
- package/package.json +1 -1
package/exports/browser/chain.js
CHANGED
|
@@ -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
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
10675
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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);
|