삽질하는플머

'eosio'에 해당되는 글 1건

  1. 블럭체인 (eosio) 환경에서 랜덤값 고민

블럭체인 (eosio) 환경에서 랜덤값 고민

탐구생활/Others

블럭체인을 정의하는 표현은 여러가지가 있지만, "장부의 공유 시스템"이라는 말이 가장 와 닿는듯 하다. 

모든 노드는 동일한 정보를 재생하여 동일한 결과를 남기며, 이는 체인의 가상머신위에서 구동되는 스마트 컨트랙트에도 해당된다. 

A 노드에서 실행된 스마트 컨트랙트의 결과는 B 노드에서도 동일해야 한다는 이야기이다. 


eosio 는 스마트 컨트랙트로 웹어셈블리를 사용하고 있다는 점 때문에 아주아주 마음에 드는 체인이다. 

예전 글에서 가지고 놀았던 바로 그 LLVM 환경을 제공한다. 

처음에 컨트랙트 개발언어가 C++이라고 들었을때는 이뭥미~~?? 컨트랙트를 네이티브로 돌리나?? 싶었는데, WASM 이라는 말에 단번에 수긍해버렸다. 

게다가 이더와 비교불가한 트랜젝션 속도까지... 한 때 게임밥을 먹었던 사람으로서는 참 매력적인 환경이 아닐 수 없다.


게임에서 가장 중요한 것은 무엇일까. 

고전적인 "가위/바위/보" 만 떠올려도 상대방이 무엇을 낼지 모른다는 기대감, 결과를 모르기 때문에 생기는 긴장감 아닐까. 

이런 긴장감을 표현하는 핵심은?? 결국 "랜덤" 함수이다. 


문제는... 블럭체인의 컨트랙트 입장에서 "랜덤" 은 여러가지로 곤란한 의미를 가진다는 것이다. 

앞에서 언급했던 장부의 공유라는 문제를 되짚어보자. A 노드에서 생성한 랜던값은 B 노드도 동일하게 나와야 한다. 

먼 미래에 체인에 참여한 노드가 로그를 리플레이해서 램을 구성할 때도 당연히 동일한 결과가 나와야 한다. 

노드를 실행하는 물리적 컴퓨터 하나에서 돌린 std::rand() 함수의 결과가 다른 노드의 값과 동일하다고 보장할 수 있을까? 

그랬다면 세상 모든 개인키는 예측 가능하고 블럭체인도 성립할 수 없었겠지. 


그렇다면 eosio 의 컨트랙트에서는 어떨까. 

명색이 C++ 환경인데 rand(), std::rand() 가 동작하기는 할까. 


hello.cpp

#include <eosiolib/eosio.hpp>


using namespace eosio;


class hello : public eosio::contract {

  public:

      using contract::contract;


      void hi( account_name user ) {

         print( "Hello, ", name{user} );

         print(std::rand()); // test 1 !!

         print(rand());      // test 2 !!

      }

};




당연한 이야기겠지만, 링크조차 되지 않는다. 


$ eosiocpp -o hello.wast hello.cpp


hello.cpp:11:15: error: no member named 'rand' in namespace 'std'

         print(std::rand());

               ~~~~~^

hello.cpp:12:9: error: use of undeclared identifier 'rand'

         print(rand());

               ^

2 errors generated.



여기에 추가로, 채굴 기반의 블럭체인인 경우'악의를 가지는 노드의 어뷰징' 문제도 고민해야 한다. 

다행히 DPOS 체계인 eos 는 블럭 생성이 BP에게 위임되기 때문에 이 문제에서는 어느정도 안전하다고 볼 수 있다. 

BP가 악의를 가지고 VM을 오염시켜도 해당 트랜젝션이 컨펌되기 어렵고, 합의를 망가뜨리는 이런 행위는 헌법과 투표로 제재받을 수 있다. 



랜덤에 대한 비슷한 고민을 다른 dApp 들은 어떻게 풀었을까. 

EOS 기반의 복권인 https://eosshishicai.com/ 에서는 특정 블럭의 해시값을 모아서 당첨번호로 사용한다. 즉 당첨자 선정이 컨트랙트 외부에서 일어난다.

요즘 핫한 EOS Knights 의 컨트랙트에는 거래 및 캐릭터 상태 외에 전투에 대한 처리는 보이지 않는다. 독립 서버가 담당하는 것으로 판단된다. 


좋은 해결방법이지만 이 글의 고민과는 맞지 않는다. 

컨트랙트의 액션 실행 시점에서 모든 노드에서 동일한 값을 가지는 랜덤함수를 고민해 보도록 하자. 



게임의 핵심이 랜덤이라면, 랜덤의 핵심은 "시드값"이다. 

컨트랙트가 참조할만한 체인 내 정보들은 어떤것이 있는지 살펴보자. 



1. now(). 

https://developers.eos.io/eosio-cpp/reference#now

1970년부터 잰 초단위 시간, 즉 UNIX TIMESTAMP 이다. 블럭이 실행되는 시점이므로 모든 노드가 일치한 값을 가진다. 


2. get_active_producers()

https://developers.eos.io/eosio-cpp/reference#get_active_producers

실행시점의 블럭 프로듀서 배열이다. BP간 경쟁이 커지면 자주 바뀌겠지만 현재는 큰 변화가 없다. 


3. tapos_block_num(), tapos_block_prefix()

https://developers.eos.io/eosio-cpp/reference#tapos_block_num

현재 실행중인 블럭의 번호와 프리픽스. 입맛당기는 값이지만, 분리된 트랜젝션에서도 동일값을 리턴하는 문제가 있다. 

헤드블럭이 아닌 비가역블럭의 값으로 판단된다.
블럭 아이디의 첫 2바이트는 실제 블럭번호를 2바이트로 캐스팅한 값이고, 뒤따라오는 4바이트는 블럭 프리픽스의 역순 배열이다.  

블럭번호값이 65535마다 오버플로한다는 것에 유의해야 할 것이다. 



4. 시스템 컨트랙트의 테이블.

global 의 "total_ram_stake", "total_activated_stake" 등과 rammarket 의 RAMCORE 값. 

큰 변화는 없지만 예측이 어려우므로 시드의 보조값으로 버무리기에 쓸만하다. 



일단 1과 3을 이용해 다음과 같은 코드를 만들어 보았다. 랜덤 알고리즘은 나무위키에서 선형 합동법 코드를 줏어다 사용했다.


...

 #include <eosiolib/transaction.hpp>

...


static uint32_t next = 1;


int32_t eos_rand(void)

{

    next = next * 1103515245 + 12345;

    return (uint32_t)(next>>16) & 0x7fff;

}


void eos_srand(uint32_t seed)

{

    next = seed;

}


......


// hi 액션을 수정. 랜덤값 출력. 


  auto seedValue = tapos_block_prefix() * tapos_block_num();

  eos_srand((uint32_t) (seedValue * now()));


  print( eos_rand(), " ", seedValue, " ", now() );




정글넷의 "test.oranke" 계정에 컨트랙트를 올리고 테스트. 


$ cleos push action test.oranke hi '["oranke"]' -p oranke

...

#  test.oranke  <= test.oranke::hi             {"user":"oranke"}

>> 23137 244695216 1535182824


$ cleos push action test.oranke hi '["oranke"]' -p oranke

...

#  test.oranke  <= test.oranke::hi             {"user":"oranke"}

>> 13367 851558428 1535182834


$ cleos push action test.oranke hi '["oranke"]' -p oranke

...

#  test.oranke  <= test.oranke::hi             {"user":"oranke"}

>> 10456 968781540 1535182838


$ cleos push action test.oranke hi '["oranke"]' -p oranke

...

#  test.oranke  <= test.oranke::hi             {"user":"oranke"}

>> 27294 968781540 1535182839


$ cleos push action test.oranke hi '["oranke"]' -p oranke

...

#  test.oranke  <= test.oranke::hi             {"user":"oranke"}

>> 12273 968781540 1535182842



액션 실행시간이 1초 이상 떨어져도 tapos_block_prefix() * tapos_block_num() 값이 동일하게 나오는 경우를 볼 수 있다. (붉은색)

now()값을 더해 시드값에 더해 그럭저럭 쓸만한 결과를 얻었다. 컨트랙트의 RAM에 이전 시드값을 저장하는 방식으로 보완한다면 좀 더 유용할 것이다. 

RAM, 즉 테이블까지 진도 나가기엔 체력이 딸리니... 이 글은 여기까지.


모쪼록 eos 로 즐길만한 게임들이 많이 출시되었으면 한다.