$RLS

Rayls Relayer 코드를 다시 보다가 이상한 점 하나를 발견했습니다.

Relayer가 메시지를 전달하는 주체라면 "executeMessage()"에도 RELAYER 권한이 있어야 하는 것 아닌가?

그런데 실제 코드를 따라가 보니 아니었습니다.

오히려 Rayls는 메시지를 전달하는 권한과 실제 실행을 분리해 놓고 있었습니다.

Public Chain에서 Relayer가 처음 들어가는 곳은 "PublicRNEndpoint.receivePayload()"입니다.

여기서 메시지를 직접 실행하지 않고,

messageExecutor.executeMessage(...)

로 Executor에게 넘깁니다.

그래서 "RNMessageExecutorV1.executeMessage()"를 확인했습니다.

여기에는 RELAYER 권한이 없습니다.

"MESSAGE_EXECUTOR"도 아닙니다.

대신 "onlyEndpoint"로 보호되어 있습니다.

if (msg.sender != authorizedEndpoint) {

revert UnauthorizedEndpoint(msg.sender);

}

즉 호출 흐름은

Relayer → Endpoint → Executor

로 나뉩니다.

그리고 Executor가 최종 목적지에

to.call(data)

를 실행합니다.

이때 목적지 컨트랙트가 보는 "msg.sender"는 Relayer가 아니라 RNMessageExecutor입니다.

그래서 목적지의 "MESSAGE_EXECUTOR" 권한과 연결됩니다.

여기까지는 코드를 따라가면 확인할 수 있었습니다.

그런데 여기서 또 하나가 궁금해졌습니다.

그렇다면 같은 메시지가 다시 들어오면 어디에서 막을까?

"RNMessageExecutorV1"에는

mapping(bytes32 => bool) public executed;

가 있습니다.

Executor가 "messageId"를 기억하고 이미 처리한 메시지는 다시 실행하지 않는 구조입니다.

여기까지만 보면 흔한 replay protection처럼 보입니다.

그래서 테스트 코드까지 찾아봤습니다.

그리고 이번 조사에서 가장 중요한 부분을 발견했습니다.

Rayls에는

"ReplayProtection_ExecutorSingleSourceOfTruth.t.sol"

이라는 별도의 보안 테스트가 있습니다.

이 테스트는 단순히 같은 메시지를 두 번 보내는 것만 확인하지 않습니다.

같은 "messageId"로 목적지를 바꿔 재실행하는 경우,

그리고 Endpoint 자체를 교체한 뒤 기존 "messageId"를 다시 실행하는 경우까지 테스트합니다.

Endpoint를 바꿔도 결과는 똑같이 차단됩니다.

왜 이게 중요할까요?

재전송 방지의 상태가 Endpoint에 있는 게 아니라 Executor에 있기 때문입니다.

기존 Endpoint에서 메시지를 한 번 실행한 뒤 새로운 Endpoint를 연결해도,

Executor의

executed[messageId]

에는 이미 실행 기록이 남아 있습니다.

따라서 Endpoint가 바뀌어도

“이 메시지는 이미 실행됐다”

라는 판단은 바뀌지 않습니다.

이 테스트를 보고 제가 확인한 건 단순한 replay protection의 존재가 아니었습니다.

Rayls가 cross-chain 메시지 실행의 신뢰 경계를 Executor에 두고 있다는 것입니다.

Endpoint는 메시지를 전달하는 경로이고,

Executor가 실제 실행과 "messageId"의 실행 상태를 가지고 있습니다.

그래서 Endpoint가 교체되는 상황에서도 과거의 실행 기록이 이어집니다.

코드 흐름을 한 줄로 정리하면:

Relayer

→ "receivePayload()"

→ "executeMessage()"

→ "to.call()"

→ "MESSAGE_EXECUTOR"

→ "executed[messageId]"

처음 가졌던 질문,

“Relayer가 메시지를 실행한다면 왜 "executeMessage()"에는 RELAYER 권한이 없지?”

에 대한 답은 명확했습니다.

Rayls는 전달 경로와 실행 주체, 그리고 replay protection의 기준점을 하나로 묶지 않았습니다.

특히 이번에 확인한 “Endpoint를 교체해도 기존 "messageId"는 다시 실행되지 않는다”는 테스트는,

Rayls의 cross-chain 실행 구조를 이해할 때 꽤 중요한 단서라고 생각합니다.

#Rayls