From 6d566ecdb9296b37e8fa018dc570246db09713ae Mon Sep 17 00:00:00 2001 From: jairajdev Date: Fri, 2 Aug 2024 11:03:38 +0545 Subject: [PATCH 1/5] Added composite indexes ( cycle and timesamp ) on each data and added debug logs for account query Adding global-txreceipt-verification Added global tx receipt validation and verification Added account verification for global tx receipt Updated the new poqo receipt change --- src/API.ts | 2 - src/Data/Collector.ts | 175 +++++++++++++++++++++++--- src/State.ts | 6 +- src/dbstore/index.ts | 1 + src/dbstore/receipts.ts | 3 +- src/primary-process/index.ts | 2 +- src/shardeum/calculateAccountHash.ts | 24 +++- src/shardeum/verifyAppReceiptData.ts | 36 ++++-- src/shardeum/verifyGlobalTxReceipt.ts | 121 ++++++++++++++++++ 9 files changed, 327 insertions(+), 43 deletions(-) create mode 100644 src/shardeum/verifyGlobalTxReceipt.ts diff --git a/src/API.ts b/src/API.ts index 73f5da74..ae42efe3 100644 --- a/src/API.ts +++ b/src/API.ts @@ -145,8 +145,6 @@ export function registerRoutes(server: FastifyInstance => { const result = { success: false } // Check the signed nodes are part of the execution group nodes of the tx - const { executionShardKey, cycle, signedReceipt, globalModification } = receipt - if (globalModification && config.skipGlobalTxReceiptVerification) return { success: true } - const { signaturePack } = signedReceipt + const { executionShardKey, cycle, globalModification } = receipt const { txId, timestamp } = receipt.tx if (config.VERBOSE) { const currentTimestamp = Date.now() @@ -432,6 +471,76 @@ export const verifyReceiptData = async ( } // Determine the home partition index of the primary account (executionShardKey) const { homePartition } = ShardFunction.addressToPartition(cycleShardData.shardGlobals, executionShardKey) + if (globalModification) { + const appliedReceipt = receipt.signedReceipt as P2PTypes.GlobalAccountsTypes.GlobalTxReceipt + if (config.skipGlobalTxReceiptVerification) return { success: true } + else { + const { signs } = appliedReceipt + // Refer to https://github.com/shardeum/shardus-core/blob/7d8877b7e1a5b18140f898a64b932182d8a35298/src/p2p/GlobalAccounts.ts#L397 + let votingGroupCount = cycleShardData.shardGlobals.nodesPerConsenusGroup + if (votingGroupCount > cycleShardData.nodes.length) { + votingGroupCount = cycleShardData.nodes.length + } + let isReceiptMajority = (signs.length / votingGroupCount) * 100 >= 60 + if (!isReceiptMajority) { + Logger.mainLogger.error( + `Invalid receipt globalModification signs count is less than 60% of the votingGroupCount, ${signs.length}, ${votingGroupCount}` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'Invalid_receipt_globalModification_signs_count_less_than_60%' + ) + return result + } + // Using a set to store the unique signers to avoid duplicates + const uniqueSigners = new Set() + for (const sign of signs) { + const { owner: nodePubKey } = sign + // Get the node id from the public key + const node = cycleShardData.nodes.find((node) => node.publicKey === nodePubKey) + if (node == null) { + Logger.mainLogger.error( + `The node with public key ${nodePubKey} of the receipt ${txId} with ${timestamp} is not in the active nodesList of cycle ${cycle}` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'globalModification_sign_owner_not_in_active_nodesList' + ) + continue + } + // Check if the node is in the execution group + if (!cycleShardData.parititionShardDataMap.get(homePartition).coveredBy[node.id]) { + Logger.mainLogger.error( + `The node with public key ${nodePubKey} of the receipt ${txId} with ${timestamp} is not in the execution group of the tx` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'globalModification_sign_node_not_in_execution_group_of_tx' + ) + continue + } + uniqueSigners.add(nodePubKey) + } + isReceiptMajority = (uniqueSigners.size / votingGroupCount) * 100 >= 60 + if (isReceiptMajority) { + Logger.mainLogger.error( + `Invalid receipt globalModification valid signs count is less than votingGroupCount ${uniqueSigners.size}, ${votingGroupCount}` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'Invalid_receipt_globalModification_valid_signs_count_less_than_votingGroupCount' + ) + return result + } + const requiredSignatures = Math.floor(votingGroupCount * (60 / 100)) + return { success: true, requiredSignatures } + } + } + const { signaturePack } = receipt.signedReceipt as Receipt.SignedReceipt if (config.newPOQReceipt === false) { // Refer to https://github.com/shardeum/shardus-core/blob/f7000c36faa0cd1e0832aa1e5e3b1414d32dcf66/src/state-manager/TransactionConsensus.ts#L1406 let votingGroupCount = cycleShardData.shardGlobals.nodesPerConsenusGroup @@ -497,7 +606,8 @@ export const verifyReceiptData = async ( } return { success: true, requiredSignatures } } - // const { confirmOrChallenge } = appliedReceipt + + // const { confirmOrChallenge } = appliedReceipt as Receipt.AppliedReceipt2 // // Check if the appliedVote node is in the execution group // if (!cycleShardData.nodeShardDataMap.has(appliedVote.node_id)) { // Logger.mainLogger.error('Invalid receipt appliedReceipt appliedVote node is not in the active nodesList') @@ -612,10 +722,34 @@ const verifyAppliedReceiptSignatures = ( nestedCounterMessages = [] ): { success: boolean } => { const result = { success: false, failedReasons, nestedCounterMessages } - const { signedReceipt, globalModification } = receipt - if (globalModification && config.skipGlobalTxReceiptVerification) return { success: true } - const { proposal, signaturePack, voteOffsets } = signedReceipt + const { globalModification } = receipt const { txId: txid } = receipt.tx + if (globalModification) { + const appliedReceipt = receipt.signedReceipt as P2PTypes.GlobalAccountsTypes.GlobalTxReceipt + // Refer to https://github.com/shardeum/shardus-core/blob/7d8877b7e1a5b18140f898a64b932182d8a35298/src/p2p/GlobalAccounts.ts#L294 + + const { signs, tx } = appliedReceipt + // Using a map to store the good signatures to avoid duplicates + const goodSignatures = new Map() + for (const sign of signs) { + if (Crypto.verify({ ...tx, sign: sign })) { + goodSignatures.set(sign.owner, sign) + // Break the loop if the required number of good signatures are found + if (goodSignatures.size >= requiredSignatures) break + } + } + if (goodSignatures.size < requiredSignatures) { + failedReasons.push( + `Invalid receipt globalModification valid signs count is less than requiredSignatures ${txid}, ${goodSignatures.size}, ${requiredSignatures}` + ) + nestedCounterMessages.push( + 'Invalid_receipt_globalModification_valid_signs_count_less_than_requiredSignatures' + ) + return result + } + return { success: true } + } + const { proposal, signaturePack, voteOffsets } = receipt.signedReceipt as Receipt.SignedReceipt // Refer to https://github.com/shardeum/shardus-core/blob/50b6d00f53a35996cd69210ea817bee068a893d6/src/state-manager/TransactionConsensus.ts#L2799 const voteHash = calculateVoteHash(proposal, failedReasons, nestedCounterMessages) // Refer to https://github.com/shardeum/shardus-core/blob/50b6d00f53a35996cd69210ea817bee068a893d6/src/state-manager/TransactionConsensus.ts#L2663 @@ -639,7 +773,7 @@ const verifyAppliedReceiptSignatures = ( } if (goodSignatures.size < requiredSignatures) { failedReasons.push( - `Invalid receipt signedReceipt valid signatures count is less than requiredSignatures ${goodSignatures.size}, ${requiredSignatures}` + `Invalid receipt signedReceipt valid signatures count is less than requiredSignatures ${txid}, ${goodSignatures.size}, ${requiredSignatures}` ) nestedCounterMessages.push( 'Invalid_receipt_signedReceipt_valid_signatures_count_less_than_requiredSignatures' @@ -869,16 +1003,17 @@ export const storeReceiptData = async ( // receiptId: tx.txId, // timestamp: tx.timestamp, // }) - const { afterStates, cycle, tx, appReceiptData, signedReceipt } = receipt - receipt.beforeStates = config.storeReceiptBeforeStates ? receipt.beforeStates : [] - - const sortedVoteOffsets = (signedReceipt.voteOffsets ?? []).sort() + const { afterStates, cycle, tx, appReceiptData, signedReceipt, globalModification } = receipt + const sortedVoteOffsets = globalModification + ? [] + : (signedReceipt as Receipt.SignedReceipt).voteOffsets.sort() const medianOffset = sortedVoteOffsets[Math.floor(sortedVoteOffsets.length / 2)] ?? 0 const applyTimestamp = tx.timestamp + medianOffset * 1000 if (config.VERBOSE) console.log('RECEIPT', 'Save', txId, timestamp, senderInfo) processedReceiptsMap.set(tx.txId, tx.timestamp) receiptsInValidationMap.delete(tx.txId) if (missingReceiptsMap.has(tx.txId)) missingReceiptsMap.delete(tx.txId) + receipt.beforeStates = globalModification || config.storeReceiptBeforeStates ? receipt.beforeStates : [] // Store beforeStates for globalModification tx, or if config.storeReceiptBeforeStates is true combineReceipts.push({ ...receipt, receiptId: tx.txId, diff --git a/src/State.ts b/src/State.ts index 88f50b46..59c204bf 100644 --- a/src/State.ts +++ b/src/State.ts @@ -40,7 +40,11 @@ export let isFirst = false export let isActive = false export const archiversReputation: Map = new Map() -export async function initFromConfig(config: Config, shutDownMode = false, useArchiverDiscovery = true): Promise { +export async function initFromConfig( + config: Config, + shutDownMode = false, + useArchiverDiscovery = true +): Promise { // Get own nodeInfo from config nodeState.ip = config.ARCHIVER_IP nodeState.port = config.ARCHIVER_PORT diff --git a/src/dbstore/index.ts b/src/dbstore/index.ts index 1ba69c5b..52a83790 100644 --- a/src/dbstore/index.ts +++ b/src/dbstore/index.ts @@ -73,6 +73,7 @@ export const initializeDB = async (config: Config): Promise => { receiptDatabase, 'CREATE INDEX if not exists `receipts_timestamp` ON `receipts` (`timestamp` ASC)' ) + await runCreate(receiptDatabase, 'CREATE INDEX if not exists `receipts_cycle` ON `receipts` (`cycle` ASC)') await runCreate( receiptDatabase, 'CREATE INDEX if not exists `receipts_cycle_timestamp` ON `receipts` (`cycle` ASC, `timestamp` ASC)' diff --git a/src/dbstore/receipts.ts b/src/dbstore/receipts.ts index 65591ffe..eb54d516 100644 --- a/src/dbstore/receipts.ts +++ b/src/dbstore/receipts.ts @@ -1,4 +1,5 @@ import { Signature } from '@shardus/crypto-utils' +import { P2P } from '@shardus/types' import * as db from './sqlite3storage' import { receiptDatabase } from '.' import * as Logger from '../Logger' @@ -34,7 +35,7 @@ export interface ArchiverReceipt { timestamp: number } cycle: number - signedReceipt: SignedReceipt + signedReceipt: SignedReceipt | P2P.GlobalAccountsTypes.GlobalTxReceipt afterStates?: AccountsCopy[] beforeStates?: AccountsCopy[] appReceiptData: object & { accountId?: string; data: object } diff --git a/src/primary-process/index.ts b/src/primary-process/index.ts index fe4b1073..be979695 100644 --- a/src/primary-process/index.ts +++ b/src/primary-process/index.ts @@ -160,7 +160,7 @@ const setupWorkerListeners = (worker: Worker): void => { } break default: - if (type && type.includes('axm')) { + if (type && typeof type === 'string' && type.includes('axm')) { if (config.VERBOSE) { console.log(`Worker ${workerId} is sending axm message: ${type}`) console.log(data) diff --git a/src/shardeum/calculateAccountHash.ts b/src/shardeum/calculateAccountHash.ts index d1150a33..5dfb77e7 100644 --- a/src/shardeum/calculateAccountHash.ts +++ b/src/shardeum/calculateAccountHash.ts @@ -1,7 +1,6 @@ -import { config } from '../Config' import * as crypto from '../Crypto' -import * as Logger from '../Logger' -import { ArchiverReceipt } from '../dbstore/receipts' +import { ArchiverReceipt, SignedReceipt } from '../dbstore/receipts' +import { verifyGlobalTxAccountChange } from './verifyGlobalTxReceipt' // account types in Shardeum export enum AccountType { @@ -63,18 +62,27 @@ export const verifyAccountHash = ( nestedCounterMessages = [] ): boolean => { try { - if (receipt.globalModification && config.skipGlobalTxReceiptVerification) return true // return true if global modification - const { accountIDs, afterStateHashes, beforeStateHashes } = receipt.signedReceipt.proposal + if (receipt.globalModification) { + const result = verifyGlobalTxAccountChange(receipt, failedReasons, nestedCounterMessages) + if (!result) return false + return true + } + const signedReceipt = receipt.signedReceipt as SignedReceipt + const { accountIDs, afterStateHashes, beforeStateHashes } = signedReceipt.proposal if (accountIDs.length !== afterStateHashes.length) { failedReasons.push( `Modified account count specified in the receipt and the actual updated account count does not match! ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` ) + nestedCounterMessages.push( + `Modified account count specified in the receipt and the actual updated account count does not match!` + ) return false } if (beforeStateHashes.length !== afterStateHashes.length) { failedReasons.push( `Account state hash before and after count does not match! ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` ) + nestedCounterMessages.push(`Account state hash before and after count does not match!`) return false } for (const [index, accountId] of accountIDs.entries()) { @@ -83,6 +91,7 @@ export const verifyAccountHash = ( failedReasons.push( `Account not found in the receipt's afterStates | Acc-ID: ${accountId}, txId: ${receipt.tx.txId}, Cycle: ${receipt.cycle}, timestamp: ${receipt.tx.timestamp}` ) + nestedCounterMessages.push(`Account not found in the receipt`) return false } const calculatedAccountHash = accountSpecificHash(accountData.data) @@ -92,12 +101,15 @@ export const verifyAccountHash = ( failedReasons.push( `Account hash does not match | Acc-ID: ${accountId}, txId: ${receipt.tx.txId}, Cycle: ${receipt.cycle}, timestamp: ${receipt.tx.timestamp}` ) + nestedCounterMessages.push(`Account hash does not match`) return false } } return true } catch (e) { - failedReasons.push('Error in verifyAccountHash', e) + console.error(`Error in verifyAccountHash`, e) + failedReasons.push(`Error in verifyAccountHash ${e}`) + nestedCounterMessages.push('Error in verifyAccountHash') return false } } diff --git a/src/shardeum/verifyAppReceiptData.ts b/src/shardeum/verifyAppReceiptData.ts index 7e6ffaa6..783712e8 100644 --- a/src/shardeum/verifyAppReceiptData.ts +++ b/src/shardeum/verifyAppReceiptData.ts @@ -1,7 +1,5 @@ -import { config } from '../Config' import * as crypto from '../Crypto' -import * as Logger from '../Logger' -import { ArchiverReceipt, Receipt } from '../dbstore/receipts' +import { ArchiverReceipt, Receipt, SignedReceipt } from '../dbstore/receipts' import { Utils as StringUtils } from '@shardus/types' export type ShardeumReceipt = object & { @@ -16,12 +14,13 @@ export const verifyAppReceiptData = async ( nestedCounterMessages = [] ): Promise<{ valid: boolean; needToSave: boolean }> => { let result = { valid: false, needToSave: false } - const { appReceiptData, globalModification, signedReceipt } = receipt - - if (globalModification && config.skipGlobalTxReceiptVerification) return { valid: true, needToSave: true } + const { appReceiptData, globalModification } = receipt + if (globalModification) return { valid: true, needToSave: true } + const signedReceipt = receipt.signedReceipt as SignedReceipt const newShardeumReceipt = appReceiptData.data as ShardeumReceipt if (!newShardeumReceipt.amountSpent || !newShardeumReceipt.readableReceipt) { failedReasons.push(`appReceiptData missing amountSpent or readableReceipt`) + nestedCounterMessages.push(`appReceiptData missing amountSpent or readableReceipt`) return result } const { accountIDs, afterStateHashes, beforeStateHashes } = signedReceipt.proposal @@ -40,6 +39,7 @@ export const verifyAppReceiptData = async ( failedReasons.push( `The account state hash before or after is missing in the receipt! ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` ) + nestedCounterMessages.push(`The account state hash before or after is missing in the receipt!`) } if ( // eslint-disable-next-line security/detect-object-injection @@ -50,6 +50,9 @@ export const verifyAppReceiptData = async ( failedReasons.push( `The receipt has 0 amountSpent and status 0 but has state updated accounts! ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` ) + nestedCounterMessages.push( + `The receipt has 0 amountSpent and status 0 but has state updated accounts!` + ) break } } @@ -103,20 +106,27 @@ export const verifyAppReceiptData = async ( // Finally verify appReceiptData hash const appReceiptDataCopy = { ...appReceiptData } - const calculatedAppReceiptDataHash = calculateAppReceiptDataHash(appReceiptDataCopy, failedReasons) - if (calculatedAppReceiptDataHash !== receipt.signedReceipt.proposal.appReceiptDataHash) { + const calculatedAppReceiptDataHash = calculateAppReceiptDataHash( + appReceiptDataCopy, + failedReasons, + nestedCounterMessages + ) + if (calculatedAppReceiptDataHash !== signedReceipt.proposal.appReceiptDataHash) { failedReasons.push( - `appReceiptData hash mismatch: ${crypto.hashObj(appReceiptData)} != ${ - receipt.signedReceipt.proposal.appReceiptDataHash - }` + `appReceiptData hash mismatch: ${calculatedAppReceiptDataHash} != ${signedReceipt.proposal.appReceiptDataHash}` ) + nestedCounterMessages.push(`appReceiptData hash mismatch`) result = { valid: false, needToSave: false } } return result } // Converting the correct appReceipt data format to get the correct hash -const calculateAppReceiptDataHash = (appReceiptData: any, failedReasons = []): string => { +const calculateAppReceiptDataHash = ( + appReceiptData: any, + failedReasons = [], + nestedCounterMessages = [] +): string => { try { if (appReceiptData.data && appReceiptData.data.receipt) { if (appReceiptData.data.receipt.bitvector) @@ -142,7 +152,9 @@ const calculateAppReceiptDataHash = (appReceiptData: any, failedReasons = []): s const hash = crypto.hashObj(appReceiptData) return hash } catch (err) { + console.error(`calculateAppReceiptDataHash error: ${err}`) failedReasons.push(`calculateAppReceiptDataHash error: ${err}`) + nestedCounterMessages.push(`calculateAppReceiptDataHash error`) return '' } } diff --git a/src/shardeum/verifyGlobalTxReceipt.ts b/src/shardeum/verifyGlobalTxReceipt.ts new file mode 100644 index 00000000..ac95a109 --- /dev/null +++ b/src/shardeum/verifyGlobalTxReceipt.ts @@ -0,0 +1,121 @@ +import { P2P } from '@shardus/types' +import { ArchiverReceipt } from '../dbstore/receipts' +import { accountSpecificHash } from './calculateAccountHash' + +// Refer to https://github.com/shardeum/shardeum/blob/89db23e1d4ffb86b4353b8f37fb360ea3cd93c5b/src/shardeum/shardeumTypes.ts#L242 +export interface SetGlobalTxValue { + isInternalTx: boolean + internalTXType: InternalTXType + timestamp: number + from: string + change: { + cycle: number + change: object + } +} + +// Refer to https://github.com/shardeum/shardeum/blob/89db23e1d4ffb86b4353b8f37fb360ea3cd93c5b/src/shardeum/shardeumTypes.ts#L87-L88 +export enum InternalTXType { + SetGlobalCodeBytes = 0, //Deprecated + InitNetwork = 1, + NodeReward = 2, //Deprecated + ChangeConfig = 3, + ApplyChangeConfig = 4, + SetCertTime = 5, + Stake = 6, + Unstake = 7, + InitRewardTimes = 8, + ClaimReward = 9, + ChangeNetworkParam = 10, + ApplyNetworkParam = 11, + Penalty = 12, +} + +export const verifyGlobalTxAccountChange = ( + receipt: ArchiverReceipt, + failedReasons = [], + nestedCounterMessages = [] +): boolean => { + try { + const signedReceipt = receipt.signedReceipt as P2P.GlobalAccountsTypes.GlobalTxReceipt + const internalTx = signedReceipt.tx.value as SetGlobalTxValue + + if (internalTx.internalTXType === InternalTXType.InitNetwork) { + // Refer to https://github.com/shardeum/shardeum/blob/89db23e1d4ffb86b4353b8f37fb360ea3cd93c5b/src/index.ts#L2334 + // no need to do anything, as it is network account creation + return true + } else if ( + internalTx.internalTXType === InternalTXType.ApplyChangeConfig || + internalTx.internalTXType === InternalTXType.ApplyNetworkParam + ) { + if (signedReceipt.tx.addressHash !== '') { + for (const account of receipt.beforeStates) { + if (account.accountId !== signedReceipt.tx.address) { + failedReasons.push( + `Unexpected account found in before accounts ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`Unexpected account found in before accounts`) + return false + } + const expectedAccountHash = signedReceipt.tx.addressHash + const calculatedAccountHash = accountSpecificHash(account.data) + if (expectedAccountHash !== calculatedAccountHash) { + failedReasons.push( + `Account hash before does not match in globalModification tx - ${account.accountId} , ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`Account hash before does not match in globalModification tx`) + return false + } + } + } + for (const account of receipt.afterStates) { + if (account.accountId !== signedReceipt.tx.address) { + failedReasons.push( + `Unexpected account found in accounts ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`Unexpected account found in accounts`) + return false + } + const networkAccountBefore = receipt.beforeStates.find( + (bAccount) => bAccount?.accountId === account.accountId + ) + const networkAccountAfter = receipt.afterStates.find( + (fAccount) => fAccount?.accountId === signedReceipt.tx.address + ) + if (!networkAccountBefore || !networkAccountAfter) { + failedReasons.push( + `No network account found in accounts ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`No network account found in accounts`) + return false + } + networkAccountBefore.data.listOfChanges?.push(internalTx.change) + networkAccountBefore.data.timestamp = signedReceipt.tx.when + const expectedAccountHash = networkAccountAfter.hash + console.dir(networkAccountBefore, { depth: null }) + const calculatedAccountHash = accountSpecificHash(networkAccountBefore.data) + if (expectedAccountHash !== calculatedAccountHash) { + failedReasons.push( + `Account hash does not match in globalModification tx - ${networkAccountAfter.accountId} , ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`Account hash does not match in globalModification tx`) + return false + } + } + return true + } else { + failedReasons.push( + `Unexpected internal transaction type in the globalModification tx ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}` + ) + nestedCounterMessages.push(`Unexpected internal transaction type in the globalModification tx`) + return false + } + } catch (error) { + console.error(`verifyGlobalTxAccountChange error`, error) + failedReasons.push( + `Error while verifying global account change ${receipt.tx.txId} , ${receipt.cycle} , ${receipt.tx.timestamp}, ${error}` + ) + nestedCounterMessages.push(`Error while verifying global account change`) + return false + } +} From 5e1c1d754c010e330868b3ae8d325c24fadd707d Mon Sep 17 00:00:00 2001 From: Sonali Thakur Date: Fri, 18 Oct 2024 22:53:47 +0530 Subject: [PATCH 2/5] fix empty cycleInfo fix receipt verification --- src/API.ts | 2 ++ src/Data/Collector.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/API.ts b/src/API.ts index ae42efe3..73f5da74 100644 --- a/src/API.ts +++ b/src/API.ts @@ -145,6 +145,8 @@ export function registerRoutes(server: FastifyInstance Date: Fri, 25 Oct 2024 15:59:08 +0530 Subject: [PATCH 3/5] enhance node validation --- src/Config.ts | 3 ++ src/Data/Collector.ts | 76 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index f851d5db..748db035 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -79,6 +79,8 @@ export interface Config { usePOQo: boolean // The percentage of votes required to confirm transaction requiredVotesPercentage: number + // The percentage of votes required for majority + requiredMajorityVotesPercentage: number // max number of recent cycle shard data to keep maxCyclesShardDataToKeep: number // the number of cycles within which we want to keep \changes to a config*/ @@ -174,6 +176,7 @@ let config: Config = { stopGossipTxData: false, usePOQo: true, requiredVotesPercentage: 2 / 3, + requiredMajorityVotesPercentage: 60, maxCyclesShardDataToKeep: 10, configChangeMaxCyclesToKeep: 5, configChangeMaxChangesToKeep: 1000, diff --git a/src/Data/Collector.ts b/src/Data/Collector.ts index e4d45aea..dd684588 100644 --- a/src/Data/Collector.ts +++ b/src/Data/Collector.ts @@ -479,26 +479,42 @@ export const verifyReceiptData = async ( // Refer to https://github.com/shardeum/shardus-core/blob/7d8877b7e1a5b18140f898a64b932182d8a35298/src/p2p/GlobalAccounts.ts#L397 let votingGroupCount = cycleShardData.shardGlobals.nodesPerConsenusGroup if (votingGroupCount > cycleShardData.nodes.length) { - votingGroupCount = cycleShardData.nodes.length + if (nestedCountersInstance) + nestedCountersInstance.countEvent('receipt', 'votingGroupCount_greater_than_nodes_length') + Logger.mainLogger.error( + 'votingGroupCount_greater_than_nodes_length', + votingGroupCount, + cycleShardData.nodes.length + ) + // votingGroupCount = cycleShardData.nodes.length } - let isReceiptMajority = (signs.length / votingGroupCount) * 100 >= 60 + let isReceiptMajority = + (signs.length / votingGroupCount) * 100 >= config.requiredMajorityVotesPercentage if (!isReceiptMajority) { Logger.mainLogger.error( - `Invalid receipt globalModification signs count is less than 60% of the votingGroupCount, ${signs.length}, ${votingGroupCount}` + `Invalid receipt globalModification signs count is less than ${config.requiredMajorityVotesPercentage}% of the votingGroupCount, ${signs.length}, ${votingGroupCount}` ) if (nestedCountersInstance) nestedCountersInstance.countEvent( 'receipt', - 'Invalid_receipt_globalModification_signs_count_less_than_60%' + `Invalid_receipt_globalModification_signs_count_less_than_${config.requiredMajorityVotesPercentage}%` ) return result } + + const nodeMap = new Map() + // Fill the map with nodes keyed by their public keys + cycleShardData.nodes.forEach((node) => { + if (node.publicKey) { + nodeMap.set(node.publicKey, node) + } + }) // Using a set to store the unique signers to avoid duplicates const uniqueSigners = new Set() for (const sign of signs) { const { owner: nodePubKey } = sign // Get the node id from the public key - const node = cycleShardData.nodes.find((node) => node.publicKey === nodePubKey) + const node = nodeMap.get(nodePubKey) if (node == null) { Logger.mainLogger.error( `The node with public key ${nodePubKey} of the receipt ${txId} with ${timestamp} is not in the active nodesList of cycle ${cycle}` @@ -524,7 +540,8 @@ export const verifyReceiptData = async ( } uniqueSigners.add(nodePubKey) } - isReceiptMajority = (uniqueSigners.size / votingGroupCount) * 100 >= 60 + isReceiptMajority = + (uniqueSigners.size / votingGroupCount) * 100 >= config.requiredMajorityVotesPercentage if (isReceiptMajority) { Logger.mainLogger.error( `Invalid receipt globalModification valid signs count is less than votingGroupCount ${uniqueSigners.size}, ${votingGroupCount}` @@ -536,7 +553,7 @@ export const verifyReceiptData = async ( ) return result } - const requiredSignatures = Math.floor(votingGroupCount * (60 / 100)) + const requiredSignatures = Math.floor(votingGroupCount * (config.requiredMajorityVotesPercentage / 100)) return { success: true, requiredSignatures } } } @@ -722,16 +739,55 @@ const verifyAppliedReceiptSignatures = ( nestedCounterMessages = [] ): { success: boolean } => { const result = { success: false, failedReasons, nestedCounterMessages } - const { globalModification } = receipt - const { txId: txid } = receipt.tx + const { globalModification, cycle, executionShardKey } = receipt + const { txId: txid, timestamp } = receipt.tx if (globalModification) { const appliedReceipt = receipt.signedReceipt as P2PTypes.GlobalAccountsTypes.GlobalTxReceipt // Refer to https://github.com/shardeum/shardus-core/blob/7d8877b7e1a5b18140f898a64b932182d8a35298/src/p2p/GlobalAccounts.ts#L294 const { signs, tx } = appliedReceipt + const cycleShardData = shardValuesByCycle.get(cycle) + const { homePartition } = ShardFunction.addressToPartition(cycleShardData.shardGlobals, executionShardKey) + const nodeMap = new Map() + // Fill the map with nodes keyed by their public keys + cycleShardData.nodes.forEach((node) => { + if (node.publicKey) { + nodeMap.set(node.publicKey, node) + } + }) + const acceptableSigners = new Set() + for (const sign of signs) { + const { owner: nodePubKey } = sign + // Get the node id from the public key + const node = nodeMap.get(nodePubKey) + if (node == null) { + Logger.mainLogger.error( + `The node with public key ${nodePubKey} of the receipt ${txid} with ${timestamp} is not in the active nodesList of cycle ${cycle}` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'globalModification_sign_owner_not_in_active_nodesList' + ) + continue + } + // Check if the node is in the execution group + if (!cycleShardData.parititionShardDataMap.get(homePartition).coveredBy[node.id]) { + Logger.mainLogger.error( + `The node with public key ${nodePubKey} of the receipt ${txid} with ${timestamp} is not in the execution group of the tx` + ) + if (nestedCountersInstance) + nestedCountersInstance.countEvent( + 'receipt', + 'globalModification_sign_node_not_in_execution_group_of_tx' + ) + continue + } + acceptableSigners.add(sign) + } // Using a map to store the good signatures to avoid duplicates const goodSignatures = new Map() - for (const sign of signs) { + for (const sign of acceptableSigners) { if (Crypto.verify({ ...tx, sign: sign })) { goodSignatures.set(sign.owner, sign) // Break the loop if the required number of good signatures are found From a14ffae51c10961378dc25460433e6d60f814a0d Mon Sep 17 00:00:00 2001 From: Sonali Thakur Date: Fri, 25 Oct 2024 17:36:22 +0530 Subject: [PATCH 4/5] shardus/types version update- 1.2.21 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 308b150c..1b642578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@fastify/rate-limit": "^7.6.0", "@shardus/archiver-discovery": "^1.1.0", "@shardus/crypto-utils": "4.1.4", - "@shardus/types": "1.2.21-1", + "@shardus/types": "1.2.21", "deepmerge": "^4.2.2", "fastify": "4.12.0", "log4js": "^6.3.0", @@ -621,9 +621,9 @@ "integrity": "sha512-QCCHm15dmEFkH+TUMUNMT/iCGCsjOR8z6/5AZasG7Gsu9CTL85V4L+Ny/SGjKzM9sdi1Vz8XARy96r+slUZBOg==" }, "node_modules/@shardus/types": { - "version": "1.2.21-1", - "resolved": "https://registry.npmjs.org/@shardus/types/-/types-1.2.21-1.tgz", - "integrity": "sha512-/lKXe1jakx4uuOqG5O6uVjQNbhS5F7P64ufydzru80j9f0eKcC0T8HoFsfpKFAB9NhRmTY7HXKavqPC3g4Hrww==" + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/@shardus/types/-/types-1.2.21.tgz", + "integrity": "sha512-vI77TFAhdc6HqG59/jS5KlzLt3dR9Ko0DetSyoaUHCCw/1GZPbzHiRum98DE0jlusNT6t2Iiit8MjLDM5ezkGw==" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", diff --git a/package.json b/package.json index 9d2d9a02..2a10cfb3 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@fastify/rate-limit": "^7.6.0", "@shardus/archiver-discovery": "^1.1.0", "@shardus/crypto-utils": "4.1.4", - "@shardus/types": "1.2.21-1", + "@shardus/types": "1.2.21", "deepmerge": "^4.2.2", "fastify": "4.12.0", "log4js": "^6.3.0", From 2208b6ee726936f2ea010d0b2011b1f6e839a7b5 Mon Sep 17 00:00:00 2001 From: Arham Jain Date: Fri, 25 Oct 2024 17:44:14 +0530 Subject: [PATCH 5/5] fix(lint): fixed list error on votingGroupCount --- src/Data/Collector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Collector.ts b/src/Data/Collector.ts index dd684588..2b10f28a 100644 --- a/src/Data/Collector.ts +++ b/src/Data/Collector.ts @@ -477,7 +477,7 @@ export const verifyReceiptData = async ( else { const { signs } = appliedReceipt // Refer to https://github.com/shardeum/shardus-core/blob/7d8877b7e1a5b18140f898a64b932182d8a35298/src/p2p/GlobalAccounts.ts#L397 - let votingGroupCount = cycleShardData.shardGlobals.nodesPerConsenusGroup + const votingGroupCount = cycleShardData.shardGlobals.nodesPerConsenusGroup if (votingGroupCount > cycleShardData.nodes.length) { if (nestedCountersInstance) nestedCountersInstance.countEvent('receipt', 'votingGroupCount_greater_than_nodes_length')