@leofcoin/chain 1.10.8 → 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.
- package/exports/browser/chain.js +273 -50
- package/exports/browser/workers/machine-worker.js +1094 -12
- package/exports/chain.js +147 -48
- package/exports/workers/machine-worker.js +1094 -12
- package/package.json +1 -1
package/exports/browser/chain.js
CHANGED
|
@@ -1056,6 +1056,111 @@ 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 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;
|
|
1065
|
+
const FEE_BASIS_POINTS = 10000n;
|
|
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;
|
|
1072
|
+
const parseProtocolVersion = (version) => {
|
|
1073
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
1074
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
|
|
1075
|
+
};
|
|
1076
|
+
const supportsTransactionFees = (version) => {
|
|
1077
|
+
const actual = parseProtocolVersion(version);
|
|
1078
|
+
const required = parseProtocolVersion(FEE_PROTOCOL_VERSION);
|
|
1079
|
+
if (!actual)
|
|
1080
|
+
return false;
|
|
1081
|
+
for (let index = 0; index < required.length; index += 1) {
|
|
1082
|
+
if (actual[index] !== required[index])
|
|
1083
|
+
return actual[index] > required[index];
|
|
1084
|
+
}
|
|
1085
|
+
return true;
|
|
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
|
+
};
|
|
1127
|
+
const feeRotationIndex = (transactionHash, validatorCount) => {
|
|
1128
|
+
let value = 0n;
|
|
1129
|
+
for (const character of transactionHash)
|
|
1130
|
+
value = (value * 31n + BigInt(character.charCodeAt(0))) % 4294967291n;
|
|
1131
|
+
return Number(value % BigInt(validatorCount));
|
|
1132
|
+
};
|
|
1133
|
+
const distributeTransactionFee = (fee, transactionHash, validatorAddresses, burnBasisPoints = 1000n) => {
|
|
1134
|
+
const canonicalValidators = [...new Set(validatorAddresses)].sort();
|
|
1135
|
+
if (canonicalValidators.length === 0)
|
|
1136
|
+
throw new Error("cannot distribute transaction fee without validators");
|
|
1137
|
+
if (fee < 0n)
|
|
1138
|
+
throw new Error("transaction fee cannot be negative");
|
|
1139
|
+
if (burnBasisPoints < 0n || burnBasisPoints > FEE_BASIS_POINTS)
|
|
1140
|
+
throw new Error("invalid fee burn rate");
|
|
1141
|
+
const burned = fee * burnBasisPoints / FEE_BASIS_POINTS;
|
|
1142
|
+
const validatorPool = fee - burned;
|
|
1143
|
+
const count = BigInt(canonicalValidators.length);
|
|
1144
|
+
const base = validatorPool / count;
|
|
1145
|
+
const remainder = Number(validatorPool % count);
|
|
1146
|
+
const start = feeRotationIndex(transactionHash, canonicalValidators.length);
|
|
1147
|
+
const validatorFees = new Map(canonicalValidators.map((validator) => [validator, base]));
|
|
1148
|
+
for (let index = 0; index < remainder; index += 1) {
|
|
1149
|
+
const validator = canonicalValidators[(start + index) % canonicalValidators.length];
|
|
1150
|
+
validatorFees.set(validator, validatorFees.get(validator) + 1n);
|
|
1151
|
+
}
|
|
1152
|
+
const payments = [...validatorFees.entries()].filter(([, amount]) => amount > 0n).map(([to, amount]) => ({ to, amount }));
|
|
1153
|
+
return { burned, validatorFees, payments };
|
|
1154
|
+
};
|
|
1155
|
+
const aggregateValidatorFees = (fees, validatorAddresses, burnBasisPoints = 1000n) => {
|
|
1156
|
+
const totals = new Map([...new Set(validatorAddresses)].sort().map((validator) => [validator, 0n]));
|
|
1157
|
+
for (const entry of fees) {
|
|
1158
|
+
const { validatorFees } = distributeTransactionFee(entry.fee, entry.transactionHash, validatorAddresses, burnBasisPoints);
|
|
1159
|
+
for (const [validator, amount] of validatorFees)
|
|
1160
|
+
totals.set(validator, totals.get(validator) + amount);
|
|
1161
|
+
}
|
|
1162
|
+
return totals;
|
|
1163
|
+
};
|
|
1059
1164
|
const createContractMessage = async (creator, contract, constructorParameters = []) => {
|
|
1060
1165
|
return new ContractMessage({
|
|
1061
1166
|
creator,
|
|
@@ -1064,13 +1169,32 @@ const createContractMessage = async (creator, contract, constructorParameters =
|
|
|
1064
1169
|
});
|
|
1065
1170
|
};
|
|
1066
1171
|
const calculateFee = async (transaction, format = false) => {
|
|
1067
|
-
if (transaction.to === validators$2)
|
|
1068
|
-
return 0;
|
|
1069
1172
|
transaction = await new TransactionMessage(transaction);
|
|
1070
|
-
|
|
1071
|
-
|
|
1173
|
+
const encodedBytes = BigInt(transaction.encoded.length);
|
|
1174
|
+
const units = (encodedBytes + TRANSACTION_FEE_BYTES - 1n) / TRANSACTION_FEE_BYTES;
|
|
1175
|
+
const fee = units * TRANSACTION_FEE_UNIT;
|
|
1072
1176
|
return format ? formatUnits(fee.toString()) : fee;
|
|
1073
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
|
+
};
|
|
1074
1198
|
const createTransactionHash = async (transaction) => {
|
|
1075
1199
|
const isRawTransactionMessage = transaction instanceof RawTransactionMessage;
|
|
1076
1200
|
let message;
|
|
@@ -7845,19 +7969,14 @@ class Transaction extends Protocol {
|
|
|
7845
7969
|
return new Promise(async (resolve, reject) => {
|
|
7846
7970
|
let size = 0;
|
|
7847
7971
|
const _transactions = [];
|
|
7848
|
-
const
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
} else {
|
|
7857
|
-
resolve(_transactions);
|
|
7858
|
-
}
|
|
7859
|
-
})
|
|
7860
|
-
);
|
|
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
|
+
}
|
|
7861
7980
|
return resolve(_transactions);
|
|
7862
7981
|
});
|
|
7863
7982
|
}
|
|
@@ -8801,7 +8920,7 @@ class Machine {
|
|
|
8801
8920
|
* @param {Array} parameters
|
|
8802
8921
|
* @returns Promise<message>
|
|
8803
8922
|
*/
|
|
8804
|
-
async execute(contract, method, parameters) {
|
|
8923
|
+
async execute(contract, method, parameters, sender) {
|
|
8805
8924
|
try {
|
|
8806
8925
|
if (contract === contractFactory$2 && method === "registerContract") {
|
|
8807
8926
|
if (await this.has(parameters[0])) throw new Error(`duplicate contract @${parameters[0]}`);
|
|
@@ -8842,11 +8961,18 @@ ${error.message}`);
|
|
|
8842
8961
|
to: contract,
|
|
8843
8962
|
contract,
|
|
8844
8963
|
method,
|
|
8845
|
-
params: parameters
|
|
8964
|
+
params: parameters,
|
|
8965
|
+
sender
|
|
8846
8966
|
}
|
|
8847
8967
|
});
|
|
8848
8968
|
});
|
|
8849
8969
|
}
|
|
8970
|
+
collectFee(from, payments, burned) {
|
|
8971
|
+
return this.#askWorker("collectFee", { from, payments, burned });
|
|
8972
|
+
}
|
|
8973
|
+
settleRewards(rewards) {
|
|
8974
|
+
return this.#askWorker("settleRewards", { rewards });
|
|
8975
|
+
}
|
|
8850
8976
|
get(contract, method, parameters) {
|
|
8851
8977
|
return new Promise((resolve, reject) => {
|
|
8852
8978
|
const id = randombytes(20).toString();
|
|
@@ -9690,9 +9816,20 @@ class State extends Contract {
|
|
|
9690
9816
|
#loadBlockTransactions;
|
|
9691
9817
|
#getLastTransactions;
|
|
9692
9818
|
// todo throw error
|
|
9693
|
-
async #_executeTransaction(transaction) {
|
|
9819
|
+
async #_executeTransaction(transaction, validators, feesEnabled, burnBasisPoints) {
|
|
9694
9820
|
try {
|
|
9695
|
-
|
|
9821
|
+
const hash = await transaction.hash();
|
|
9822
|
+
if (feesEnabled) {
|
|
9823
|
+
const fee = BigInt(await calculateFee(transaction.decoded));
|
|
9824
|
+
const { payments, burned } = distributeTransactionFee(fee, hash, validators, burnBasisPoints);
|
|
9825
|
+
await this.#machine.collectFee(transaction.decoded.from, payments, burned);
|
|
9826
|
+
}
|
|
9827
|
+
await this.#machine.execute(
|
|
9828
|
+
transaction.decoded.to,
|
|
9829
|
+
transaction.decoded.method,
|
|
9830
|
+
transaction.decoded.params,
|
|
9831
|
+
transaction.decoded.from
|
|
9832
|
+
);
|
|
9696
9833
|
} catch (error) {
|
|
9697
9834
|
console.log(error);
|
|
9698
9835
|
await globalThis.transactionPoolStore.delete(await transaction.hash());
|
|
@@ -9721,23 +9858,34 @@ class State extends Contract {
|
|
|
9721
9858
|
try {
|
|
9722
9859
|
debug$1(`loading block: ${Number(block.index)} ${block.hash}`);
|
|
9723
9860
|
let transactions = await this.#loadBlockTransactions(block.transactions || []);
|
|
9861
|
+
const validators = block.validators.map(({ address }) => address);
|
|
9724
9862
|
debug$1(`loading transactions: ${transactions.length} for block ${block.index}`);
|
|
9725
|
-
let priority = [];
|
|
9726
9863
|
for (const transaction of transactions) {
|
|
9727
9864
|
const hash = await transaction.hash();
|
|
9728
|
-
if (transaction.decoded.priority) priority.push(transaction);
|
|
9729
9865
|
if (poolTransactionKeys.has(hash)) await globalThis.transactionPoolStore.delete(hash);
|
|
9730
9866
|
}
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
}
|
|
9738
|
-
transactions = transactions.filter((transaction) => !transaction.decoded.priority);
|
|
9867
|
+
transactions = transactions.sort((a, b) => {
|
|
9868
|
+
if (a.decoded.priority !== b.decoded.priority) return a.decoded.priority ? -1 : 1;
|
|
9869
|
+
const left = BigInt(a.decoded.nonce);
|
|
9870
|
+
const right = BigInt(b.decoded.nonce);
|
|
9871
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
9872
|
+
});
|
|
9739
9873
|
debug$1(`executing ${transactions.length} transactions for block ${block.index}`);
|
|
9740
|
-
|
|
9874
|
+
const feesEnabled = supportsTransactionFees(block.protocolVersion);
|
|
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
|
+
}
|
|
9741
9889
|
this.#blocks[block.index].loaded = true;
|
|
9742
9890
|
debug$1(`executed transactions for block ${block.index}`);
|
|
9743
9891
|
if (Number(block.index) === 0) this.#loaded = true;
|
|
@@ -10181,7 +10329,6 @@ const validateChainLink = (localTip, incoming) => {
|
|
|
10181
10329
|
return "append";
|
|
10182
10330
|
};
|
|
10183
10331
|
|
|
10184
|
-
const BLOCK_REWARD = 150n;
|
|
10185
10332
|
const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validators) => {
|
|
10186
10333
|
const actualValidators = validators.map(({ address }) => address);
|
|
10187
10334
|
const canonicalExpected = [...new Set(expectedValidators)].sort();
|
|
@@ -10191,19 +10338,18 @@ const validateCanonicalValidatorSet = (blockIndex, expectedValidators, validator
|
|
|
10191
10338
|
);
|
|
10192
10339
|
}
|
|
10193
10340
|
};
|
|
10194
|
-
const validateBlockEconomics = (block, calculatedFees) => {
|
|
10341
|
+
const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PURE__ */ new Map(), subsidyRewards = /* @__PURE__ */ new Map(), expectedSubsidy = [...subsidyRewards.values()].reduce((sum, reward) => sum + reward, 0n)) => {
|
|
10195
10342
|
if (block.validators.length === 0) {
|
|
10196
10343
|
throw new Error(`Block ${block.index} cannot distribute rewards without validators`);
|
|
10197
10344
|
}
|
|
10198
|
-
if (block.reward !==
|
|
10199
|
-
throw new Error(`Block ${block.index} has invalid base reward: expected ${
|
|
10345
|
+
if (block.reward !== expectedSubsidy) {
|
|
10346
|
+
throw new Error(`Block ${block.index} has invalid base reward: expected ${expectedSubsidy}, got ${block.reward}`);
|
|
10200
10347
|
}
|
|
10201
10348
|
if (block.fees !== calculatedFees) {
|
|
10202
10349
|
throw new Error(`Block ${block.index} has invalid fees: expected ${calculatedFees}, got ${block.fees}`);
|
|
10203
10350
|
}
|
|
10204
|
-
const validatorCount = BigInt(block.validators.length);
|
|
10205
|
-
const expectedReward = calculatedFees / validatorCount + BLOCK_REWARD / validatorCount;
|
|
10206
10351
|
for (const validator of block.validators) {
|
|
10352
|
+
const expectedReward = (subsidyRewards.get(validator.address) || 0n) + (validatorFees.get(validator.address) || 0n);
|
|
10207
10353
|
if (validator.reward !== expectedReward) {
|
|
10208
10354
|
throw new Error(
|
|
10209
10355
|
`Block ${block.index} has an invalid reward for validator ${validator.address}: expected ${expectedReward}, got ${validator.reward}`
|
|
@@ -10671,10 +10817,57 @@ class Chain extends VersionControl {
|
|
|
10671
10817
|
return transaction;
|
|
10672
10818
|
})
|
|
10673
10819
|
);
|
|
10674
|
-
const
|
|
10675
|
-
|
|
10820
|
+
const feesEnabled = supportsTransactionFees(blockMessage.decoded.protocolVersion);
|
|
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);
|
|
10825
|
+
const calculatedFees = feesEnabled ? (await Promise.all(transactions.map((transaction) => calculateFee(transaction.decoded)))).reduce((total, fee) => total + BigInt(fee), 0n) : 0n;
|
|
10826
|
+
const feeEntries = feesEnabled ? await Promise.all(
|
|
10827
|
+
transactions.map(async (transaction) => ({
|
|
10828
|
+
fee: BigInt(await calculateFee(transaction.decoded)),
|
|
10829
|
+
transactionHash: await transaction.hash()
|
|
10830
|
+
}))
|
|
10831
|
+
) : [];
|
|
10832
|
+
const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
|
|
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);
|
|
10836
|
+
if (feesEnabled) {
|
|
10837
|
+
const feesBySender = /* @__PURE__ */ new Map();
|
|
10838
|
+
for (let index = 0; index < transactions.length; index += 1) {
|
|
10839
|
+
const sender = transactions[index].decoded.from;
|
|
10840
|
+
feesBySender.set(sender, (feesBySender.get(sender) || 0n) + feeEntries[index].fee);
|
|
10841
|
+
}
|
|
10842
|
+
await Promise.all(
|
|
10843
|
+
[...feesBySender].map(async ([sender, required]) => {
|
|
10844
|
+
const balance = BigInt(await this.balanceOf(sender) || 0n);
|
|
10845
|
+
if (balance < required) {
|
|
10846
|
+
throw new Error(`insufficient balance for transaction fees from ${sender}: need ${required}, got ${balance}`);
|
|
10847
|
+
}
|
|
10848
|
+
})
|
|
10849
|
+
);
|
|
10850
|
+
}
|
|
10676
10851
|
return transactions;
|
|
10677
10852
|
}
|
|
10853
|
+
async #assertFeeBurnSupported(protocolVersion) {
|
|
10854
|
+
const creator = await this.staticCall(addresses.nativeToken, "creator");
|
|
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");
|
|
10861
|
+
}
|
|
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
|
+
}
|
|
10678
10871
|
/** Check if the next block will cross an epoch boundary (block-based timing) */
|
|
10679
10872
|
#isEpochBoundary(blockHeight) {
|
|
10680
10873
|
return (blockHeight + 1) % this.#epochLength === 0;
|
|
@@ -11123,9 +11316,12 @@ class Chain extends VersionControl {
|
|
|
11123
11316
|
async #versionHandler() {
|
|
11124
11317
|
return new globalThis.peernet.protos["peernet-response"]({ response: this.version });
|
|
11125
11318
|
}
|
|
11126
|
-
async #executeTransaction({ hash, from, to, method, params, nonce }) {
|
|
11319
|
+
async #executeTransaction({ hash, from, to, method, params, nonce, feePayments = { payments: [], burned: 0n } }) {
|
|
11127
11320
|
try {
|
|
11128
|
-
|
|
11321
|
+
if (feePayments.payments.length > 0 || feePayments.burned > 0n) {
|
|
11322
|
+
await this.machine.collectFee(from, feePayments.payments, feePayments.burned);
|
|
11323
|
+
}
|
|
11324
|
+
let result = await this.machine.execute(to, method, params, from);
|
|
11129
11325
|
globalThis.pubsub.publish(`transaction.completed.${hash}`, { status: "fulfilled", hash });
|
|
11130
11326
|
return result || "no state change";
|
|
11131
11327
|
} catch (error) {
|
|
@@ -11194,10 +11390,18 @@ class Chain extends VersionControl {
|
|
|
11194
11390
|
if (nonceDiff !== 0) return nonceDiff;
|
|
11195
11391
|
return 0;
|
|
11196
11392
|
});
|
|
11393
|
+
const monetaryPolicy = await this.#monetaryPolicy(blockMessage.decoded.protocolVersion);
|
|
11197
11394
|
for (const transaction of allTransactions) {
|
|
11198
11395
|
if (!contracts.includes(transaction.decoded.to)) contracts.push(transaction.decoded.to);
|
|
11199
11396
|
this.removePendingNonce(transaction.decoded.from, transaction.decoded.nonce);
|
|
11200
|
-
await
|
|
11397
|
+
const transactionHash = await transaction.hash();
|
|
11398
|
+
const validatorAddresses = blockMessage.decoded.validators.map(({ address }) => address);
|
|
11399
|
+
const feeDistribution = supportsTransactionFees(blockMessage.decoded.protocolVersion) ? distributeTransactionFee(BigInt(await calculateFee(transaction.decoded)), transactionHash, validatorAddresses, monetaryPolicy.burnBasisPoints) : { payments: [], burned: 0n };
|
|
11400
|
+
await this.#handleTransaction(transaction, [], void 0, feeDistribution);
|
|
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)]);
|
|
11201
11405
|
}
|
|
11202
11406
|
try {
|
|
11203
11407
|
promises = await Promise.allSettled(promises);
|
|
@@ -11254,7 +11458,7 @@ class Chain extends VersionControl {
|
|
|
11254
11458
|
if (await this.hasTransactionToHandle() && !this.#runningEpoch && this.#participating) await this.#runEpoch();
|
|
11255
11459
|
return true;
|
|
11256
11460
|
}
|
|
11257
|
-
async #handleTransaction(transaction, latestTransactions, block) {
|
|
11461
|
+
async #handleTransaction(transaction, latestTransactions, block, feePayments = { payments: [], burned: 0n }) {
|
|
11258
11462
|
await this.validateTransactionSignature(transaction);
|
|
11259
11463
|
const hash = await transaction.hash();
|
|
11260
11464
|
const doubleTransactions = [];
|
|
@@ -11267,7 +11471,7 @@ class Chain extends VersionControl {
|
|
|
11267
11471
|
return;
|
|
11268
11472
|
}
|
|
11269
11473
|
try {
|
|
11270
|
-
const result = await this.#executeTransaction({ ...transaction.decoded, hash });
|
|
11474
|
+
const result = await this.#executeTransaction({ ...transaction.decoded, hash, feePayments });
|
|
11271
11475
|
if (block) {
|
|
11272
11476
|
block.transactions.push(hash);
|
|
11273
11477
|
block.fees = block.fees += await calculateFee(transaction.decoded);
|
|
@@ -11298,7 +11502,7 @@ class Chain extends VersionControl {
|
|
|
11298
11502
|
fees: BigInt(0),
|
|
11299
11503
|
timestamp,
|
|
11300
11504
|
previousHash: "",
|
|
11301
|
-
reward:
|
|
11505
|
+
reward: 0n,
|
|
11302
11506
|
index: 0,
|
|
11303
11507
|
producer: "",
|
|
11304
11508
|
producerProof: "",
|
|
@@ -11320,10 +11524,18 @@ class Chain extends VersionControl {
|
|
|
11320
11524
|
if (nonceDiff !== 0) return nonceDiff;
|
|
11321
11525
|
return 0;
|
|
11322
11526
|
});
|
|
11527
|
+
let blockTransactionBytes = 0;
|
|
11323
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;
|
|
11324
11535
|
await this.validateTransactionSignature(transaction);
|
|
11325
11536
|
block.transactions.push(hash);
|
|
11326
|
-
|
|
11537
|
+
blockTransactionBytes += transactionBytes;
|
|
11538
|
+
if (supportsTransactionFees(this.version)) block.fees += BigInt(await calculateFee(transaction.decoded));
|
|
11327
11539
|
await globalThis.peernet.put(hash, transaction.encoded, "transaction");
|
|
11328
11540
|
}
|
|
11329
11541
|
if (block.transactions.length === 0) return;
|
|
@@ -11333,9 +11545,20 @@ class Chain extends VersionControl {
|
|
|
11333
11545
|
const canonicalValidators = await this.staticCall(addresses.validators, "validators");
|
|
11334
11546
|
const sortedValidators = [...canonicalValidators].sort();
|
|
11335
11547
|
if (sortedValidators.length === 0) throw new Error("cannot produce a block without validators");
|
|
11548
|
+
if (supportsTransactionFees(this.version)) await this.#assertFeeBurnSupported(this.version);
|
|
11549
|
+
const monetaryPolicy = await this.#monetaryPolicy(this.version);
|
|
11550
|
+
block.reward = monetaryPolicy.subsidy;
|
|
11551
|
+
const feeEntries = supportsTransactionFees(this.version) ? await Promise.all(
|
|
11552
|
+
allTransactions.map(async ({ transaction, hash }) => ({
|
|
11553
|
+
fee: BigInt(await calculateFee(transaction.decoded)),
|
|
11554
|
+
transactionHash: hash
|
|
11555
|
+
}))
|
|
11556
|
+
) : [];
|
|
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)]));
|
|
11336
11559
|
block.validators = sortedValidators.map((validatorAddress) => ({
|
|
11337
11560
|
address: validatorAddress,
|
|
11338
|
-
reward:
|
|
11561
|
+
reward: (validatorFees.get(validatorAddress) || 0n) + (subsidyRewards.get(validatorAddress) || 0n)
|
|
11339
11562
|
}));
|
|
11340
11563
|
try {
|
|
11341
11564
|
block.producer = globalThis.peernet.selectedAccount || "";
|
|
@@ -11460,7 +11683,7 @@ class Chain extends VersionControl {
|
|
|
11460
11683
|
* @returns
|
|
11461
11684
|
*/
|
|
11462
11685
|
internalCall(sender, contract, method, parameters) {
|
|
11463
|
-
return this.machine.execute(contract, method, parameters);
|
|
11686
|
+
return this.machine.execute(contract, method, parameters, sender);
|
|
11464
11687
|
}
|
|
11465
11688
|
/**
|
|
11466
11689
|
*
|
|
@@ -11470,7 +11693,7 @@ class Chain extends VersionControl {
|
|
|
11470
11693
|
* @returns
|
|
11471
11694
|
*/
|
|
11472
11695
|
call(contract, method, parameters) {
|
|
11473
|
-
return this.machine.execute(contract, method, parameters);
|
|
11696
|
+
return this.machine.execute(contract, method, parameters, globalThis.peernet.selectedAccount);
|
|
11474
11697
|
}
|
|
11475
11698
|
staticCall(contract, method, parameters) {
|
|
11476
11699
|
return this.machine.get(contract, method, parameters);
|