Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1784 from LiskHQ/1783-transaction-minimum-fee-is-…
Browse files Browse the repository at this point in the history
…wrongly-calculated

Transaction minimum fee is wrongly calculated
  • Loading branch information
sameersubudhi authored Aug 10, 2023
2 parents 01746c6 + 064cced commit 2c059c0
Show file tree
Hide file tree
Showing 19 changed files with 313 additions and 63 deletions.
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3.3"
version: '3.3'
services:

nats:
Expand Down Expand Up @@ -458,7 +458,6 @@ services:
networks:
services_network:


volumes:
mysql-primary-data:
redis-data:
Expand Down
3 changes: 1 addition & 2 deletions jenkins/mysql-with-replication/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3.3"
version: '3.3'
services:

mysql-primary:
Expand Down Expand Up @@ -77,6 +77,5 @@ services:
networks:
services_network:


volumes:
mysql-primary-data:
2 changes: 1 addition & 1 deletion jenkins/redis/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3.3"
version: '3.3'
services:

redis_service_dev:
Expand Down
2 changes: 1 addition & 1 deletion services/blockchain-app-registry/jobs/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = [
await syncWithRemoteRepo();
logger.info('Database has been successfully synchronized.');
} catch (err) {
logger.warn(`Refreshing blockchain application metadata failed due to: ${err.message}`);
logger.warn(`Refreshing blockchain application metadata failed due to: ${err.message}.`);
}
},
},
Expand Down
6 changes: 6 additions & 0 deletions services/blockchain-connector/methods/interoperability.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
getChainAccount,
getMainchainID,
getChannel,
getChainRegistrationFee,
} = require('../shared/sdk');

const regex = require('../shared/utils/regex');
Expand All @@ -41,4 +42,9 @@ module.exports = [
chainID: { optional: false, type: 'string', pattern: regex.CHAIN_ID },
},
},
{
name: 'getChainRegistrationFee',
controller: async () => getChainRegistrationFee(),
params: {},
},
];
2 changes: 2 additions & 0 deletions services/blockchain-connector/shared/sdk/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const {
getChainAccount,
getMainchainID,
getChannel,
getRegistrationFee,
} = require('./interoperability');

const { getLegacyAccount } = require('./legacy');
Expand Down Expand Up @@ -210,6 +211,7 @@ module.exports = {
getChainAccount,
getMainchainID,
getChannel,
getChainRegistrationFee: getRegistrationFee,

// Legacy
getLegacyAccount,
Expand Down
17 changes: 17 additions & 0 deletions services/blockchain-connector/shared/sdk/interoperability.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const { getNodeInfo } = require('./endpoints_1');
const logger = Logger();

let mainchainID;
let registrationFee;

const getChainAccount = async (chainID) => {
try {
Expand Down Expand Up @@ -72,8 +73,24 @@ const getChannel = async (chainID) => {
}
};

const getRegistrationFee = async () => {
try {
if (!registrationFee) {
registrationFee = await invokeEndpoint('interoperability_getRegistrationFee');
}
return registrationFee;
} catch (err) {
if (err.message.includes(timeoutMessage)) {
throw new TimeoutException('Request timed out when calling \'getRegistrationFee\'.');
}
logger.warn(`Error returned when invoking 'interoperability_getRegistrationFee'.\n${err.stack}`);
throw err;
}
};

module.exports = {
getChainAccount,
getMainchainID,
getChannel,
getRegistrationFee,
};
8 changes: 8 additions & 0 deletions services/blockchain-indexer/shared/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ const COMMAND = Object.freeze({
CHANGE_COMMISSION: 'changeCommission',
TRANSFER: 'transfer',
TRANSFER_CROSS_CHAIN: 'transferCrossChain',
REGISTER_SIDECHAIN: 'registerSidechain',
REGISTER_MAINCHAIN: 'registerMainchain',
});

const LENGTH_CHAIN_ID = 4 * 2; // Each byte is represented with 2 nibbles
Expand All @@ -147,6 +149,9 @@ const PATTERN_ANY_TOKEN_ID = '*';
const PATTERN_ANY_CHAIN_TOKEN_ID = '*'.repeat(LENGTH_TOKEN_LOCAL_ID);
const LENGTH_TOKEN_ID = LENGTH_CHAIN_ID + LENGTH_TOKEN_LOCAL_ID;
const LENGTH_NETWORK_ID = 1 * 2; // Each byte is represented with 2 nibbles
const LENGTH_BYTE_SIGNATURE = 64;
const LENGTH_BYTE_ID = 32;
const DEFAULT_NUM_OF_SIGNATURES = 1;

const MAX_COMMISSION = BigInt('10000');

Expand Down Expand Up @@ -205,4 +210,7 @@ module.exports = {
KV_STORE_KEY,
TRANSACTION_STATUS,
TRANSACTION_VERIFY_RESULT,
LENGTH_BYTE_SIGNATURE,
LENGTH_BYTE_ID,
DEFAULT_NUM_OF_SIGNATURES,
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
* Removal or modification of this copyright notice is prohibited.
*
*/
const { requestConnector } = require('../../../utils/request');

let moduleConstants;

const APP_STATUS = {
ACTIVE: 'active',
REGISTERED: 'registered',
Expand All @@ -26,7 +30,23 @@ const CHAIN_STATUS = Object.freeze({
2: 'terminated',
});

const getInteroperabilityConstants = async () => {
if (typeof moduleConstants === 'undefined') {
const registrationFee = await requestConnector('getChainRegistrationFee');

moduleConstants = {
chainRegistrationFee: registrationFee.fee,
};
}

return {
data: moduleConstants,
meta: {},
};
};

module.exports = {
APP_STATUS,
CHAIN_STATUS,
getInteroperabilityConstants,
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,34 @@ const {
const {
HTTP,
Exceptions: { ValidationException },
Logger,
} = require('lisk-service-framework');

const { getAuthAccountInfo } = require('./auth');
const { resolveMainchainServiceURL, resolveChannelInfo } = require('./mainchain');
const { dryRunTransactions } = require('./transactionsDryRun');
const { tokenHasUserAccount, getTokenConstants } = require('./token');

const { MODULE, COMMAND, EVENT } = require('../../constants');
const {
MODULE,
COMMAND,
EVENT,
LENGTH_BYTE_SIGNATURE,
LENGTH_BYTE_ID,
DEFAULT_NUM_OF_SIGNATURES,
} = require('../../constants');

const { getLisk32AddressFromPublicKey } = require('../../utils/account');
const { parseToJSONCompatObj } = require('../../utils/parser');
const { requestConnector } = require('../../utils/request');
const config = require('../../../config');

const { getPosConstants } = require('./pos/constants');
const { getInteroperabilityConstants } = require('./interoperability/constants');
const { getFeeEstimates } = require('./feeEstimates');
const regex = require('../../regex');

const SIZE_BYTE_SIGNATURE = 64;
const SIZE_BYTE_ID = 32;
const DEFAULT_NUM_OF_SIGNATURES = 1;
const logger = Logger();

const { bufferBytesLength: BUFFER_BYTES_LENGTH } = config.estimateFees;

const OPTIONAL_TRANSACTION_PROPERTIES = Object.freeze({
Expand All @@ -53,11 +60,11 @@ const OPTIONAL_TRANSACTION_PROPERTIES = Object.freeze({
propName: 'signatures',
defaultValue: (params) => new Array(params.numberOfSignatures)
.fill()
.map(() => getRandomBytes(SIZE_BYTE_SIGNATURE).toString('hex')),
.map(() => getRandomBytes(LENGTH_BYTE_SIGNATURE).toString('hex')),
},
ID: {
propName: 'id',
defaultValue: () => getRandomBytes(SIZE_BYTE_ID).toString('hex'),
defaultValue: () => getRandomBytes(LENGTH_BYTE_ID).toString('hex'),
},
});

Expand All @@ -84,36 +91,58 @@ const mockOptionalProperties = (inputObject, inputObjectOptionalProps, additiona
return inputObject;
};

const mockTransaction = async (_transaction, authAccountInfo) => {
const transaction = _.cloneDeep(_transaction);
const filterOptionalProps = (inputObject, optionalProps) => _.omit(inputObject, optionalProps);

const mockTransaction = async (_transaction, numberOfSignatures) => {
if (!_transaction) throw new Error('No transaction passed.');

const numberOfSignatures = (authAccountInfo.mandatoryKeys.length
+ authAccountInfo.optionalKeys.length) || DEFAULT_NUM_OF_SIGNATURES;
const transactionCopy = _.cloneDeep(_transaction);
const txWithRequiredProps = filterOptionalProps(
transactionCopy,
Object.values(OPTIONAL_TRANSACTION_PROPERTIES).map(optionalProp => optionalProp.propName),
);

const mockedTransaction = mockOptionalProperties(
transaction,
txWithRequiredProps,
OPTIONAL_TRANSACTION_PROPERTIES,
{ numberOfSignatures },
);

const channelInfo = transaction.module === MODULE.TOKEN
&& transaction.command === COMMAND.TRANSFER_CROSS_CHAIN
? await resolveChannelInfo(transaction.params.receivingChainID)
const channelInfo = txWithRequiredProps.module === MODULE.TOKEN
&& txWithRequiredProps.command === COMMAND.TRANSFER_CROSS_CHAIN
? await resolveChannelInfo(txWithRequiredProps.params.receivingChainID)
: {};

const { messageFeeTokenID } = channelInfo;

const mockedTransactionParams = messageFeeTokenID
? mockOptionalProperties(
transaction.params,
filterOptionalProps(
txWithRequiredProps.params,
Object
.values(OPTIONAL_TRANSACTION_PARAMS_PROPERTIES)
.map(optionalProp => optionalProp.propName),
),
OPTIONAL_TRANSACTION_PARAMS_PROPERTIES,
{ messageFeeTokenID },
)
: transaction.params;
: txWithRequiredProps.params;

return { ...mockedTransaction, params: mockedTransactionParams };
};

const getNumberOfSignatures = async (address) => {
try {
const authAccountInfo = await requestConnector('getAuthAccount', { address });
const numberOfSignatures = (authAccountInfo.mandatoryKeys.length
+ authAccountInfo.optionalKeys.length) || DEFAULT_NUM_OF_SIGNATURES;
return numberOfSignatures;
} catch (error) {
logger.warn(`Error while retrieving auth information for the account ${address}.`);
return DEFAULT_NUM_OF_SIGNATURES;
}
};

const getCcmBuffer = async (transaction) => {
if (transaction.module !== MODULE.TOKEN
|| transaction.command !== COMMAND.TRANSFER_CROSS_CHAIN) return null;
Expand Down Expand Up @@ -202,6 +231,14 @@ const calcAdditionalFees = async (transaction) => {
};
additionalFees.total += BigInt(posConstants.data.validatorRegistrationFee);
}
} else if (transaction.module === MODULE.INTEROPERABILITY) {
if ([COMMAND.REGISTER_MAINCHAIN, COMMAND.REGISTER_SIDECHAIN].includes(transaction.command)) {
const interoperabilityConstants = await getInteroperabilityConstants();
additionalFees.fee = {
chainRegistrationFee: interoperabilityConstants.data.chainRegistrationFee,
};
additionalFees.total += BigInt(interoperabilityConstants.data.chainRegistrationFee);
}
}

return additionalFees;
Expand All @@ -226,9 +263,9 @@ const estimateTransactionFees = async params => {
}

const senderAddress = getLisk32AddressFromPublicKey(params.transaction.senderPublicKey);
const { data: authAccountInfo } = await getAuthAccountInfo({ address: senderAddress });
const numberOfSignatures = await getNumberOfSignatures(senderAddress);

const trxWithMockProps = await mockTransaction(params.transaction, authAccountInfo);
const trxWithMockProps = await mockTransaction(params.transaction, numberOfSignatures);
const additionalFees = await calcAdditionalFees(trxWithMockProps);
let formattedTransaction = await requestConnector(
'formatTransaction',
Expand Down Expand Up @@ -293,18 +330,9 @@ const estimateTransactionFees = async params => {

const { minFee, size } = formattedTransaction;

const estimateMinFee = (() => {
const totalAdditionalFees = Object.entries(additionalFees.fee).reduce((acc, [k, v]) => {
if (k.toLowerCase().endsWith('fee')) {
return acc + BigInt(v);
}
return acc;
}, BigInt(0));

// TODO: Remove BUFFER_BYTES_LENGTH support after https://github.com/LiskHQ/lisk-service/issues/1604 is complete
return BigInt(minFee) + totalAdditionalFees
+ BigInt(BUFFER_BYTES_LENGTH * feeEstimatePerByte.minFeePerByte);
})();
// TODO: Remove BUFFER_BYTES_LENGTH support after RC is tagged
const estimatedMinFee = BigInt(minFee)
+ BigInt(BUFFER_BYTES_LENGTH * feeEstimatePerByte.minFeePerByte);

// Populate the response with transaction minimum fee information
estimateTransactionFeesRes.data = {
Expand All @@ -313,7 +341,7 @@ const estimateTransactionFees = async params => {
...estimateTransactionFeesRes.data.transaction,
fee: {
tokenID: feeEstimatePerByte.feeTokenID,
minimum: estimateMinFee.toString(),
minimum: estimatedMinFee.toString(),
},
},
};
Expand All @@ -335,11 +363,11 @@ const estimateTransactionFees = async params => {
},
};

// Add priority only when fee values are not 0
// Add priority only when the priority fee values are non-zero
const { low, med, high } = feeEstimatePerByte;
if (low !== 0 || med !== 0 || high !== 0) {
const dynamicFeeEstimates = calcDynamicFeeEstimates(
feeEstimatePerByte, estimateMinFee, size);
feeEstimatePerByte, estimatedMinFee, size);

estimateTransactionFeesRes.transaction.fee.priority = dynamicFeeEstimates;
}
Expand All @@ -354,4 +382,6 @@ module.exports = {
calcDynamicFeeEstimates,
mockTransaction,
calcAdditionalFees,
filterOptionalProps,
getNumberOfSignatures,
};
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const computeValidatorStatus = async () => {

const MIN_ELIGIBLE_VOTE_WEIGHT = Transactions.convertLSKToBeddows('1000');

const latestBlock = getLastBlock();
const latestBlock = await getLastBlock();
const generatorsList = await business.getGenerators();

const generatorMap = new Map(generatorsList.map(generator => [generator.address, generator]));
Expand Down
Loading

0 comments on commit 2c059c0

Please sign in to comment.