EOS.IO 트랜잭션 구조 – 개발자 일지 항성력 201707.9

in #eoskorea7 years ago

오늘은 개발자들이 동시성 모델을 더 잘 이해하게끔 현재의 EOS.IO 트랜젝션 구조를 설명할 시간을 가지려고 합니다. 아래는 samalice에게 currency(화폐) 를 보내는 JSON 형식의 거래입니다. 이 상황에서 currency, sam, alice는 계정 이름입니다; 그렇지만 그들은 다른 방식으로 쓰입니다.

{
  "refBlockNum": "12",
  "refBlockPrefix": "2792049106",
  "expiration": "2015-05-15T14:29:01",
  "scope": [
    "alice",
    "sam"
  ],
  "messages": [
    {
      "code": "currency",
      "type": "transfer",
      "recipients": [
        "sam",
        "alice"
      ],
      "authorization": [
        {
          "account": "sam",
          "permission": "active"
        }
      ],
      "data": "a34a59dcc8000000c9251a0000000000501a00000000000008454f53000000000568656c6c6f"
    }
  ],
  "signatures": []
}

단일 시그니처를 가지고 바이너리로 직렬화 되었을 때, 이 거래는 120바이트 정도인 Steem 거래나 94바이트 정도의 BitShares 거래보다 약간 더 큰 160 바이트입니다. 추가 공간의 대부분은 수신자와 권한, 그리고 범위를 명시적으로 지정하는 51바이트를 메시지에 추가한 것입니다.

TaPoS - POS의 트랜잭션(Transactions as Proof of Stake)

Steem & BitShares에 익숙한 분들은 트랜잭션의 3가지 필드를 알고 있을 것입니다; 이 필드들은 그대로 입니다. 이 필드들은 TaPoS에서 이용되었고(POS의 트랜잭션) 이 트랜잭션이 참조된 블록 이후, 그리고 만료되기 전에만 포함될 수 있다는 것을 보장합니다.

범위(Scope)

필드“Scope”는 EOS.IO에 추가된 것이며, 읽히거나 쓰여질 데이터의 범위를 구체화합니다. 만약 메시지가 Scope 밖에 있는 데이터를 읽거나 쓰려고 시도한다면 트랜잭션은 실패할 것입니다. 트랜잭션들은 Scope가 겹치지 않는다면 병렬적으로 이루어질 수 있습니다.

EOS.IO 소프트웨어의 핵심적인 혁신은 scopecode가 완전히 다른 개념이라는 것입니다. 여러분은 우리가 currency 컨트렉트의 code를 이용해 트랜잭션을 실행함에도 불구하고, currency 가 *scope *에 참조되지 않았다는 것을 주목해야합니다.

메시지(Messages)

트랜잭션은 순차적으로 적용되고 원자적(Atomically; 전부 성공하거나 전부 실패하는 단일 항목)으로 적용되는 하나 혹은 그 이상의 메시지를 가집니다. 이 경우에는 정확히 하나의 메시지만 있으므로, 이 메시지를 자세히 살펴봅시다.

code(코드):

모든 메시지들은 실행할 코드를 명시해야하는데, 이 경우에 currency 컨트렉트의 코드가 실행되어 다음 메소드가 호출됩니다.

currency::apply_currency_transfer(data)

type(타입):

타입 필드는 메시지 타입을 규정합니다(암시적으로 데이터 포맷도). 객체지향 프로그래밍 관점에서 여러분은 타입을 “currency” 클래스의 "name” 메소드로 볼 수 있습니다. 이 예제에서 타입은 “transfer”이므로 아래와 같이 호출되는 메소드 이름을 설명할 수 있습니다.

${namespace}::apply_${code}_${type}( data )

이 경우에서 “namespace”는 currency 컨트렉트입니다; 하지만, 같은 메소드apply_currency_transfer가 다른 namespace 에서 호출될 수 있습니다.

recipients(수신자):

currency::apply_currency_transfer(data)을 호출하는것 외에, 수신자들에 대해서 apply_currency_transfer(data) 메소드가 호출됩니다. 예를 들면, 아래 나온 순서대로 순차적으로 메서드들이 호출됩니다.

currency::apply_currency_transfer(data)
alice::apply_currency_transfer(data)
sam::apply_currency_transfer(data)

account::같이 쓰는 것은 메소드를 구현하는 컨트렉트를 지정합니다. alicesamcurrency::apply_currency_transfer가 실행될 때 수행할 특별한 로직이 없다면 이 메소드를 구현하지 않을 수도 있습니다. 그렇지만 만약 sam이 거래소였다면, 아마 sam은 currency 이동이 발생할 때, 입금과 출금을 처리하려 할것입니다.

트랜잭션을 발생시키는 사람은 제한없이 수신자를 추가할 수 있습니다. (수신자들이 모두 충분히 빠르게 실행된다면). 추가적으로 어떤 컨트렉트들은 특정한 당사자들에게 통보할 필요가 있습니다. currency의 경우에는 발신자와 수신자가 모두에게 알려줘야합니다. 당신은 currency 컨트렉트에서 어떻게 이것이 명시되었는지 볼 수 있습니다.

void apply_currency_transfer() {
   const auto& transfer  = currentMessage<Transfer>();
   requireNotice( transfer.to, transfer.from );
   ...
}

authorization(권한):

각각의 메시지는 하나 혹은 그 이상의 권한을 필요로 할 수 있습니다. Steem과 BitShares에서 필요된 허가는 암묵적으로 메시지 타입에 따라 정의됩니다; 그렇지만, EOS.IO 에서는 메시지가 제공받은 권한을 명시적으로 정의해야합니다. EOS.IO 시스템은 트랜잭션이 명시된 모든 권한을 부여하기 위해 필요한 서명 전부를 자동으로 확인합니다.

이 경우에서 메시지는 samactive 권한 레벨에서 서명되어야 한다는 것을 보여준다. currency 코드는 sam의 권한이 제공되었는지를 확인할 것 입니다. 여러분은 검사를 currency 컨트렉트 예제에서 확인할 수 있습니다.

void apply_currency_transfer() {
   const auto& transfer  = currentMessage<Transfer>();
   requireNotice( transfer.to, transfer.from );
   requireAuth( transfer.from );
   ...
}

data(데이터):

모든 컨트렉트는 그 자체의 형식을 정의할 수 있습니다. ABI가 없다면 데이터는 16진법의 데이터로만 해석될 수 있습니다; 하지만, currency 컨트렉트는 *데이터 *형식이 Transfer struct가 되도록 정의합니다.

struct Transfer {
  AccountName from;
  AccountName to;
  uint64_t    amount = 0;
};

이 정의로 인해 우리는 바이너리 blob을 비슷한 무언가로 바꿀 수 있습니다.

{ "from" : "sam",  "to": "alice",  "amount": 100 }

스케쥴링

이제 우리가 EOS.IO 트랜잭션의 구조를 이해하고 있으며, 우리는 EOS.IO의 블록의 구조를 살펴볼 수 있습니다. 각각의 블록은 순차적으로 실행되는 사이클로 나눠지며, 각각의 사이클에서 병렬로 실행되는 스레드의 갯수는 제한이 없습니다. 트릭은 두 개의 스레드가 교차 참조하는 scope가 있는 트랜잭션을 포함하지 않도록 하는 것입니다. 블록은 만약 단일 사이클 내에서 어떤 scope가 스레드들에 의해서 동시에 참조 된다면, 외부의 데이터를 참조하지 않고 무효하다고 선언할 수 있습니다.

결론

병렬 실행에서 가장 큰 도전 과제는 동시에 두 개의 스레드가 동일 데이터에 접근하지 않는 것을 보장하는 것입니다. 전통적인 병렬 프로그래밍과 달리, 실행 결과가 필연적으로 비결정적이거나, 잠재적으로는 합의를 깰 수 있기 때문에 메모리 락을 사용하는 것이 불가능합니다. 만약 락이 가능하더라도, 락의 과도한 사용이 싱글 스레드 실행보다 아래로 성능이 떨어질 수 있으므로 바람직하지 않습니다.

데이터에 접근할 때 lock을 거는 대신, 실행을 예약할 때 lock을 걸 수 있습니다. 이 관점에서, scope필드는 lock을 걸고자 하는 트랜잭션의 계정을 정의합니다. 스케쥴러(블록 생성자)는 동시에 두 스레드가 동시에 같은 lock을 걸려고 하지 않는 것을 보장합니다. 이 트랜잭션 구조와 스케쥴링을 통해서 메모리 접근 충돌을 피하고, 획기적으로 병렬 실행의 기회를 늘리는 것이 가능합니다.

다른 플랫폼들과 달리 데이터(계정 스토리지)와 코드(currency 컨트렉트)의 분리를 통해 락을 필요로 하는 사항들을 분리할 수 있습니다. 만약 currency 컨트렉트가 데이터가 묶여 있다면 매 송금마다 currency 컨트렉트에 락을 걸고, 단일 스레드 처리량으로 한정될 것입니다. 하지만 송금메시지가 발신자와 수신자의 데이터만 락을 건다면, currency 컨트렉트는 더 이상 병목 현상을 일으키지 않을 것입니다.

currency 컨트렉트의 병목 현상을 제거하는 것은 만약 당신이 거래소 컨트렉트를 고려하고 있다면 매우 중요합니다. 거래소에서는 입금이나 출금을 할때마다 currency 컨트렉트와 거래소 컨트렉트를 같은 스레드에서 처리해야 하는데, 만약 이 컨트렉트들이 동시에 과도하게 사용된다면 모든 currency와 모든 거래소유저들의 성능을 저하시킬 것입니다.

EOS.IO의 모델에서는 두 유저는 거래에 포함되어 있는 다른 계정들/계약들의 처리량에 대한 순차적(싱글 스레드) 처리량에 대한 걱정 없이 송금할 수 있습니다.


원문: https://steemit.com/eos/@dan/eos-developer-s-log-stardate-201707-9
@ludorum님께서 번역해주신 글입니다.

Sort:  

열심히 봤는데 이해가 안가네요..ㅠㅠ 이런쪽으로 쉽게 접할려면 다른방법은 없을까요??

무슨 말인지 모르지만. 수고가 많으시네요. ㅎㅎ

문과인 저는 이해가 어렵지만, 저 말고 다른 분들은 이해를 하실 거라 믿습니다 ㅠㅠ 수능 때처럼 공부를 열심히 해야겠어요 ㅠㅠ

매번 좋은 글 감사드립니다. @ludorum님께도 감사드립니다.
이번 포스팅도 병렬처리를 죽어라 파고 있네요. EOS.IO의 개발 기조는 "트랜잭션 처리 량"을 획기적으로 늘리자! 로 보입니다. 핵심적인 부분은 scope입니다. 병렬처리를 할 때에 중요한 것은 동시에 같은 데이터를 조작하지 않는다. 라는 것인데, 가장 무식하게는 DB 전체를 lock거는 것이겠죠. graph DB의 경우는 node 단위로 lock을 걸긴하지만 레코드 단위의 lock은 trade-off가 발생해서 때론 비효율을 야기하기도 합니다. EOS.IO의 경우는 account별로 lock을 걸려는 것 같네요. 나쁘지는 않아 보입니다.
다만, 암시적으로 scope을 기술하는게 아니라 명시적으로 scope를 기술하게 하고 있는데.. 트랜잭션을 만드는 쪽에서 의도적으로 scope를 무시해버리거나 잘못 기재하거나 악의적으로 특정 계정을 계속 포함시킨다던지 했을 때, 문제가 발생할 소지가 있네요.

Coin Marketplace

STEEM 0.20
TRX 0.13
JST 0.030
BTC 65702.61
ETH 3485.24
USDT 1.00
SBD 2.51