diff --git a/framework-plugins/lisk-framework-chain-connector-plugin/src/chain_connector_plugin.ts b/framework-plugins/lisk-framework-chain-connector-plugin/src/chain_connector_plugin.ts index 623747b729a..545ac224b60 100644 --- a/framework-plugins/lisk-framework-chain-connector-plugin/src/chain_connector_plugin.ts +++ b/framework-plugins/lisk-framework-chain-connector-plugin/src/chain_connector_plugin.ts @@ -23,7 +23,6 @@ import { OutboxRootWitness, JSONObject, Schema, - OwnChainAccountJSON, Transaction, LastCertificate, CcmSendSuccessEventData, @@ -140,15 +139,7 @@ export class ChainConnectorPlugin extends BasePlugin this.endpoint.load(this._chainConnectorStore); this._sendingChainClient = this.apiClient; - - this._ownChainID = Buffer.from( - ( - await this._sendingChainClient.invoke( - 'interoperability_getOwnChainAccount', - ) - ).chainID, - 'hex', - ); + this._ownChainID = Buffer.from(this.appConfig.genesis.chainID, 'hex'); if (this._receivingChainID[0] !== this._ownChainID[0]) { throw new Error('Receiving Chain ID network does not match the sending chain network.'); } @@ -313,15 +304,32 @@ export class ChainConnectorPlugin extends BasePlugin record.height <= (newCertificate ? newCertificate.height : this._lastCertificate.height), ); // Calculate messageWitnessHashes for pending CCMs if any - const sendingChainChannelDataJSON = await this._receivingChainClient.invoke( + const channelDataOnReceivingChain = await this._receivingChainClient.invoke( 'interoperability_getChannel', { chainID: this._ownChainID.toString('hex') }, ); - const sendingChainChannelData = channelDataJSONToObj(sendingChainChannelDataJSON); + if (!channelDataOnReceivingChain?.inbox) { + this.logger.info('Receiving chain is not registered yet on the sending chain.'); + return; + } + const inboxSizeOnReceivingChain = channelDataJSONToObj(channelDataOnReceivingChain).inbox.size; + + const receivingChainChannelDataJSON = await this._sendingChainClient.invoke( + 'interoperability_getChannel', + { chainID: this._receivingChainID.toString('hex') }, + ); + + if (!receivingChainChannelDataJSON?.outbox) { + this.logger.info('Sending chain is not registered yet on the receiving chain.'); + return; + } + const outboxSizeOnSendingChain = channelDataJSONToObj(receivingChainChannelDataJSON).outbox + .size; const messageWitnessHashesForCCMs = calculateMessageWitnesses( - sendingChainChannelData, - ccmsToBeIncluded, + inboxSizeOnReceivingChain, + outboxSizeOnSendingChain, lastSentCCM, + ccmsToBeIncluded, this._maxCCUSize, ); const { crossChainMessages, lastCCMToBeSent, messageWitnessHashes } = diff --git a/framework-plugins/lisk-framework-chain-connector-plugin/src/inbox_update.ts b/framework-plugins/lisk-framework-chain-connector-plugin/src/inbox_update.ts index c410128aad4..b01d920e742 100644 --- a/framework-plugins/lisk-framework-chain-connector-plugin/src/inbox_update.ts +++ b/framework-plugins/lisk-framework-chain-connector-plugin/src/inbox_update.ts @@ -12,7 +12,7 @@ * Removal or modification of this copyright notice is prohibited. */ -import { ccmSchema, codec, tree, ChannelData } from 'lisk-sdk'; +import { ccmSchema, codec, tree } from 'lisk-sdk'; import { CCMsFromEvents, LastSentCCMWithHeight } from './types'; /** @@ -33,12 +33,13 @@ import { CCMsFromEvents, LastSentCCMWithHeight } from './types'; } */ export const calculateMessageWitnesses = ( - sendingChainChannelInfo: ChannelData, - ccmsToBeIncluded: CCMsFromEvents[], - lastSentCCMInfo: { + inboxSizeOnReceivingChain: number, + outboxSizeOnSendingChain: number, + lastSentCCM: { height: number; nonce: bigint; }, + ccmsToBeIncluded: CCMsFromEvents[], maxCCUSize: number, ): { crossChainMessages: Buffer[]; @@ -51,9 +52,14 @@ export const calculateMessageWitnesses = ( let totalSize = 0; // Make an array of ccms with nonce greater than last sent ccm nonce for (const ccmsFromEvents of ccmsToBeIncluded) { - const { ccms } = ccmsFromEvents; + const { ccms, height } = ccmsFromEvents; for (const ccm of ccms) { - if (ccm.nonce > lastSentCCMInfo.nonce) { + if (height !== 0 && lastSentCCM.height === height) { + if (ccm.nonce === lastSentCCM.nonce) { + continue; + } + } + if (inboxSizeOnReceivingChain < outboxSizeOnSendingChain) { const ccmBytes = codec.encode(ccmSchema, ccm); totalSize += ccmBytes.length; if (totalSize < maxCCUSize) { @@ -86,7 +92,7 @@ export const calculateMessageWitnesses = ( const remainingSerializedCCMs = allSerializedCCMs.slice(includedSerializedCCMs.length); // Generate messageWitness const messageWitnessHashes = tree.regularMerkleTree.calculateRightWitness( - sendingChainChannelInfo.inbox.size + includedSerializedCCMs.length, + inboxSizeOnReceivingChain + includedSerializedCCMs.length, remainingSerializedCCMs, ); diff --git a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/endpoint.spec.ts b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/endpoint.spec.ts index 0007cba6543..d9597a7c62a 100644 --- a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/endpoint.spec.ts +++ b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/endpoint.spec.ts @@ -27,7 +27,8 @@ import * as chainConnectorDB from '../../src/db'; import { CCMsFromEvents, CCMsFromEventsJSON, LastSentCCMWithHeightJSON } from '../../src/types'; import { ccmsFromEventsToJSON, getMainchainID } from '../../src/utils'; -describe('getSentCCUs', () => { +describe('endpoints', () => { + const ownChainID = Buffer.from('10000000', 'hex'); const appConfigForPlugin: ApplicationConfigForPlugin = { system: { keepEventsForHeights: -1, @@ -52,7 +53,9 @@ describe('getSentCCUs', () => { minEntranceFeePriority: '0', minReplacementFeeDifference: '10', }, - genesis: {} as GenesisConfig, + genesis: { + chainID: ownChainID.toString('hex'), + } as GenesisConfig, generator: { keys: { fromFile: '', @@ -114,7 +117,6 @@ describe('getSentCCUs', () => { const defaultPassword = '123'; const defaultCCUFee = '100000000'; - const ownChainID = Buffer.from('10000000', 'hex'); let chainConnectorPlugin: ChainConnectorPlugin; beforeEach(async () => { diff --git a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/inbox_update.spec.ts b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/inbox_update.spec.ts index c86a96f1ac7..6b167227516 100644 --- a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/inbox_update.spec.ts +++ b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/inbox_update.spec.ts @@ -12,44 +12,17 @@ * Removal or modification of this copyright notice is prohibited. */ -import { - CCMsg, - Certificate, - testing, - cryptography, - BlockHeader, - tree, - ChannelData, -} from 'lisk-sdk'; +import { CCMsg, tree } from 'lisk-sdk'; import { CCU_TOTAL_CCM_SIZE } from '../../src/constants'; import { CCMsFromEvents } from '../../src/types'; import { calculateMessageWitnesses } from '../../src/inbox_update'; import { getSampleCCM } from '../utils/sampleCCM'; describe('inboxUpdate', () => { - let sampleBlock: BlockHeader; - let sampleCertificate: Certificate; - let sampleCCMs: CCMsg[]; let sampleCCMsFromEvents: CCMsFromEvents[]; beforeEach(() => { - sampleBlock = testing.createFakeBlockHeader({ - stateRoot: cryptography.utils.getRandomBytes(32), - validatorsHash: cryptography.utils.getRandomBytes(32), - height: 100, - }) as BlockHeader & { validatorsHash: Buffer; stateRoot: Buffer }; - - sampleCertificate = { - blockID: sampleBlock.id, - height: sampleBlock.height, - timestamp: sampleBlock.timestamp, - validatorsHash: sampleBlock.validatorsHash as Buffer, - stateRoot: sampleBlock.stateRoot as Buffer, - aggregationBits: Buffer.alloc(0), - signature: cryptography.utils.getRandomBytes(32), - }; - sampleCCMs = new Array(4).fill(0).map((_, index) => getSampleCCM(index + 1)); sampleCCMsFromEvents = [ @@ -73,27 +46,16 @@ describe('inboxUpdate', () => { }); describe('calculateMessageWitnesses', () => { - const channelData: ChannelData = { - inbox: { - size: 2, - appendPath: [], - root: Buffer.alloc(1), - }, - outbox: { - size: 2, - appendPath: [], - root: Buffer.alloc(1), - }, - messageFeeTokenID: Buffer.from('04000001', 'hex'), - partnerChainOutboxRoot: Buffer.alloc(2), - minReturnFeePerByte: BigInt(0), - }; it('should return one inboxUpdate when all the ccms can be included', () => { jest.spyOn(tree.regularMerkleTree, 'calculateRightWitness').mockReturnValue([]); const messageWitnessHashesForCCMs = calculateMessageWitnesses( - channelData, + 0, + 1, + { + height: 1, + nonce: BigInt(0), + }, sampleCCMsFromEvents, - { height: 1, nonce: BigInt(0) }, CCU_TOTAL_CCM_SIZE, ); @@ -119,9 +81,13 @@ describe('inboxUpdate', () => { ]; const messageWitnessHashesForCCMs = calculateMessageWitnesses( - channelData, + 0, + 1, + { + height: 1, + nonce: BigInt(0), + }, ccmListWithBigSize, - { height: 1, nonce: BigInt(0) }, CCU_TOTAL_CCM_SIZE, ); @@ -136,9 +102,13 @@ describe('inboxUpdate', () => { .mockReturnValue([Buffer.alloc(1)]); const { crossChainMessages, lastCCMToBeSent, messageWitnessHashes } = calculateMessageWitnesses( - channelData, + 0, + 1, + { + height: 1, + nonce: BigInt(0), + }, [], - { height: sampleCertificate.height + 1, nonce: BigInt(2) }, CCU_TOTAL_CCM_SIZE, ); diff --git a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/plugin.spec.ts b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/plugin.spec.ts index 34d09510def..aebe834a0e8 100644 --- a/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/plugin.spec.ts +++ b/framework-plugins/lisk-framework-chain-connector-plugin/test/unit/plugin.spec.ts @@ -62,6 +62,9 @@ describe('ChainConnectorPlugin', () => { const ownChainID = Buffer.from('04000000', 'hex'); const appConfigForPlugin: ApplicationConfigForPlugin = { ...testing.fixtures.defaultConfig, + genesis: { + chainID: ownChainID.toString('hex'), + } as any, rpc: { modes: ['ipc'], port: 8080, @@ -361,7 +364,6 @@ describe('ChainConnectorPlugin', () => { new db.InMemoryDatabase() as never, ); - expect(sendingChainAPIClientMock.invoke).toHaveBeenCalledTimes(1); expect(sendingChainAPIClientMock.subscribe).toHaveBeenCalledTimes(2); }); @@ -794,15 +796,14 @@ describe('ChainConnectorPlugin', () => { */ expect(sendingChainAPIClientMock.subscribe).toHaveBeenCalledTimes(2); /** - * Total 6 calls to below RPCs through sendingChainAPIClient - * 1. interoperability_getOwnChainAccount - * 2. chain_getEvents - * 3. system_getMetadata - * 4. state_prove - * 5. consensus_getBFTParameters - * 6. consensus_getBFTHeights + * Total 5 calls to below RPCs through sendingChainAPIClient + * 1. chain_getEvents + * 2. system_getMetadata + * 3. state_prove + * 4. consensus_getBFTParameters + * 5. consensus_getBFTHeights */ - expect(sendingChainAPIClientMock.invoke).toHaveBeenCalledTimes(5); + expect(sendingChainAPIClientMock.invoke).toHaveBeenCalledTimes(4); /** * Two calls to below RPC through receivingChainAPIClient * 1. interoperability_getChainAccount: in load() function @@ -1067,6 +1068,15 @@ describe('ChainConnectorPlugin', () => { }) .mockResolvedValue(sampleChannelDataJSON); + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 3 }, + }); + jest .spyOn(certificateGenerationUtil, 'getNextCertificateFromAggregateCommits') .mockReturnValue(undefined); @@ -1084,7 +1094,7 @@ describe('ChainConnectorPlugin', () => { }; await chainConnectorPlugin['_chainConnectorStore'].setLastSentCCM({ ...getSampleCCM(12), - height: 10, + height: 11, }); const result = await chainConnectorPlugin['_computeCCUParams']( @@ -1126,6 +1136,14 @@ describe('ChainConnectorPlugin', () => { }); it('should successfully create CCU with messageWitness for pending ccms', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 4 }, + }); jest.spyOn(chainConnectorPlugin['logger'], 'info'); const validatorsHashAtLastCertificate = sampleBlockHeaders.find(b => b.height === 8); @@ -1137,7 +1155,7 @@ describe('ChainConnectorPlugin', () => { }; const lastSentCCMsFromEvents = sampleCCMsWithEvents[5]; const expectedCCMsToBeSent = sampleCCMsWithEvents - .slice(6, 8) + .slice(5, 7) .reduce((ccms: CCMsg[], record: CCMsFromEvents) => { for (const ccm of record.ccms) { ccms.push(ccm); @@ -1206,6 +1224,14 @@ describe('ChainConnectorPlugin', () => { }); it('should throw error when no validators data is found for the new certificate height', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 4 }, + }); const newCertificate = { aggregationBits: Buffer.alloc(1), blockID: blockHeaderAtCertificateHeight.id as Buffer, @@ -1236,6 +1262,14 @@ describe('ChainConnectorPlugin', () => { }); it('should return empty activeValidatorsUpdate when (lastCertificate.validatorsHash === newCertificate.validatorsHash)', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 2 }, + }); const newCertificate = { aggregationBits: Buffer.alloc(1), blockID: blockHeaderAtCertificateHeight.id as Buffer, @@ -1266,17 +1300,6 @@ describe('ChainConnectorPlugin', () => { }, ]; - const expectedCCMsToBeSent = sampleCCMsWithEvents - .slice(4, 9) - .reduce((ccms: CCMsg[], record: CCMsFromEvents) => { - for (const ccm of record.ccms) { - ccms.push(ccm); - } - - return ccms; - }, []) - .map(ccm => codec.encode(ccmSchema, ccm)); - const result = await chainConnectorPlugin['_computeCCUParams']( sampleBlockHeaders, sampleAggregateCommits, @@ -1294,14 +1317,23 @@ describe('ChainConnectorPlugin', () => { expect(result?.ccuParams.certificate).toEqual( codec.encode(certificateSchema, newCertificate), ); - expect(result?.ccuParams.inboxUpdate.crossChainMessages).toEqual(expectedCCMsToBeSent); + expect(result?.ccuParams.inboxUpdate.crossChainMessages).toEqual([]); expect(result?.ccuParams.inboxUpdate.messageWitnessHashes).toEqual([]); - expect(result?.ccuParams.inboxUpdate.outboxRootWitness).toEqual( - sampleCCMsWithEvents[8].inclusionProof, - ); + expect(result?.ccuParams.inboxUpdate.outboxRootWitness).toEqual({ + bitmap: Buffer.alloc(0), + siblingHashes: [], + }); }); it('should return non-empty activeValidatorsUpdate when (lastCertificate.validatorsHash !== newCertificate.validatorsHash)', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 4 }, + }); const newCertificate = { aggregationBits: Buffer.alloc(1), blockID: blockHeaderAtCertificateHeight.id as Buffer, @@ -1349,23 +1381,13 @@ describe('ChainConnectorPlugin', () => { }, ]; - const expectedCCMsToBeSent = sampleCCMsWithEvents - .slice(4, 9) - .reduce((ccms: CCMsg[], record: CCMsFromEvents) => { - for (const ccm of record.ccms) { - ccms.push(ccm); - } - - return ccms; - }, []) - .map(ccm => codec.encode(ccmSchema, ccm)); - const result = await chainConnectorPlugin['_computeCCUParams']( sampleBlockHeaders, sampleAggregateCommits, validatorsData, sampleCCMsWithEvents, ); + expect(result?.ccuParams.activeValidatorsUpdate).toEqual( validatorsUpdateResult.activeValidatorsUpdate, ); @@ -1375,7 +1397,10 @@ describe('ChainConnectorPlugin', () => { expect(result?.ccuParams.certificate).toEqual( codec.encode(certificateSchema, newCertificate), ); - expect(result?.ccuParams.inboxUpdate.crossChainMessages).toEqual(expectedCCMsToBeSent); + + expect(result?.ccuParams.inboxUpdate.crossChainMessages.length).toEqual( + sampleCCMsWithEvents.slice(4, 9).length, + ); expect(result?.ccuParams.inboxUpdate.messageWitnessHashes).toEqual([]); expect(result?.ccuParams.inboxUpdate.outboxRootWitness).toEqual( sampleCCMsWithEvents[8].inclusionProof, @@ -1383,6 +1408,15 @@ describe('ChainConnectorPlugin', () => { }); it('should return serialized ccms and message witnesses when pending ccms', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 4 }, + }); + const messageWitnessHashes = [cryptography.utils.getRandomBytes(HASH_LENGTH)]; jest .spyOn(tree.regularMerkleTree, 'calculateRightWitness') @@ -1447,17 +1481,6 @@ describe('ChainConnectorPlugin', () => { await chainConnectorPlugin['_chainConnectorStore'].setCrossChainMessages( sampleCCMsWithEvents, ); - // CCMs are only included until height 8 where a big CCM takes up all the space - const expectedCCMsToBeSent = sampleCCMsWithEvents - .slice(4, 7) - .reduce((ccms: CCMsg[], record: CCMsFromEvents) => { - for (const ccm of record.ccms) { - ccms.push(ccm); - } - - return ccms; - }, []) - .map(ccm => codec.encode(ccmSchema, ccm)); const result = await chainConnectorPlugin['_computeCCUParams']( sampleBlockHeaders, @@ -1474,7 +1497,9 @@ describe('ChainConnectorPlugin', () => { expect(result?.ccuParams.certificate).toEqual( codec.encode(certificateSchema, newCertificate), ); - expect(result?.ccuParams.inboxUpdate.crossChainMessages).toEqual(expectedCCMsToBeSent); + expect(result?.ccuParams.inboxUpdate.crossChainMessages.length).toEqual( + sampleCCMsWithEvents.slice(5, 8).length, + ); expect(result?.ccuParams.inboxUpdate.messageWitnessHashes).toEqual(messageWitnessHashes); expect(result?.ccuParams.inboxUpdate.outboxRootWitness).toEqual( sampleCCMsWithEvents[8].inclusionProof, @@ -1482,6 +1507,15 @@ describe('ChainConnectorPlugin', () => { }); it('should return empty list of ccms and message witnesses when no pending ccms', async () => { + when(sendingChainAPIClientMock.invoke) + .calledWith('interoperability_getChannel', { + chainID: chainConnectorPlugin['_receivingChainID'].toString('hex'), + }) + .mockResolvedValue({ + ...sampleChannelDataJSON, + outbox: { ...sampleChannelDataJSON.outbox, size: 4 }, + }); + const newCertificate = { aggregationBits: Buffer.alloc(1), blockID: blockHeaderAtCertificateHeight.id as Buffer,