[EOS] 에어드랍에 필요한 자원(RAM) 사용량 계산하기

in #kr6 years ago (edited)

우선 대략적인 내용은 아래 글을 참고하면 될 것 같습니다.
(다만, 정글넷이라는 테스트넷에서 실험을 바탕으로 쓰여진 글이므로 사용량이 정확한건 아닙니다.)

https://steemit.com/eos/@leordev/eos-ram-and-bandwith-analysis-airdropping-steps-on-junglenet


<최근 3일간 EOS RAM 시세>

어제 EOS 메인넷에서 최초로 ADD 토큰이 에어드랍 되었는데, 이 상황을 예로 들어 RAM 사용량을 좀 더 정확하게 계산해보겠습니다.
(CPU, Net은 필요한만큼 Delegate 해뒀다가 나중에 Undelegate 하면 되므로 여기서는 다루지 않겠습니다)

글 작성 편의상 cleos 의 -u 옵션은 생략하였습니다.

Contract 용량

$ cleos set contract eosadddddddd build/contracts/eosio.token
(ADD 토큰 발행 계정은 eosadddddddd 입니다)

EOS 토큰 생성(create) 및 발급(issue)에 사용한 eosio.token 을 별다른 수정없이 그대로 가져쓴다고 가정하면, set contract 시 차지하는 RAM 은 Contract를 컴파일 한 WASM 파일 사이즈의 10배입니다.
(libraries/chain/include/eosio/chain/config.hpp 에 정의된 setcode_ram_bytes_multiplier 값이 10입니다.)

WASM 파일은 18965 byte이고 RAM은 10배인 약 185 KB 를 차지합니다.

토큰 생성

$ cleos push action eosadddddddd create '["eosadddddddd", "10000000000.0000 ADD"]' -p eosadddddddd

ADD 토큰의 생성 및 발행량은
$ cleos get table eosadddddddd ADD stat 명령으로 확인할 수 있으며,
총 100억개를 생성하여 100억개를 발행하였음을 알 수 있습니다.
ADD 외에도 AD 라는 토큰도 100억개를 발행했네요.

토큰 생성에는 264 byte의 RAM을 사용합니다.

     struct currency_stats {
        asset          supply;
        asset          max_supply;
        account_name   issuer;

        uint64_t primary_key()const { return supply.symbol.name(); }
     };

위는 stat 테이블의 레코드 정의이며 asset(16 byte) + asset(16 byte) + account_name(8 byte) 의 data 와 asset.symbol(8 byte) 을 key로 가지는 multi-index table을 아래와 같이 정의하여 사용합니다.

     typedef eosio::multi_index<N(stat), currency_stats> stats;

    stats statstable( _self, sym.name() );
    statstable.emplace( _self, [&]( auto& s ) {
       s.supply.symbol = maximum_supply.symbol;
       s.max_supply    = maximum_supply;
       s.issuer        = issuer;
    });

최초 emplace 시에는 table을 생성하여 record를 추가하고, 이후부터 emplace 시에는 record만 추가합니다.
(모든 record를 erase 하는 경우에는 table도 삭제됩니다)

토큰 생성의 경우 table size(112byte) + key에 의한 size(112byte) + 레코드 data size(40byte)가 되어 총 264byte를 차지하게 됩니다.
이후에 토큰을 추가 생성하는 경우 레코드만 추가되므로 40byte 씩 RAM을 차지합니다.

토큰 발행(전송)

지금까지는 RAM 사용량이 얼마 되지 않는데요, 문제는 지금부터 입니다.
$ cleos push action eosadddddddd issue '["<대상 account>", "x.xxxx ADD", ""]' -p eosadddddddd

issue 액션을 호출하면 우선 issuer(여기서는 eosadddddddd 계정) 에게 issue 하고자 하는 토큰 개수를 balance에 더하고 (add_balance() 호출) issuer가 <대상 account> 에게 transfer 하게 됩니다.
transfer 액션은 보내는 사람의 balance에서 토큰 개수를 빼고 (sub_balance() 호출), 받는 사람의 balance에 토큰 개수를 더합니다 (add_balance() 호출).
issuer가 자신에게 issue 하는 경우 transfer 액션은 호출되지 않습니다.

여기서 add_balance() 를 호출하는 곳을 보면 ram_payer가 issuer가 됩니다.
즉, 토큰 balance 정보를 가지고 있는 accounts table<대상 account>가 소유하지만 (scope), 그에 필요한 RAM은 ram_payer (여기서는 eosadddddddd) 의 RAM에서 빠져나갑니다. (토큰도 나눠주는데 좀 불합리 한 것 같죠? ㅋㅋ)

     struct account {
        asset    balance;

        uint64_t primary_key()const { return balance.symbol.name(); }
     };

RAM을 차지하는 size는 table size(112byte) + key에 의한 size(112byte) + 레코드 data size(16byte)가 되어 총 240byte입니다.

제네시스 스냅샷 기준 163930 개의 계정에 모두 issue를 하게 되면 163930 x 240byte ≈ 37.5206MB 를 소모하게 됩니다.

이는 최초 에어드랍 시에만 해당하는 내용이며, 이후 토큰을 받은 사람들이 transfer를 하기 시작하면 table의 modify() 가 불리면서 issuer는 자신의 RAM에서 빠져나갔던 일정 부분을 돌려받게 됩니다.
(이 내용은 좀 복잡하기 때문에 저 아래에서 다루겠습니다)

ADD 토큰은 EOS 제네시스 스냅샷 기준으로 EOS : ADD = 1 : 0.5 비율로 에어드랍 하였습니다.
$ cleos get table eosadddddddd b1 accounts 명령으로 b1에게 5천만개가 있는 것을 보고 1 : 0.5 비율임을 알았습니다.

163930개의 모든 account에게 에어드랍을 한다면 총 5억개의 ADD를 발행(전송) 할 것이고 eosadddddddd 계정은 나머지 95억개의 ADD를 보유하게 되겠죠.

ADD 토큰이 실제로 어떤 과정을 통해 분배되었는지 살펴보면,

$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 22 20
eosadddddddd 계정의 action을 22번째부터 20개이후 만큼 가져오는 명령인데, 보시면 자신에게 100억개를 모두 issue 한 것을 알 수 있습니다.
$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 72 10
$ cleos -u http://br.eosrio.io:8080 get actions eosadddddddd --full 172691 10
이후 72번째 action에서 b1 에게 5000만개 transfer를 시작으로 172696번째 action에서 마지막 transfer를 함으로 에어드랍이 완료된 것을 알 수 있습니다. (총 3시간 8분 정도 걸렸네요)

마지막 transfer 한 ADD 개수가 50개 인 것을 보아 EOS 100개 이상 account에게만 에어드랍 한 것을 알 수 있습니다.

이 글 최상단에 링크되어 있는 글에 따르면 제네시스 스냅샷 기준으로 EOS 100개 이상을 보유한 account는 총 84979개 이므로,
총 16만 계정에 에어드랍 하는 것보다 절반의 RAM을 절약할 수 있습니다.

여기서 한 가지 특이한 것이 에어드랍을 issue 액션으로 하지 않고, 자신에게 전체토큰을 issue 한 뒤 transfer 액션을 이용했다는 점인데 어차피 issue가 내부적으로 transfer를 호출하기 때문에 cpu 사용량을 줄이기 위함이 아닌가 합니다.

그럼 여기서 의문이 드는 것이 32만 계정에 에어드랍을 하게되면 64MB를 넘어가게 되는데, contract가 사용할 수 있는 RAM은 64MB로 제한되어 있는 현재 상황에서 에어드랍을 어떻게 해야할까요?

https://github.com/EOSIO/eos/issues/4285

여기에 질문을 올려놨는데 답변을 해줄 지 모르겠네요 ㅎㅎ

Plactal의 Eric song 님께서 지적해주셔서 확인해보니, multi-index table의 용량은 execution memory (64MB로 제한) 과는 별개입니다. 즉, RAM을 구매한만큼 multi-index table 용량은 사용할 수 있기 때문에 32만 계정 이상에게 에어드랍도 가능합니다.


부연 설명

위에 언급했던, 에어드랍 받은 사람들이 transfer를 하게 되면 토큰 발급자(issuer)가 일부 RAM을 돌려받는 부분에 대한 설명입니다.

transfer 시 table에 대한 modify()의 payer는 다음과 같습니다.

account name of the payer for the Storage usage of the updated row; a value of 0 indicates that the payer of the modified row is the same as the existing payer.

transfer 시 보내는 사람의 RAM payer는 보내는 사람이지만 받는 사람의 RAM payer는 0 (받는 사람의 table을 생성해준 사람) 입니다.

modify()에 관한 몇 가지 예를 들면,

1. ADD를 분배받은 A가 ADD를 분배받은 B에게 일정량을 보내는 경우

  • A의 ADD accounts table은 eosadddddddd RAM을 사용 중입니다. (payer : eosadddddddd)
  • B의 ADD accounts table도 eosadddddddd RAM을 사용 중입니다. (payer : eosadddddddd)
  • A가 B에게 ADD를 일정량 보내면 각각의 accounts table에 modify가 일어납니다.
  • A의 ADD account table에 대해 A가 payer가 되면서 table size를 제외한 만큼(112+16 byte) RAM을 A가 사용하게 됩니다.
  • eosadddddddd 는 이제 A의 ADD acccount table의 table size만 가지게 되고 128 byte를 A로부터 돌려받게 됩니다.
  • B의 ADD accounts table은 payer가 0 (즉, 첫 payer인 eosadddddddd)이므로 RAM의 변화는 없습니다.
  • 결과적으로 eosadddddddd 는 128 byte의 여유가 생기고 A는 128 byte를 더 사용하게 됩니다.

2. ADD를 분배받은 A가 ADD를 받지 못한 C에게 일정량을 보내는 경우

  • eosadddddddd는 앞에 1의 경우와 같습니다. 따라서 A에게서 128 byte를 돌려받게 됩니다.
  • A는 앞에 1의 경우와 같이 128 byte를 더 사용하게 됩니다.
  • 그런데 C에게는 ADD accounts table이 없었기 때문에 토큰을 보내는 A의 RAM을 소모하여 C의 ADD accounts table이 생성됩니다.
  • 결과적으로 eosadddddddd 는 128 byte의 여유가 생기고 A는 128+240 = 368 byte를 더 사용하게 됩니다.
  • 여기서 C가 다른 누군가에게 ADD 토큰을 transfer 해야 A는 그나마 128 byte를 돌려받을 수 있고, C가 ADD 토큰을 모두 사용하여 balance가 0이 되어야 A는 나머지 112 byte까지 돌려받을 수 있습니다.

좀 어렵죠? ㅠㅠ
이 내용은 ADD 토큰 뿐만 아니라 EOS 에도 해당됩니다.
첫 메인넷 때는 eosio 계정이 EOS 토큰을 issue 해주었기 때문에 16만여개의 accounts table을 eosio의 RAM을 이용해서 생성했지만 이후에 EOS를 transfer 하게 되면 128 byte 또는 368 byte 만큼 추가로 사용하게 됩니다.

새로 계정을 생성 (A 계정이라 칭함) 해서 사용하는 경우, A 계정의 accounts table 만큼 RAM을 아끼고 싶다면 A 계정에게 EOS를 송금해주기 전에 A 계정의 RAM을 소량 팔아버립니다. 그러면 A계정의 accounts table 이 eosio.ram 계정의 RAM을 사용해서 생성됩니다. (꼼수라고 해야할까.. 그래봐야 240 byte ~ 112 byte 세이브 하는 정도. RAM 판매 수수료는 최소 0.0001 EOS. 약간의 이득이긴 한데 ㅋ)

앞으로 EOS 메인넷에서 수많은 에어드랍(또는 ERC20 -> EOS토큰 스왑)이 있을텐데, 위의 table 생성 시 소모되는 RAM 을 고려하여 꼭 필요한 송금만 하는 것이 RAM을 아껴쓸 수 있는 방법입니다.

소스코드 분석과 여러 번의 테스트를 거치면서 확인한 수치들인데 혹시 잘못된 부분이 있다면 지적 부탁 드립니다.

Sort:  

(jjangjjangman 태그 사용시 댓글을 남깁니다.)
호출에 감사드립니다! 즐거운 스티밋하세요!

와 램과 관련된 상세한 분석 감사합니다~!! 👍👍👍

개발자 텔방에서 @raindays 님 덕분에 알게된 내용이 많네요. ^^

구체적인 설명감사합니당^^

도움이 되었으면 좋겠네요^^

eos 에어드랍 받을일은 없지만 어떤식으로 이루어지는지 잘 배우고 갑니다.

댓글 감사합니다^^

복잡하군요. dApp을 운영하는 입장에서는 RAM 비용도 상당하겠군요.

어플리케이션이 많이 개발되면 램 사용량이 늘어나겠군요. 코드는 모르지만 흥미롭네요^^