diff --git a/packages/protocol/contracts/signal/ISignalService.sol b/packages/protocol/contracts/signal/ISignalService.sol index 83a813ac925..2677fb468c3 100644 --- a/packages/protocol/contracts/signal/ISignalService.sol +++ b/packages/protocol/contracts/signal/ISignalService.sol @@ -106,6 +106,23 @@ interface ISignalService { ) external; + /// @notice Verifies if a signal has been received on the target chain. + /// This is the "readonly" version of proveSignalReceived. + /// @param _chainId The identifier for the source chain from which the + /// signal originated. + /// @param _app The address that initiated the signal. + /// @param _signal The signal (message) to send. + /// @param _proof Merkle proof that the signal was persisted on the + /// source chain. + function isSignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + external + view; + /// @notice Verifies if a particular signal has already been sent. /// @param _app The address that initiated the signal. /// @param _signal The signal (message) that was sent. diff --git a/packages/protocol/contracts/signal/SignalService.sol b/packages/protocol/contracts/signal/SignalService.sol index 4bb26d7c8dd..cc7980a9f08 100644 --- a/packages/protocol/contracts/signal/SignalService.sol +++ b/packages/protocol/contracts/signal/SignalService.sol @@ -85,7 +85,7 @@ contract SignalService is EssentialContract, ISignalService { bytes32 _signal, bytes calldata _proof ) - public + external virtual validSender(_app) nonZeroValue(_signal) @@ -132,6 +132,59 @@ contract SignalService is EssentialContract, ISignalService { } } + /// @inheritdoc ISignalService + /// @dev This function may revert. + function isSignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + public + view + validSender(_app) + nonZeroValue(_signal) + { + HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[])); + if (hopProofs.length == 0) revert SS_EMPTY_PROOF(); + + uint64 chainId = _chainId; + address app = _app; + bytes32 signal = _signal; + bytes32 value = _signal; + address signalService = resolve(chainId, "signal_service", false); + + HopProof memory hop; + for (uint256 i; i < hopProofs.length; ++i) { + hop = hopProofs[i]; + + _verifyHopProof(chainId, app, signal, value, hop, signalService); + bool isLastHop = i == hopProofs.length - 1; + + if (isLastHop) { + if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID(); + signalService = address(this); + } else { + if (hop.chainId == 0 || hop.chainId == block.chainid) { + revert SS_INVALID_MID_HOP_CHAINID(); + } + signalService = resolve(hop.chainId, "signal_service", false); + } + + bool isFullProof = hop.accountProof.length != 0; + + bytes32 kind = isFullProof ? LibSignals.STATE_ROOT : LibSignals.SIGNAL_ROOT; + signal = signalForChainData(chainId, kind, hop.blockId); + value = hop.rootHash; + chainId = hop.chainId; + app = signalService; + } + + if (value == 0 || value != _loadSignalValue(address(this), signal)) { + revert SS_SIGNAL_NOT_FOUND(); + } + } + /// @inheritdoc ISignalService function isChainDataSynced( uint64 _chainId, @@ -211,6 +264,7 @@ contract SignalService is EssentialContract, ISignalService { address _signalService ) internal + view virtual validSender(_app) nonZeroValue(_signal)