diff --git a/bmv/contracts/BtpMessageVerifier.sol b/bmv/contracts/BtpMessageVerifier.sol index 6b3a3dd..ef53012 100644 --- a/bmv/contracts/BtpMessageVerifier.sol +++ b/bmv/contracts/BtpMessageVerifier.sol @@ -51,7 +51,8 @@ contract BtpMessageVerifier is IBMV { string memory _srcNetworkId, uint256 _networkTypeId, bytes memory _firstBlockHeader, - uint256 _sequenceOffset + uint256 _sequenceOffset, + uint256 _nextMessageOffset ) { bmc = _bmc; srcNetworkId = _srcNetworkId; @@ -68,7 +69,7 @@ contract BtpMessageVerifier is IBMV { header.mainHeight, header.messageCount, header.messageSn, - header.messageSn, + header.messageSn + _nextMessageOffset, header.nextValidators ); } diff --git a/bmv/contracts/BtpMessageVerifierV2.sol b/bmv/contracts/BtpMessageVerifierV2.sol deleted file mode 100644 index 7daf4f5..0000000 --- a/bmv/contracts/BtpMessageVerifierV2.sol +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; - -import "@iconfoundation/btp2-solidity-library/contracts/interfaces/IBMV.sol"; -import "@iconfoundation/btp2-solidity-library/contracts/utils/RLPEncode.sol"; -import "./libraries/Errors.sol"; -import "./libraries/RelayMessageLib.sol"; -import "./libraries/String.sol"; -import "./libraries/Utils.sol"; - -contract BtpMessageVerifierV2 is IBMV { - using BlockUpdateLib for Header; - using MessageProofLib for MessageProof; - using RelayMessageLib for RelayMessage; - using String for string; - - address private immutable bmc; - uint256 private immutable networkTypeId; - uint256 private immutable networkId; - uint256 private immutable sequenceOffset; - string private srcNetworkId; - StateDB private db; - - // @dev wrap state variables to avoid stack too deep error - struct StateDB { - bytes32 networkSectionHash; - bytes32 messageRoot; - uint256 height; - uint256 messageCount; - uint256 firstMessageSn; - uint256 nextMessageSn; - address[] validators; - } - - modifier onlyBmc() { - require(msg.sender == bmc, Errors.ERR_UNAUTHORIZED); - _; - } - - modifier onlyBtpNetwork(string memory _from) { - (string memory network, ) = _from.splitBTPAddress(); - require( - keccak256(abi.encodePacked(srcNetworkId)) == keccak256(abi.encodePacked(network)), - Errors.ERR_UNAUTHORIZED - ); - _; - } - - // @dev wrap integer variables to avoid stack too deep error - constructor( - address _bmc, - string memory _srcNetworkId, - bytes32 _networkSectionHash, - bytes32 _messageRoot, - uint256[7] memory _integers, - // uint256 _networkTypeId, - // uint256 _sequenceOffset, - // uint256 _networkId, - // uint256 _height, - // uint256 _messageCount, - // uint256 _firstMessageSn, - // uint256 _nextMessageSn, - address[] memory _validators - ) { - bmc = _bmc; - srcNetworkId = _srcNetworkId; - networkTypeId = _integers[0]; - sequenceOffset = _integers[1]; - networkId = _integers[2]; - db = StateDB( - _networkSectionHash, - _messageRoot, - _integers[3], - _integers[4], - _integers[5], - _integers[6], - _validators - ); - } - - function getStatus() external view returns (IBMV.VerifierStatus memory) { - bytes[] memory extra = new bytes[](3); - extra[0] = RLPEncode.encodeUint(sequenceOffset); - extra[1] = RLPEncode.encodeUint(db.firstMessageSn); - extra[2] = RLPEncode.encodeUint(db.messageCount); - return IBMV.VerifierStatus(db.height, RLPEncode.encodeList(extra)); - } - - function handleRelayMessage( - string memory, - string memory _prev, - uint256 _sn, - bytes memory _msg - ) external onlyBmc onlyBtpNetwork(_prev) returns (bytes[] memory messages) { - StateDB memory _db = db; - require(_db.nextMessageSn == _sn - sequenceOffset, Errors.ERR_INVALID_ARGS); - uint256 remainMessageCount = _db.messageCount - (_db.nextMessageSn - _db.firstMessageSn); - RelayMessage[] memory rms = RelayMessageLib.decode(_msg); - - for (uint256 i = 0; i < rms.length; i++) { - if (rms[i].typ == RelayMessageLib.TYPE_BLOCK_UPDATE) { - require(remainMessageCount == 0, string.concat(Errors.ERR_UNKNOWN, ":UnexpectedBlockUpdate")); - (Header memory header, Proof memory proof) = rms[i].toBlockUpdate(); - - require(networkId == header.networkId, string.concat(Errors.ERR_UNKNOWN, ":InvalidNID")); - checkMessageSn(_db.nextMessageSn, header.messageSn); - require(_db.networkSectionHash == header.prevNetworkSectionHash, string.concat(Errors.ERR_UNKNOWN, ":InvalidNSH")); - checkBlockProof(header, proof, _db.validators); - - _db.height = header.mainHeight; - _db.networkSectionHash = header.getNetworkSectionHash(); - if (header.hasNextValidators) { - _db.validators = header.nextValidators; - } - if (header.messageRoot != bytes32(0)) { - _db.messageRoot = header.messageRoot; - _db.firstMessageSn = header.messageSn; - remainMessageCount = _db.messageCount = header.messageCount; - } - } else if (rms[i].typ == RelayMessageLib.TYPE_MESSAGE_PROOF) { - MessageProof memory mp = rms[i].toMessageProof(); - (bytes32 root, uint256 leafCount) = mp.calculate(); - require(root == _db.messageRoot && leafCount == _db.messageCount, string.concat(Errors.ERR_UNKNOWN, ":InvalidMessageProof")); - messages = Utils.append(messages, mp.mesgs); - remainMessageCount -= mp.mesgs.length; - _db.nextMessageSn += mp.mesgs.length; - } - } - - if (_db.height != 0) { - db.height = _db.height; - } - db.networkSectionHash = _db.networkSectionHash; - db.messageRoot = _db.messageRoot; - db.messageCount = _db.messageCount; - db.firstMessageSn = _db.firstMessageSn; - db.nextMessageSn = _db.nextMessageSn; - db.validators = _db.validators; - - return messages; - } - - function getSrcNetworkId() external view returns (string memory) { - return srcNetworkId; - } - - function getNetworkTypeId() external view returns (uint256) { - return networkTypeId; - } - - function getNetworkId() external view returns (uint256) { - return networkId; - } - - function getHeight() external view returns (uint256) { - return db.height; - } - - function getNetworkSectionHash() external view returns (bytes32) { - return db.networkSectionHash; - } - - function getMessageRoot() external view returns (bytes32) { - return db.messageRoot; - } - - function getMessageCount() external view returns (uint256) { - return db.messageCount; - } - - function getRemainMessageCount() external view returns (uint256) { - return db.messageCount - (db.nextMessageSn - db.firstMessageSn); - } - - function getNextMessageSn() external view returns (uint256) { - return db.nextMessageSn; - } - - function getValidators(uint256 nth) external view returns (address) { - return db.validators[nth]; - } - - function getValidatorsCount() external view returns (uint256) { - return db.validators.length; - } - - function hasQuorumOf(uint256 nvalidators, uint256 votes) private pure returns (bool) { - return votes * 3 > nvalidators * 2; - } - - function checkMessageSn(uint256 expected, uint256 actual) private pure { - if (expected < actual) { - revert(Errors.ERR_NOT_VERIFIABLE); - } else if (expected > actual) { - revert(Errors.ERR_ALREADY_VERIFIED); - } - } - - function checkBlockProof(Header memory header, Proof memory proof, address[] memory validators) private view { - uint256 votes = 0; - bytes32 decision = header.getNetworkTypeSectionDecisionHash(srcNetworkId, networkTypeId); - for (uint256 i = 0; i < proof.signatures.length && !hasQuorumOf(validators.length, votes); i++) { - if (Utils.recoverSigner(decision, proof.signatures[i]) == validators[i]) { - votes++; - } - } - require(hasQuorumOf(validators.length, votes), string.concat(Errors.ERR_UNKNOWN, ":LackQuorum")); - } -} diff --git a/bmv/contracts/libraries/BlockUpdateLib.sol b/bmv/contracts/libraries/BlockUpdateLib.sol index 435f685..fd0afcc 100644 --- a/bmv/contracts/libraries/BlockUpdateLib.sol +++ b/bmv/contracts/libraries/BlockUpdateLib.sol @@ -37,20 +37,24 @@ library BlockUpdateLib { RLPDecode.RLPItem memory i = enc.toRlpItem(); RLPDecode.RLPItem[] memory l = i.toList(); - return - Header( - l[0].toUint(), - l[1].toUint(), - bytes32(l[2].toBytes()), - l[3].payloadLen() > 0 ? decodeNSRootPath(l[3].toRlpBytes()) : new Path[](0), - l[4].toUint(), - l[5].toUint() >> 1, - l[5].toUint() & 1 == 1, - l[6].payloadLen() > 0 ? bytes32(l[6].toBytes()) : bytes32(0), - l[7].toUint(), - l[8].payloadLen() > 0 ? bytes32(l[8].toBytes()) : bytes32(0), - l[5].toUint() & 1 == 1 ? decodeValidators(l[9].toBytes()) : new address[](0) - ); + header = Header( + l[0].toUint(), + l[1].toUint(), + bytes32(l[2].toBytes()), + l[3].payloadLen() > 0 ? decodeNSRootPath(l[3].toRlpBytes()) : new Path[](0), + l[4].toUint(), + l[5].toUint() >> 1, + l[5].toUint() & 1 == 1, + l[6].payloadLen() > 0 ? bytes32(l[6].toBytes()) : bytes32(0), + l[7].toUint(), + l[8].payloadLen() > 0 ? bytes32(l[8].toBytes()) : bytes32(0), + l[9].payloadLen() > 0 ? decodeValidators(l[9].toBytes()) : new address[](0) + ); + + if (header.nextValidators.length > 0) { + require(header.nextProofContextHash == calcProofContextHash(header.nextValidators), + "Inconsistent next proof context hashes"); + } } function decodeProof(bytes memory enc) internal pure returns (Proof memory) { @@ -127,4 +131,12 @@ library BlockUpdateLib { } return validators; } + + function calcProofContextHash(address[] memory validators) private pure returns (bytes32) { + bytes[] memory buf = new bytes[](validators.length); + for (uint256 i = 0; i < validators.length; i++) { + buf[i] = RLPEncode.encodeAddress(validators[i]); + } + return keccak256(RLPEncode.encodeList(RLPEncode.encodeList(buf))); + } } diff --git a/bmv/migrations/2_deploy_bmv.js b/bmv/migrations/2_deploy_bmv.js index b16805d..aba301d 100644 --- a/bmv/migrations/2_deploy_bmv.js +++ b/bmv/migrations/2_deploy_bmv.js @@ -24,6 +24,7 @@ module.exports = async function (deployer, network, accounts) { SRC_NETWORK_ID, NETWORK_TYPE_ID, FIRST_BLOCK_UPDATE, - SEQUENCE_OFFSET + SEQUENCE_OFFSET, + 0 ); }; diff --git a/bmv/test/BtpMessageVerifier.test.js b/bmv/test/BtpMessageVerifier.test.js index 9428d66..5fae7b4 100644 --- a/bmv/test/BtpMessageVerifier.test.js +++ b/bmv/test/BtpMessageVerifier.test.js @@ -30,7 +30,7 @@ contract('BtpMessageVerifier', (accounts) => { const FIRST_BLOCK_UPDATE = '0xf8a40a00a07335dc4b60092f2d5aa4b419bf4ea1fe7e76b6a74f7177a26b20733a20d75081c00201f80001a041791102999c339c844880b23950704cc43aa840f3739e365323cda4dfa89e7ab858f856f8549435343874344652abe559b226f00abec23a98a7a594cfb89e639a3b69704631cadae3517165fbf06e1e944fe6e85b23709cbf74e98f81d5869e2b46b9721f94ccefbb67c172b02e70b699884b76e39806eefe00'; beforeEach(async () => { - this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, NETWORK_TYPE_ID, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET); + this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, NETWORK_TYPE_ID, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET, 0); }); describe('sends RelayMessage=[MessageProof]', () => { @@ -128,7 +128,7 @@ contract('BtpMessageVerifier', (accounts) => { beforeEach(async () => { const FIRST_BLOCK_UPDATE = '0xf8850a01a07335dc4b60092f2d5aa4b419bf4ea1fe7e76b6a74f7177a26b20733a20d75081c00101f80000f800b858f856f8549435343874344652abe559b226f00abec23a98a7a594cfb89e639a3b69704631cadae3517165fbf06e1e944fe6e85b23709cbf74e98f81d5869e2b46b9721f94ccefbb67c172b02e70b699884b76e39806eefe00'; - this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, 1, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET); + this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, 1, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET, 0); }); describe('when send RELAY_MESSAGE = [BlockUpdate, MessageProof]', () => { @@ -402,7 +402,7 @@ contract('BtpMessageVerifier', (accounts) => { ]; beforeEach(async () => { - this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, NETWORK_TYPE_ID, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET); + this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, NETWORK_TYPE_ID, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET, 0); }); describe('when miss RELAY_MESSAGE = [MessageProof], and send RELAY_MESSAGE = [BlockUpdate]', () => { @@ -461,6 +461,27 @@ contract('BtpMessageVerifier', (accounts) => { }); }); }); + + describe('deploy with intermediate block header', () => { + const SRC_NETWORK_ID = '0x1.icon' + const VALIDATORS = [ + '0x14cC53a31B003232512Ea3c03C57f8EA35f1eDbF', + '0xB29b239a2BbF5a9507EBb71106C9cA9D0b519Be8', + '0xFFF26dBCD6d0Db8Ee81F1DEE3B2200d6F3430Bb1', + '0x09E75e3075bc1Bf9C9Dcf3F281D7c73544dD3c2D' + ]; + const FIRST_BLOCK_UPDATE = '0xf8c7840084e27800a0ad580fcaaa2d13c3685dd75ab459acb795d5a507058dd8a5eb94c42033761f32c06a06a021170787b9e47f08040209617d46a8b8506c92c8d2e3a2144c250a41d5e22dd201a0284056656da32bd9ad313d2b6e1f889f092d851d35e0fafa81ce24ca5467c6bfb858f856f8549414cc53a31b003232512ea3c03c57f8ea35f1edbf94b29b239a2bbf5a9507ebb71106c9ca9d0b519be894fff26dbcd6d0db8ee81f1dee3b2200d6f3430bb19409e75e3075bc1bf9c9dcf3f281d7c73544dd3c2d'; + + beforeEach(async () => { + this.instance = await BtpMessageVerifier.new(BMC, SRC_NETWORK_ID, 1, FIRST_BLOCK_UPDATE, SEQUENCE_OFFSET, 0); + }); + + describe('when installed: UpdateValidatorFlag=false, Contains Validators set', () => { + shouldHaveThisState.call(this, { + validators: VALIDATORS + }); + }); + }); }); function shouldHaveImmutableState(props) { @@ -476,44 +497,50 @@ function shouldHaveImmutableState(props) { } function shouldHaveThisState(props) { - it('has block height', async () => { - expect(await this.instance.getHeight()) - .to.be.bignumber.equal(props.height); - }); - - it('has network section hash', async () => { - expect(await this.instance.getNetworkSectionHash()) - .to.equal(props.networkSectionHash); - }); - - it('has message root', async () => { - expect(await this.instance.getMessageRoot()) - .to.equal(props.messageRoot); - }); + if (props.height != undefined) { + it('has network section hash', async () => { + expect(await this.instance.getNetworkSectionHash()) + .to.equal(props.networkSectionHash); + }); + } - it('has message count', async () => { - expect(await this.instance.getMessageCount()) - .to.be.bignumber.equal(props.messageCount); - }); + if (props.messageRoot != undefined) { + it('has message root', async () => { + expect(await this.instance.getMessageRoot()) + .to.equal(props.messageRoot); + }); + } - it('has remain message count', async () => { - expect(await this.instance.getRemainMessageCount()) - .to.be.bignumber.equal(props.remainMessageCount); - }); + if (props.messageCount != undefined) { + it('has message count', async () => { + expect(await this.instance.getMessageCount()) + .to.be.bignumber.equal(props.messageCount); + }); + } + if (props.remainMessageCount != undefined) { + it('has remain message count', async () => { + expect(await this.instance.getRemainMessageCount()) + .to.be.bignumber.equal(props.remainMessageCount); + }); + } - it('has next message sequence number', async () => { - expect(await this.instance.getNextMessageSn()) - .to.be.bignumber.equal(props.messageSn); - }); + if (props.messageSn != undefined) { + it('has next message sequence number', async () => { + expect(await this.instance.getNextMessageSn()) + .to.be.bignumber.equal(props.messageSn); + }); + } - it('has validators', async () => { - expect(await this.instance.getValidatorsCount()) - .to.be.bignumber.equal(new BN(props.validators.length)); + if (props.validators != undefined) { + it('has validators', async () => { + expect(await this.instance.getValidatorsCount()) + .to.be.bignumber.equal(new BN(props.validators.length)); - for (nth in props.validators) { - expect((await this.instance.getValidators(nth)).toLowerCase()) - .to.equal(props.validators[nth]); - } - }); + for (nth in props.validators) { + expect((await this.instance.getValidators(nth)).toLowerCase()) + .to.equal(props.validators[nth].toLowerCase()); + } + }); + } }