Qaupot Blog
Software Engineering, Trip

Random On Smart Contract

일반적으로 Smart Contract 에는 Random 이 없습니다. 이는 Smart Contract 를 구성하는 언어적 스펙이라기 보다는, 블록체인 시스템의 특성에 기인합니다.

블록체인 내 노드들은 서로를 믿지 않고, 서로에 대해 검증 절차를 거칩니다. 이 검증을 위해서는 네가 계산한 것과 내가 계산한 것은 동일해야 한다가 성립해야 합니다.

  • 입력되는 Transaction 이 같다면, Transaction 의 처리 결과 역시 같아야 합니다.

Random 은 입력된 Transaction 이 같더라도, 어떤 노드에서, 어떤 시간에 실행했는지에 따라 그 결과가 다르게 나올 수 있게 만듭니다. 따라서, 블록체인은 Random 을 시스템에서 지원하지 않습니다.

문제는 실제 서비스를 만들 때, Random 이 필요한 상황이 다수 존재합니다. 이런 경우, Deterministic 한 방법을 통해서 Input 이 같다면 항상 같은 Random 결과를 얻을 수 있도록 만들어야만 합니다.

Smart Contract 에서 Random 을 만들 수 있는 몇 가지 방법을 소개합니다.

Random By Oracle

Oracle 이 외부에서 random seed 혹은 random value 를 결정하는 방식입니다.

  • 이 방식은 'Oracle' 이라는 존재 자체가 문제가 됩니다. Oracle 은 Random 결과를 마음대로 결정할 수 있습니다.
  • Contract 스스로 완결되지 못 하고, 외부의 Batch 프로세스가 존재하여야만 서비스가 완성됩니다.
    • Oracle 이 Random 을 주입해 주지 않는 시점부터 Contract 는 더 이상 동작하지 않게 되며, 이는 Centralize Service 의 성격을 갖습니다.
  • Governance 를 도입하여 여러 Oracle 간 경쟁하도록 만드는 것으로 일부 문제를 해소할 수 있습니다.

Deterministic Random

Block Hash

bytes32 randomHash = keccak256(abi.encode(uint256(blockhash(block.number - 1))));

가장 널리 사용되는 방식으로, 직전 블록해시를 참조해서 random 값을 결정합니다.

이 방식은 User 혹은 Miner 가 Tx 의 실행 시점을 조절하는 방법으로 원하는 Random 이 나오도록 유도할 가능성이 있습니다.

  • Miner 는 원하는 Block hash 시점이 되었을 때 Tx 를 실행합니다.
  • User 는 원하는 Block hash 시점이 되었을 때 높은 Gas Price 를 Bidding 하여 다음 블록에 포함되도록 유도합니다.
bytes32 randomHash = keccak256(abi.encode(uint256(blockhash(block.number - 3))));

블록 간격을 넓혀 지정할 경우에는, User 는 타이밍을 맞추기 어렵게 되지만, Miner 는 미리 값을 예측할 수 있어 더 쉽게 Tx 조정 시점을 결정할 수 있습니다.

Block Hash + Oracle

bytes32 randomHash = keccak256(abi.encode(uint256(blockhash(block.number - 1)), randomSeed));

Oracle 로 부터 Random Seed 를 Random 하게 주입 받아, 블록 해시와 같이 섞습니다.

  • Oracle 의 존재는 여전히 문제가 됩니다.
  • Miner 역시 여전히 자신이 원하는 시점에 Tx 실행 시점을 조절하는 것으로 Random 을 유도할 수 있습니다.

Recursive

bytes32 randomHash = keccak256(abi.encode(previousRandom));
previousRandom = randomHash;

Tx 를 시행할 때 마다, Random 을 갱신합니다.

  • Miner 는 추가 Tx 를 실행시켜, 원하는 Random 이 나오도록 유도할 수 있습니다.
  • State Write 가 발생하므로, Gas 소모량이 증가합니다.

Block Hash + Recursive + Block / Time Gap

require(gap >= now-previous, "time gap");

bytes32 randomHash = keccak256(abi.encode(uint256(blockhash(block.number - 1)), previousRandom));
previousRandom = randomHash;

Block hash 와 이전 Random 을 조합해서 사용 하면서, 지정된 시간 (블록 수) 가 지난 시점에 Random 이 결정되도록 합니다.

  • 즉시 Random 을 결정하는 방식이 아니므로, 제한된 시나리오에서만 사용할 수 있습니다.
  • State Write 가 발생하므로, Gas 소모량이 증가합니다.

예)

  • 유저들은 자유롭게 Tx를 만들어 전송합니다. 네트워크 내의 Tx 들이 Mining 되는 시점에 따라, 다수의 블록에 걸쳐 Random 영향을 줍니다.
  • Time Gap 시점이 되면, Contract 는 Tx 를 거절합니다.
    • Miner 가 Time Gap 시점의 블록 조작에 성공하였더라도, Time Gap 이 해소되는 타이밍을 맞춰 다시 블록을 생산하기는 매우 어렵습니다. Miner 들은 경쟁하고 있습니다.
  • Time Gap 이 풀리고 나면, 유저의 다음 Tx 가 처리되는 시점에, Random 이 결정됩니다.

Block Hash 및 Time Gap 은 Miner 간의 경쟁, Recursive 는 예측할 수 없는 유저들의 행동 불확실성을 의미합니다.

이 블로그는 개인 블로그입니다. 게시글은 오류를 포함하고 있을 수 있지만, 저자는 오류를 해결하기 위해 노력하고 있습니다.
게시글에 별도의 고지가 없는 경우, 크리에이티브 커먼즈 저작자표시-비영리-변경금지 4.0 라이선스를 따릅니다.

This blog is personal blog. published posts may contain some errors, but author doing efforts to clear errors.
If post have not notice of license, it under creative commons Attribution-NonCommercial-NoDerivatives 4.0.