Skip to content

Commit

Permalink
nico/split channel handshake api again (#66)
Browse files Browse the repository at this point in the history
* channel handshake: split open-init and open-try

* channel handshake: split open-ack and open-confirm
  • Loading branch information
nicopernas authored Mar 21, 2024
1 parent 4388051 commit cda477d
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 287 deletions.
193 changes: 116 additions & 77 deletions contracts/core/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,42 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable {
}

/**
* This func is called by a 'relayer' on behalf of a dApp. The dApp should be implements IbcChannelHandler.
* The dApp should implement the onOpenIbcChannel method to handle one of the first two channel handshake methods,
* ie. ChanOpenInit or ChanOpenTry.
* If callback succeeds, the dApp should return the selected version, and an emitted event will be relayed to the
* IBC/VIBC hub chain.
* This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's
* onChanOpenInit. If the callback succeeds, the dApp should return the selected version and the emitted event
* will be relayed to the IBC/VIBC hub chain.
*/
function openIbcChannel(
IbcChannelReceiver portAddress,
function channelOpenInit(
IbcChannelReceiver receiver,
string calldata version,
ChannelOrder ordering,
bool feeEnabled,
string[] calldata connectionHops,
string calldata counterpartyPortId
) external {
if (bytes(counterpartyPortId).length == 0) {
revert IBCErrors.invalidCounterPartyPortId();
}

(bool success, bytes memory data) = _callIfContract(
address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenInit.selector, version)
);

if (success) {
emit ChannelOpenInit(
address(receiver), abi.decode(data, (string)), ordering, feeEnabled, connectionHops, counterpartyPortId
);
} else {
emit ChannelOpenInitError(address(receiver), data);
}
}

/**
* This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's
* onChanOpenTry. If the callback succeeds, the dApp should return the selected version and the emitted event
* will be relayed to the IBC/VIBC hub chain.
*/
function channelOpenTry(
IbcChannelReceiver receiver,
CounterParty calldata local,
ChannelOrder ordering,
bool feeEnabled,
Expand All @@ -88,28 +116,19 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable {
revert IBCErrors.invalidCounterPartyPortId();
}

if (Ibc._isChannelOpenTry(counterparty)) {
consensusStateManager.verifyMembership(
proof,
Ibc.channelProofKey(local.portId, local.channelId),
Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty)
);
}
consensusStateManager.verifyMembership(
proof,
Ibc.channelProofKey(local.portId, local.channelId),
Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty)
);

(bool success, bytes memory data) = _callIfContract(
address(portAddress),
abi.encodeWithSelector(
IbcChannelReceiver.onOpenIbcChannel.selector,
local.version,
ordering,
feeEnabled,
connectionHops,
counterparty
)
address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenTry.selector, counterparty.version)
);

if (success) {
emit OpenIbcChannel(
address(portAddress),
emit ChannelOpenTry(
address(receiver),
abi.decode(data, (string)),
ordering,
feeEnabled,
Expand All @@ -118,58 +137,72 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable {
counterparty.channelId
);
} else {
emit OpenIbcChannelError(address(portAddress), data);
emit ChannelOpenTryError(address(receiver), data);
}
}

/**
* This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the onOpenIbcChannel event.
* The dApp should implement the onConnectIbcChannel method to handle the last two channel handshake methods, ie.
* ChanOpenAck or ChanOpenConfirm.
* This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event.
* The dApp should implement the onChannelConnect method to handle the third channel handshake method: ChanOpenAck
*/
function connectIbcChannel(
IbcChannelReceiver portAddress,
function channelOpenAck(
IbcChannelReceiver receiver,
CounterParty calldata local,
string[] calldata connectionHops,
ChannelOrder ordering,
bool feeEnabled,
bool isChanConfirm,
CounterParty calldata counterparty,
Ics23Proof calldata proof
) external {
_verifyConnectIbcChannelProof(local, connectionHops, ordering, isChanConfirm, counterparty, proof);
consensusStateManager.verifyMembership(
proof,
Ibc.channelProofKey(local.portId, local.channelId),
Ibc.channelProofValue(ChannelState.ACK_PENDING, ordering, local.version, connectionHops, counterparty)
);

(bool success, bytes memory data) = _callIfContract(
address(portAddress),
abi.encodeWithSelector(
IbcChannelReceiver.onConnectIbcChannel.selector,
local.channelId,
counterparty.channelId,
counterparty.version
)
address(receiver),
abi.encodeWithSelector(IbcChannelReceiver.onChanOpenAck.selector, local.channelId, counterparty.version)
);

if (success) {
emit ConnectIbcChannel(address(portAddress), local.channelId);

// Register port and channel mapping
// TODO: check duplicated channel registration?
// TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase
// Then `connectIbcChannel` phase can use the `version` as part of `require` condition.
portChannelMap[address(portAddress)][local.channelId] = Channel(
counterparty.version, // TODO: this should be self version instead of counterparty version
ordering,
feeEnabled,
connectionHops,
counterparty.portId,
counterparty.channelId
);
_connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty);
emit ChannelOpenAck(address(receiver), local.channelId);
} else {
emit ChannelOpenAckError(address(receiver), data);
}
}

// initialize channel sequences
nextSequenceSend[address(portAddress)][local.channelId] = 1;
nextSequenceRecv[address(portAddress)][local.channelId] = 1;
nextSequenceAck[address(portAddress)][local.channelId] = 1;
/**
* This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event.
* The dApp should implement the onChannelConnect method to handle the last channel handshake method:
* ChannelOpenConfirm
*/
function channelOpenConfirm(
IbcChannelReceiver receiver,
CounterParty calldata local,
string[] calldata connectionHops,
ChannelOrder ordering,
bool feeEnabled,
CounterParty calldata counterparty,
Ics23Proof calldata proof
) external {
consensusStateManager.verifyMembership(
proof,
Ibc.channelProofKey(local.portId, local.channelId),
Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ordering, local.version, connectionHops, counterparty)
);

(bool success, bytes memory data) = _callIfContract(
address(receiver),
abi.encodeWithSelector(IbcChannelReceiver.onChanOpenConfirm.selector, local.channelId, counterparty.version)
);

if (success) {
_connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty);
emit ChannelOpenConfirm(address(receiver), local.channelId);
} else {
emit ConnectIbcChannelError(address(portAddress), data);
emit ChannelOpenConfirmError(address(receiver), data);
}
}

Expand Down Expand Up @@ -504,39 +537,45 @@ contract Dispatcher is IbcDispatcher, IbcEventsEmitter, Ownable {
emit SendPacket(sender, channelId, packet, sequence, timeoutTimestamp);
}

function _verifyConnectIbcChannelProof(
function _connectChannel(
IbcChannelReceiver portAddress,
CounterParty calldata local,
string[] calldata connectionHops,
ChannelOrder ordering,
bool isChanConfirm,
CounterParty calldata counterparty,
Ics23Proof calldata proof
bool feeEnabled,
CounterParty calldata counterparty
) internal {
consensusStateManager.verifyMembership(
proof,
Ibc.channelProofKey(local.portId, local.channelId),
Ibc.channelProofValue(
isChanConfirm ? ChannelState.CONFIRM_PENDING : ChannelState.ACK_PENDING,
ordering,
local.version,
connectionHops,
counterparty
)
// Register port and channel mapping
// TODO: check duplicated channel registration?
// TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase
// Then `connectIbcChannel` phase can use the `version` as part of `require` condition.
portChannelMap[address(portAddress)][local.channelId] = Channel(
counterparty.version, // TODO: this should be self version instead of counterparty version
ordering,
feeEnabled,
connectionHops,
counterparty.portId,
counterparty.channelId
);

// initialize channel sequences
nextSequenceSend[address(portAddress)][local.channelId] = 1;
nextSequenceRecv[address(portAddress)][local.channelId] = 1;
nextSequenceAck[address(portAddress)][local.channelId] = 1;
}

// Returns the result of the call if no revert, otherwise returns the error if thrown.
function _callIfContract(address portAddress, bytes memory args)
function _callIfContract(address receiver, bytes memory args)
internal
returns (bool success, bytes memory message)
{
if (!Address.isContract(portAddress)) {
if (!Address.isContract(receiver)) {
return (false, bytes("call to non-contract"));
}
// Only call if we are sure portAddress is a contract
// Only call if we are sure receiver is a contract
// Note: This tx won't revert if the low-level call fails, see
// https://docs.soliditylang.org/en/latest/cheatsheet.html#members-of-address
(success, message) = portAddress.call(args);
(success, message) = receiver.call(args);
}

// _isPacketTimeout returns true if the given packet has timed out acoording to host chain's block height and
Expand Down
65 changes: 37 additions & 28 deletions contracts/core/UniversalChannelHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,6 @@ contract UniversalChannelHandler is IbcReceiverBase, IbcUniversalChannelMW {
dispatcher.closeIbcChannel(channelId);
}

// IBC callback functions
function onConnectIbcChannel(bytes32 channelId, bytes32, string calldata counterpartyVersion)
external
onlyIbcDispatcher
{
if (keccak256(abi.encodePacked(counterpartyVersion)) != keccak256(abi.encodePacked(VERSION))) {
revert UnsupportedVersion();
}
connectedChannels.push(channelId);
}

function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher {
// logic to determin if the channel should be closed
bool channelFound = false;
Expand Down Expand Up @@ -149,23 +138,43 @@ contract UniversalChannelHandler is IbcReceiverBase, IbcUniversalChannelMW {
mwStackAddrs[mwBitmap] = mwAddrs;
}

function onOpenIbcChannel(
string calldata version,
ChannelOrder,
bool,
string[] calldata,
CounterParty calldata counterparty
) external view onlyIbcDispatcher returns (string memory selectedVersion) {
if (counterparty.channelId == bytes32(0)) {
// ChanOpenInit
if (keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION))) {
revert UnsupportedVersion();
}
} else {
// ChanOpenTry
if (keccak256(abi.encodePacked(counterparty.version)) != keccak256(abi.encodePacked(VERSION))) {
revert UnsupportedVersion();
}
// IBC callback functions
function onChanOpenAck(bytes32 channelId, string calldata counterpartyVersion) external onlyIbcDispatcher {
_connectChannel(channelId, counterpartyVersion);
}

function onChanOpenConfirm(bytes32 channelId, string calldata counterpartyVersion) external onlyIbcDispatcher {
_connectChannel(channelId, counterpartyVersion);
}

function onChanOpenInit(string calldata version)
external
view
onlyIbcDispatcher
returns (string memory selectedVersion)
{
return _openChannel(version);
}

function onChanOpenTry(string calldata counterpartyVersion)
external
view
onlyIbcDispatcher
returns (string memory selectedVersion)
{
return _openChannel(counterpartyVersion);
}

function _connectChannel(bytes32 channelId, string calldata version) private {
if (keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION))) {
revert UnsupportedVersion();
}
connectedChannels.push(channelId);
}

function _openChannel(string calldata version) private pure returns (string memory selectedVersion) {
if (keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION))) {
revert UnsupportedVersion();
}
return VERSION;
}
Expand Down
Loading

0 comments on commit cda477d

Please sign in to comment.