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 는 예측할 수 없는 유저들의 행동 불확실성을 의미합니다.