삽질하는플머

파라오 퍼즐 릴리즈

탐구생활/zhLib

짬짬이 끄적이던 코코스2D 기반의 게임을 드디어 릴리즈. 

20년 전 셀빅용으로 만들었던 '파라오'의 리메이크임. 

 

딱 한 장 남아있는 당시 스샷.

 

리메이크 스샷. 

 

디자인은 20년 전 물건이 훨씬 낫다는 게 한결같은 주위의 평가. 

레벨은 넉넉하게 2,200개 준비함. 

 

코코스2D 위에 OpenGL-ES로 구성했는데...
만드는 중에 iOS에서 OpenGL이 Deprecated!!
메탈 API를 공부하거나 MoltenGL을 구매하기 전 까지

안드로이드용만 올리기로 함. 

 

광고버전은 여기

상용버전은 여기. (GMT 기준 2020.5.19 ~ 2020.5.26 까지 0원으로 할인중.) 

 

 

emscripten + cocos2d?

탐구생활/WEB 관련

취미로 cocos2d를 UI 엔진으로 쓰고 뼈대는 opengl es를 사용하는 삽질을 하고 있다. 

opengl 코드를 디버깅/웹퍼블리싱 하려고 GLFW를 버무리는 방법은 지난번에 정리 해 두었는데...

 

Emscripten 가지고 놀기 1. (feat. CodeLite)

Emscripten 가지고 놀기 2. (feat. GLFW, ZLib) 

Emscripten 가지고 놀기 3. (feat. event.c) 

 

 

이걸로 테스트삼아 웹버전 퍼즐 편집기를 만들었더니 

https://oranke.github.io/pharaoh-editor/

https://github.com/oranke/pharaoh-editor

 

모바일 브라우저에서는 전혀 동작하지 않는다. 터치관련 리스너가 구현되지 않았더라는...

 

어찌어찌 GLFW 를 수정해 돌아가게 했었고, 혼자 쓰기 아까워 PR을 보냈더니 오늘 머지되었다. 
https://github.com/emscripten-core/emscripten/pull/10428

 

삽질은 길어도 코드는 짧은데 AUTHORS 에도 기록해주네. (되게 기분 좋음) 

https://github.com/emscripten-core/emscripten/blob/master/AUTHORS#L458

 

 

아무튼 PR 보내느라 emscripten 최신 버전을 내려받아 잠시 돌려보았는데, 눈이 커지는 정보가 보인다. 

 

$ emcc --show-ports 
Available ports: 
    Boost headers v1.70.0 (USE_BOOST_HEADERS=1; Boost license) 
    icu (USE_ICU=1; Unicode License) 
    zlib (USE_ZLIB=1; zlib license) 
    bzip2 (USE_BZIP2=1; BSD license) 
    libjpeg (USE_LIBJPEG=1; BSD license) 
    libpng (USE_LIBPNG=1; zlib license) 
    SDL2 (USE_SDL=2; zlib license) 
    SDL2_image (USE_SDL_IMAGE=2; zlib license) 
    SDL2_gfx (zlib license) 
    ogg (USE_OGG=1; zlib license) 
    vorbis (USE_VORBIS=1; zlib license) 
    SDL2_mixer (USE_SDL_MIXER=2; zlib license) 
    bullet (USE_BULLET=1; zlib license) 
    freetype (USE_FREETYPE=1; freetype license) 
    harfbuzz (USE_HARFBUZZ=1; MIT license) 
    SDL2_ttf (USE_SDL_TTF=2; zlib license) 
    SDL2_net (zlib license) 
    cocos2d 
    regal (USE_REGAL=1; Regal license)

 

저게뭐야? 코코스가 포팅되어있었네??

 

레포지토리는 여기. 

https://github.com/emscripten-ports/Cocos2d

 

3.0 alpha 버전 기준이라고 한다. 2017년이면 한참 가지고 놀던 시점인데 왜 몰랐을까. 

다른건 몰라도 씬과 액션관리는 배울게 참 많은 엔진인데 ...

 

일단 킵 해두고 다음에 여유될 때 파보기로 함. 

 

nodejs 모듈 추천 : mmap-io

탐구생활/node.js

최근 사용해본 노드 모듈중 개인적으로 제일 감탄한 물건.
https://www.npmjs.com/package/mmap-io

 

 

간단히 말해 "메모리맵드 파일 IO"를 nodejs 에서 가능하게 하는 놈이다.

 

윈도와 리눅스에서 동작 확인했고 맥은 귀찮아서 안해봤다.
알맹이는 C로 되어있는데... posix의 mmap() 함수를 윈도에서 CreateFileMapping() 으로 완벽히 에뮬레이트 한다.
원래 두 함수의 개념이 비슷하니까 그럴 수도 있겠지만... 50줄도 안되는 구현체를 실제 알현하는 충격은 크다.

 

어디에 써먹냐고?
IPC, 또는 덩치가 큰 파일을 메모리에 올려야 하는 경우 유용하게 사용할 수 있다.

 

4바이트 부호없는 정수 정보가 3천만개 늘어선 배열을 떠올려보자.
4 * 3천만 = 120메가. 일단 파일로 저장해두었다.


이 배열의 특정 인덱스를 요청받을 때 응답하는 서버를 만든다고 하자.

 

 

데이터를 부르고

 

var data_buffer = fs.readFileSync(dataFile);  

 

4바이트 부호없는 정수 배열로 간주, 지정 인덱스의 정보를 리턴.

 

function readData( index ) { 
    return data_buffer.readUInt32LE( index * 4 ); 
} 

 

이 기능을 가지는 서버는 일단 120메가를 먹고 시작한다.

데이터 부르는 부분을 mmap-io를 써서 이렇게 바꿔보자.

 

const mmap = require('mmap-io');  
const fs = require('fs');  

var fd = fs.openSync(dataFile, 'r');  
var size = fs.fstatSync(fd).size;  

var data_buffer = mmap.map(size, mmap.PROT_READ, mmap.MAP_SHARED, fd);  

 

동작메모리를 살펴보면 상당히 줄어들어있을 것이다.

파일을 메모리에 올리지 않고 매핑한 상태이기 때문이다. 

이 상태에서도 수정 전 읽어서 올린방식과 동일하게 readData() 함수는 정상적으로 돌아간다.

 

seek/read 없이 파일의 어떤 위치건 마치 메모리 블럭처럼 랜덤억세스 할 수 있다는 얘기. 

 

결론은...
DB에 담기 애매한 크기의 정보를 제공해야하는 경우 아주 유용하다는 것.

 

블럭체인 (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 로 즐길만한 게임들이 많이 출시되었으면 한다. 


Ubuntu + MySQL 에서 셸 커맨드 사용

탐구생활/WEB 관련

우분투 16.04 + MySQL 에서 특정 데이터가 입력될 경우 셸 커맨드를 돌려야 할 상황이 생겼다. 
오늘 요리재료는 이럴 때 안성마춤인 lib_mysqludf_sys 되시겠다. 


깃허브 링크: https://github.com/mysqludf/lib_mysqludf_sys 


먼저 MySQL 에서 플러그인 경로를 확인하자. 


mysql> show variables like 'plugin_dir';


+---------------+------------------------+

| Variable_name | Value                  |

+---------------+------------------------+

| plugin_dir    | /usr/lib/mysql/plugin/ |

+---------------+------------------------+




UDF 소스를 내려받고.  


$ git clone https://github.com/mysqludf/lib_mysqludf_sys.git

$ cd lib_mysqludf_sys/



포함된 Makefile 을 참조해 빌드 테스트. -fPIC을 주라는 에러가 나오니 그대로 따른다. 

$ sudo apt-get install libmysqlclient-dev
$ gcc -Wall -I/usr/include/mysql -I. -fPIC  -shared lib_mysqludf_sys.c -o lib_mysqludf_sys.so


빌드 확인한 뒤, Makefile 수정. 

Makefile 

LIBDIR=/usr/lib/mysql/plugin

install:
        gcc -Wall -I/usr/include/mysql -I. -fPIC -shared lib_mysqludf_sys.c -o $(LIBDIR)/lib_mysqludf_sys.so



빌드 및 설치


$ sudo ./install.sh



MySQL 실행 후 테스트. 


mysql> select sys_exec('touch /tmp/test_mysql') ;

+-----------------------------------+

| sys_exec('touch /tmp/test_mysql') |

+-----------------------------------+

|                             32512 |

+-----------------------------------+

1 row in set (0.01 sec)



내부적으로 sys() 함수를 사용하는데 왠 31512 에러... 역시 한 번에 되는 건 하나도 없군. 


구글께 여쭈니 apparmor 에서 UDF 실행을 막는다는 신탁을 내려주심. 

https://www.cyberciti.biz/faq/ubuntu-linux-howto-disable-apparmor-commands/



먼저 apparmor 의 상태를 확인. 


$ sudo apparmor_status


apparmor module is loaded.

16 profiles are loaded.

16 profiles are in enforce mode.

......

   /usr/sbin/mysqld

...



mysqld 를 대상에서 제거


$ sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/

$ sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld


# 상태 다시 확인

# sudo apparmor_status



apparmor 와 mysql 재구동


$ sudo /etc/init.d/mysql restart

$ sudo /etc/init.d/apparmor restart



MySQL 에서 sys_exe() 재시도


mysql> select sys_exec('touch /tmp/test_mysql');

+-----------------------------------+

| sys_exec('touch /tmp/test_mysql') |

+-----------------------------------+

|                                 0 |

+-----------------------------------+

1 row in set (0.01 sec)



/tmp 에 생성된 파일 확인. 


$ ll /tmp

total ...

drwxrwxrwt 13 root    root    4096 Aug 20 22:38 ./

drwxr-xr-x 24 root    root    4096 Jul  6 14:40 ../

-rw-r-----  1 mysql   mysql      0 Aug 20 22:38 test_mysql

......



이제 트리거에서 외부 실행파일을 마음껏 실행시킬 수 있다.



참고링크

https://github.com/mysqludf/lib_mysqludf_sys

http://bernardodamele.blogspot.com/2009/01/command-execution-with-mysql-udf.html

https://www.cyberciti.biz/faq/ubuntu-linux-howto-disable-apparmor-commands/