[멋쟁이 사자처럼 블록체인 스쿨 3기] 23-07-03

임형석·2023년 7월 3일
0

VRF


체인링크의 VRF 란?

체인링크 공식 사이트에서는 아래와 같이 VRF 를 소개 하고있다.

체인링크 VRF는 검증가능한 랜덤 펑션을 통해 온체인에서 검증할 수 있는 무작위성(랜덤성)을 생성할 수 있는 기능입니다.

체인링크는 아래와 같은 방식으로 랜덤한 값을 만들어낸다고 설명하고 있다.

  1. 스마트 컨트랙트 애플리케이션이 무작위성을 요청
  1. 체인링크가 무작위성을 생성해 VRF 컨트랙트에 증거 전송
  1. VRF 컨트랙트가 무작위성 검증
  1. 스마트 컨트랙트 애플리케이션이 검증된 무작위성 수신

한마디로, 체인링크는 오라클이 예측할 수 없는 Seed 를 생성 및 제공하고, 체인링크는 이 seed 가 유효한지 검증까지 해줌으로써 무작위성을 증명한다.
그리고 검증된 seed 를 통해 검증된 무작위한 값을 만들어낼 수 있게 된다.


VRF 사용

체인링크를 사용하기 위해, 세폴리아 테스트 네트워크의 체인링크 테스트 코인을 받는다.

Link sepolia faucet

테스트 코인을 받았다면, Create subscribe. 구독 설정을 한다.

Create subscribe

구독 설정을 했다면, 아래 사진의 fund Subscribe 를 클릭하고,

받았던 테스트 코인을 여기에 넣어준다.


다음, 아래의 코드를 Remix 에 붙여 넣어준다.

// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

contract VRFv2Consumer is VRFConsumerBaseV2, ConfirmedOwner {
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */
    VRFCoordinatorV2Interface COORDINATOR;

    uint64 s_subscriptionId;

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    bytes32 keyHash = "yr key hash";

    uint32 callbackGasLimit = 100000;
    uint16 requestConfirmations = 3;
    uint32 numWords = 2;

    constructor(uint64 subscriptionId) VRFConsumerBaseV2(0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625) ConfirmedOwner(msg.sender) {
        COORDINATOR = VRFCoordinatorV2Interface(0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625);
        s_subscriptionId = subscriptionId;
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords()
        external
        onlyOwner
        returns (uint256 requestId)
    {
        // Will revert if subscription is not set and funded.
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }

    function getRequestStatus(
        uint256 _requestId
    ) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }
}

keyHash 그리고, constructor 의 주소 값을 상황에 알맞게 넣어주어야 한다.

아래의 체인링크 공식 문서에서 확인할 수 있다.

ChainLink network config

VRF coordinator 는 constructor 안에.

30 gwei key hash 는 keyHash 안에 입력해준다.


컨트랙트 수정이 완료 되었다면, 배포한다.

배포 시의 입력 값은 자신의 ID 값을 넣으면 된다. (3366)


배포된 컨트랙트 주소를 복사한 후, Subscribe 아래의 consumers 설정에서 배포한 컨트랙트 주소를 넣고 Add consumer 버튼을 눌러준다.


트랜잭션이 보내지고 컨펌되면, remix 로 돌아와서 아래의 requestRandomWords 함수를 실행한다.


컨펌되면 아래의 lastRequestId 를 call 해서 값을 가져온다.

이것은 random 값이 아닌 seed 이다.

이 seed 값은 위에서 설명한 값으로, 오라클이 예측할 수 없는 값으로 생성된다.


위에서 반환받은 seed 를 아래의 s_requests 에 넣어 call 해본다.

여기서 나온 fulfilled 값과 bool 값이 true 일때, random 값을 가져올 수 있다.

이 과정을 통해 생성된 seed 는 체인링크를 통해 검증하고,

검증된 seed 로 예측할 수 없는 random 값을 만들어 낼 수 있는 것이다.


getRequestStatus 함수에 위에서 받은 seed 값을 넣고 call 하면 검증된 random 값을 반환 받을 수 있다.


왜 난수 생성에 체인링크의 오라클 서비스를 이용해야 하는지 궁금했다.

검색을 해서 몇가지의 이유를 찾아 정리했다.

첫 번째. 이더리움 블록체인은 투명하고 결정론적이며 검증 가능한 구조이기 때문이다.
모든 노드가 블록체인의 상태에 대해 합의해야 하는 문제로 인해 난수를 생성할 수 없다는 것.

두 번째. 이더리움 블록체인을 사용하는 solidity 에서는 스마트 컨트랙트를 실행할 때, 이에 대한 합의를 도달하기 위해서는 항상 같은 결과값을 도출해야 하는데, 난수를 생성할 경우 이러한 결과값 도출에 문제가 생기기에 불가능하다.

세 번째. solidity 내에서도 block.timestamp, blockhash 와 같은 미래에 정해질 값(현재 알 수 없고, 예측도 할 수 없는 값) 을 seed 로 사용해 난수를 생성할 수도 있다.
하지만 이러한 경우, 채굴자가 블록과 관련된 위치에 있어, Seed 값에 가장 먼저 접근할 수 있고, 이는 난수를 예측하거나 계산하기에 유리한 위치에 있다. 또한, 악의적인 행동을 할 수도 있기에 완벽한 난수 생성이라고 보기 어렵다.

위 세가지 이유 때문에 이더리움 블록체인과 solidity 내에서의 난수 생성은 완벽하지 않다. 따라서 체인링크의 오라클 서비스를 이용한 난수를 생성하는 것이 가장 신뢰성 있다고 볼 수 있다.


0개의 댓글