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