Skip to content

Commit

Permalink
implement timeoutPacket
Browse files Browse the repository at this point in the history
Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele committed Sep 21, 2023
1 parent e5b66f1 commit f233a92
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
IBCTest:testBenchmarkCreateMockClient() (gas: 213946)
IBCTest:testBenchmarkRecvPacket() (gas: 156383)
IBCTest:testBenchmarkSendPacket() (gas: 85195)
IBCTest:testBenchmarkRecvPacket() (gas: 156517)
IBCTest:testBenchmarkSendPacket() (gas: 85231)
IBCTest:testBenchmarkUpdateMockClient() (gas: 129390)
IBCTest:testConnectionOpenInit() (gas: 495954)
IBCTest:testToUint128((uint64,uint64)) (runs: 256, μ: 1051, ~: 1051)
7 changes: 7 additions & 0 deletions contracts/apps/commons/IBCAppBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,11 @@ abstract contract IBCAppBase is Context, IIBCModule {
* NOTE: You should apply an `onlyIBC` modifier to the function if a derived contract overrides it.
*/
function onAcknowledgementPacket(Packet.Data calldata, bytes calldata, address) external virtual override onlyIBC {}

/**
* @dev See IIBCModule-onTimeoutPacket
*
* NOTE: You should apply an `onlyIBC` modifier to the function if a derived contract overrides it.
*/
function onTimeoutPacket(Packet.Data calldata, address relayer) external virtual onlyIBC {}
}
110 changes: 108 additions & 2 deletions contracts/core/04-channel/IBCPacket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ contract IBCPacket is IBCStore, IIBCPacket {
ILightClient client = ILightClient(clientImpls[connection.client_id]);

(Height.Data memory latestHeight, bool found) = client.getLatestHeight(connection.client_id);
require(found, "clientState not found");
require(
timeoutHeight.isZero() || latestHeight.lt(timeoutHeight),
"receiving chain block height >= packet timeout height"
Expand Down Expand Up @@ -222,8 +223,101 @@ contract IBCPacket is IBCStore, IIBCPacket {
delete commitments[packetCommitmentKey];
}

function hashString(string memory s) private pure returns (bytes32) {
return keccak256(abi.encodePacked(s));
function timeoutPacket(IBCMsgs.MsgTimeoutPacket calldata msg_) external {
Channel.Data storage channel = channels[msg_.packet.source_port][msg_.packet.source_channel];
require(channel.state == Channel.State.STATE_OPEN, "channel state must be OPEN");

require(
hashString(msg_.packet.destination_port) == hashString(channel.counterparty.port_id),
"packet destination port doesn't match the counterparty's port"
);
require(
hashString(msg_.packet.destination_channel) == hashString(channel.counterparty.channel_id),
"packet destination channel doesn't match the counterparty's channel"
);

ConnectionEnd.Data storage connection = connections[channel.connection_hops[0]];
require(bytes(connection.client_id).length != 0, "connection not found");
ILightClient client = ILightClient(clientImpls[connection.client_id]);
{
uint64 proofTimestamp;
(Height.Data memory latestHeight, bool found) = client.getLatestHeight(connection.client_id);
require(found, "clientState not found");
(proofTimestamp, found) = client.getTimestampAtHeight(connection.client_id, latestHeight);
require(found, "consensusState not found");

require(
(msg_.packet.timeout_height.isZero() || msg_.proofHeight.lt(msg_.packet.timeout_height))
&& (msg_.packet.timeout_timestamp == 0 || proofTimestamp < msg_.packet.timeout_timestamp),
"packet timeout has not been reached for height or timestamp"
);
}

{
bytes32 commitment = commitments[IBCCommitment.packetCommitmentKey(
msg_.packet.source_port, msg_.packet.source_channel, msg_.packet.sequence
)];
// NOTE: if false, this indicates that the timeoutPacket already been executed
require(commitment != bytes32(0), "packet commitment not found");
require(
commitment
== keccak256(
abi.encodePacked(
sha256(
abi.encodePacked(
msg_.packet.timeout_timestamp,
msg_.packet.timeout_height.revision_number,
msg_.packet.timeout_height.revision_height,
sha256(msg_.packet.data)
)
)
)
),
"commitment bytes are not equal"
);
}

if (channel.ordering == Channel.Order.ORDER_ORDERED) {
// check that packet has not been received
require(msg_.nextSequenceRecv <= msg_.packet.sequence, "packet sequence > next receive sequence");
require(
client.verifyMembership(
connection.client_id,
msg_.proofHeight,
connection.delay_period,
calcBlockDelay(connection.delay_period),
msg_.proof,
connection.counterparty.prefix.key_prefix,
IBCCommitment.nextSequenceRecvCommitmentPath(
msg_.packet.destination_port, msg_.packet.destination_channel
),
uint64ToBigEndianBytes(msg_.nextSequenceRecv)
),
"failed to verify next sequence receive"
);
channel.state = Channel.State.STATE_CLOSED;
} else if (channel.ordering == Channel.Order.ORDER_UNORDERED) {
require(
client.verifyNonMembership(
connection.client_id,
msg_.proofHeight,
connection.delay_period,
calcBlockDelay(connection.delay_period),
msg_.proof,
connection.counterparty.prefix.key_prefix,
IBCCommitment.packetReceiptCommitmentPath(
msg_.packet.destination_port, msg_.packet.destination_channel, msg_.packet.sequence
)
),
"failed to verify packet receipt absense"
);
} else {
revert("unknown ordering type");
}

delete commitments[IBCCommitment.packetCommitmentKey(
msg_.packet.source_port, msg_.packet.source_channel, msg_.packet.sequence
)];
}

/* Verification functions */
Expand Down Expand Up @@ -275,4 +369,16 @@ contract IBCPacket is IBCStore, IIBCPacket {
}
return blockDelay;
}

function hashString(string memory s) private pure returns (bytes32) {
return keccak256(abi.encodePacked(s));
}

function uint64ToBigEndianBytes(uint64 v) private pure returns (bytes memory) {
bytes memory b = new bytes(8);
for (uint256 i = 0; i < 8; i++) {
b[i] = bytes1(uint8(v >> (8 * (7 - i))));
}
return b;
}
}
10 changes: 10 additions & 0 deletions contracts/core/04-channel/IIBCChannel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,14 @@ interface IIBCPacket {
* It will also increment NextSequenceAck in case of ORDERED channels.
*/
function acknowledgePacket(IBCMsgs.MsgPacketAcknowledgement calldata msg_) external;

/**
* @dev TimeoutPacket is called by a module which originally attempted to send a
* packet to a counterparty module, where the timeout height has passed on the
* counterparty chain without the packet being committed, to prove that the
* packet can no longer be executed and to allow the calling module to safely
* perform appropriate state transitions. Its intended usage is within the
* ante handler.
*/
function timeoutPacket(IBCMsgs.MsgTimeoutPacket calldata msg_) external;
}
2 changes: 2 additions & 0 deletions contracts/core/05-port/IIBCModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ interface IIBCModule {
function onRecvPacket(Packet.Data calldata, address relayer) external returns (bytes memory);

function onAcknowledgementPacket(Packet.Data calldata, bytes calldata acknowledgement, address relayer) external;

function onTimeoutPacket(Packet.Data calldata, address relayer) external;
}
2 changes: 1 addition & 1 deletion contracts/core/24-host/IBCCommitment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ library IBCCommitment {
return keccak256(packetAcknowledgementCommitmentPath(portId, channelId, sequence));
}

function packetReceiptCommitmentKey(string memory portId, string memory channelId, uint64 sequence)
function packetReceiptCommitmentKey(string calldata portId, string calldata channelId, uint64 sequence)
internal
pure
returns (bytes32)
Expand Down
7 changes: 7 additions & 0 deletions contracts/core/25-handler/IBCMsgs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,11 @@ library IBCMsgs {
bytes proof;
Height.Data proofHeight;
}

struct MsgTimeoutPacket {
Packet.Data packet;
bytes proof;
Height.Data proofHeight;
uint64 nextSequenceRecv;
}
}
9 changes: 9 additions & 0 deletions contracts/core/25-handler/IBCPacketHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ abstract contract IBCPacketHandler is Context, ModuleManager {
string destinationPortId, string destinationChannel, uint64 sequence, bytes acknowledgement
);
event AcknowledgePacket(Packet.Data packet, bytes acknowledgement);
event TimeoutPacket(Packet.Data packet);

constructor(address _ibcPacket) {
require(Address.isContract(_ibcPacket), "address must be contract");
Expand Down Expand Up @@ -103,4 +104,12 @@ abstract contract IBCPacketHandler is Context, ModuleManager {
require(success);
emit AcknowledgePacket(msg_.packet, msg_.acknowledgement);
}

function timeoutPacket(IBCMsgs.MsgTimeoutPacket calldata msg_) external {
IIBCModule module = lookupModuleByChannel(msg_.packet.source_port, msg_.packet.source_channel);
(bool success,) = ibcPacket.delegatecall(abi.encodeWithSelector(IIBCPacket.timeoutPacket.selector, msg_));
require(success);
module.onTimeoutPacket(msg_.packet, _msgSender());
emit TimeoutPacket(msg_.packet);
}
}
2 changes: 1 addition & 1 deletion tests/foundry/src/IBC.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ contract IBCTest is Test {
}

function setUpMockApp() internal {
mockApp = new MockApp();
mockApp = new MockApp(address(handler));
handler.bindPort(MOCK_PORT_ID, address(mockApp));
handler.claimCapabilityDirectly(handler.channelCapabilityPath(MOCK_PORT_ID, "channel-0"), address(mockApp));
handler.claimCapabilityDirectly(handler.channelCapabilityPath(MOCK_PORT_ID, "channel-0"), address(this));
Expand Down
53 changes: 13 additions & 40 deletions tests/foundry/src/MockApp.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,28 @@
pragma solidity ^0.8.9;

import "../../../contracts/proto/Channel.sol";
import "../../../contracts/core/05-port/IIBCModule.sol";
import "../../../contracts/core/25-handler/IBCHandler.sol";
import "../../../contracts/core/24-host/IBCHost.sol";
import "../../../contracts/apps/commons/IBCAppBase.sol";
import "@openzeppelin/contracts/utils/Context.sol";

contract MockApp is IIBCModule {
contract MockApp is IBCAppBase {
event MockRecv(bool ok);

/// Module callbacks ///
address immutable ibcAddr;

function onRecvPacket(Packet.Data calldata, address) external virtual override returns (bytes memory) {
emit MockRecv(true);
return bytes("1");
constructor(address ibcAddr_) {
ibcAddr = ibcAddr_;
}

function onAcknowledgementPacket(Packet.Data calldata packet, bytes calldata acknowledgement, address relayer)
external
virtual
override
{}

function onChanOpenInit(
Channel.Order,
string[] calldata,
string calldata,
string calldata channelId,
ChannelCounterparty.Data calldata,
string calldata
) external virtual override {}

function onChanOpenTry(
Channel.Order,
string[] calldata,
string calldata,
string calldata channelId,
ChannelCounterparty.Data calldata,
string calldata,
string calldata
) external virtual override {}

function onChanOpenAck(string calldata portId, string calldata channelId, string calldata counterpartyVersion)
external
virtual
override
{}

function onChanOpenConfirm(string calldata portId, string calldata channelId) external virtual override {}
function ibcAddress() public view virtual override returns (address) {
return ibcAddr;
}

function onChanCloseInit(string calldata portId, string calldata channelId) external virtual override {}
/// Module callbacks ///

function onChanCloseConfirm(string calldata portId, string calldata channelId) external virtual override {}
function onRecvPacket(Packet.Data calldata, address) external virtual override onlyIBC returns (bytes memory) {
emit MockRecv(true);
return bytes("1");
}
}

0 comments on commit f233a92

Please sign in to comment.