'opcode'에 해당되는 글 1건

  1. 2017.08.15 이더리움 프로그래밍 수업(3)

이더리움 프로그래밍 수업(3)


들어가며


지난 두 번의 글을 통해 우리는 이더리움의 코어 엔진인 geth 를 다루는 다양한 방법에 대해 살펴보았습니다. 앞으로는 2회에 걸쳐 솔리디티를 이용해 컨트랙을 프로그래밍하는 방법에 대해 살펴보겠습니다. 특히, 단순 개발 뿐만 아니라 실제 해당 컨트랙이 어떻게 geth의 EVM 을 통해 실행되고 이 때 어떤 과정들을 거치는 지도 함께 살펴 보겠습니다.


// 첫번째 아주 간단한 예제 : Greeter.sol        

 

다음 예제 프로그램은 Greeter.sol 이라는 아주 간단한 솔리디티 프로그램입니다. Solidity-baby-steps에서 제공되는 첫번째 예제입니다. https://github.com/fivedogit/solidity-baby-steps ) 단계별로 많은 예제들이 모여 있어 해당 예제를 분석하면서 솔리디티 매뉴얼을 함게 보시면 솔리디티 프로그래밍을 익히는 데 효과적입니다.

 

아래 예제는 greet()를 호출할 때 마다 입력받은  스트링을 반환해주는 단순한 프로그램 입니다. 이 프로그램을 통해 솔리디티로 작성한 스마트 컨트렉트 프로그램의 구조를 이해해 보겠습니다. 

 

pragma solidity ^0.4.0;

contract Greeter        

{

    address creator;    

    string greeting;    

 

    function Greeter(string _greeting) public  

    {

        creator = msg.sender;

        greeting = _greeting;

    }

 

    function greet() constant returns (string)         

    {

return greeting;

    }

   

    function getBlockNumber() constant returns (uint)

    {

        return block.number;

    }

    function setGreeting(string _newgreeting)

    { 

        greeting = _newgreeting;    

     }

    function kill()

    {

        if (msg.sender == creator) 

        suicide(creator);      

    }

}

[ 3-1 ] Greeter.sol

 

하나씩 분해해서 살펴 보겠습니다.

 

pragma solidity ^0.4.0;

이 소스 파일은 0.4.0 보다 이전 버전의 컴파일로 컴파일 하지 마세요. 그리고 ^는 버전 0.5.0로 시작하는 컴파일러에서도 작동하지 않는다 라는 의미입니다. 항상 pragma 버전은 선언이 되어야 합니다

 

contract Greeter  {   }

Greeter 라는 컨트렉을 만든다 라는 것이고 하나의 파일에 여러개의 컨트렉을 정의하고 사용할 수 있습니다.

 

address creator;  string greeting;    

Greeter의 상태를 선언하는 것 입니다. address 데이타 타입의 creator와 string 데이타 타입의 greeting을 상태로 선언합니다. address는 20바이트(160 bit, 이더리움의 어드레스 사이즈) 로 산출연산을 할 수 없기 때문에 컨트랙의 주소나 키값 등을 저장하는 데 용의합니다. 또한 balance와 transfer를 멤버로 갖습니다.

 

가령, address myaddress = this; 라는 의미는 현재 이 컨트랙의 주소를 말하는 데 myaddress의 balace는 현재 이더 금액을 그리고 myadress.transfer(금액)은 해당 금액 만큼 내게 전송한다 라는 의미입니다.  아주 많이 이용되는 Value Type 중 하나입니다. 그리고 string은 UTF8 으로 인코딩되었고 크기가 지정되지 않은 임의 크기를 갖는 타입입니다. 


function Greeter(string _greeting) public { }

상태를 선언했다면 해당 상태를 바꿀, 실행할 수 있는 코드 단위인 함수를 선언하고 작성해야 합니다. 이 때 function 키워드를 사용하여 함수를 선언합니다. 위의 함수 선언은 Greeter라는 컨트랙 이름과 동일합니다. 생성함수로서 Creeter 컨트랙이 생성되는 순간 자동으로 호출되어 실행됩니다. strng 타입의 _geeting을 입력받도록 선언되어 있습니다. public 키워드는 외부에서도 호출이 될 수 있도록 지정하는 것 입니다. 

 

참고로 , 여기서 모든 설명을 할 수는 없지만 다음은 솔리디티에서 사용하는 함수 선언 방식입니다. 가령, internal은 내부에서만 사용하기 때문에 외부로 보여지지 않습니다. 쉽게 말해 Remix에서도 접근이 안되니 실행이 안됩니다. Payable은 실행을 할 때 이더(Ether)를 대가로 받는 함수입니다. 모두 미리 상세히 알고 있을 필요는 없습니다. 실제 개발을 하며 하나씩 하나씩 적용하며 익히는 것이 가장 효율적입니다.


function (<parameter types>) {internal|external} [constant] [payable] [returns ,!<return types>)]

 

creator = msg.sender;

선언하지 않았는데 갑자기 나타난 변수가 있습니다. msg 입니다. msg는 컨트랙을 생성한 사람의 어드레스를 영구적으로 저장하고 있는 글로벌 변수로서 블록체인에 접근해서 다양한 정보를 획득할 수 있습니다. msg.sender는 현재 함수를 호출한 사람의 주소를 알려준다. 호출자를 확인 후 제약할 수 있어 유용하게 사용된다.

 

function greet() constant returns (string)         

greet 함수는 변하지 않는 상수 타입의 스트링을 반환하는 함수라는 것을 말한다.

 

function getBlockNumber() constant returns (uint)

getBlockNumber 함수에는 앞서 선언하지 않았지만 block.number 이라는 것이 사용된다. msg처럼 block은 블록체인에 대한 정보에 접근할 수 있는 글로벌 변수이다. block.number 는 현재 블록의 넘버를 알려준다. 현재 블록의 gas limt을 알고 싶다면 block.gaslimit을 사용하면 된다. msg,block,tx 등은 아주 유용한 글로벌 변수로서 잘 이해하는 것이 필요하다.

 

function kill()

마지막에 나오는 kill 이라는 무시무시해 보이는 함수는 내부에서 suicide는 호출한다. suicide는 self descruct를 말하는 데 해당 컨트랙을 kill하고 남은 금액을 모두 생성한 사람에게 보내라는 것이다. 이를 위해  if (msg.sender == creator)    msg.sender가 creator인지 체크한 후  맞다면 컨트랙을 모두 suicide 하고 남은 금액을 반환하게 된다. 

 

//Remix에서 실행 - Greeter 컨트랙 배포

 

해당 소스를 오타 없이 입력했다면 Remix에서 제대로 실행이 될 것 입니다. 다음은  "how are you"  문자열을 입력하고 <<Create>> 버튼을 통해 배포된 Greeter의 실행 화면 입니다.  greet 함수의 value를 보면 "how are you"  문자열이 실제 utf8으로 인코딩되어 있는 실제값을 볼 수 있습니다.

 

[3-2] Greeter 컨트랙 배포

 

setGreeting 함수에 "반갑습니다." 라는 문자열을 입력하고 실행을 시켜 보겠습니다. 정상적으로 실행이 된 후 , 다시 greeet 함수를 실행하면 기존 greeting의 상태가  "how are you"  문자열에서 "반갑습니다." 로 변경된 것을 확인할 수 있습니다.

 


[3-3]  setGreeting 함수 실행

 

kill 함수를  실행시켜 보겠습니다.  앞서 설명 드린 것처럼 kill 함수는 suicide를 호출하여 해당 컨트랙을 삭제합니다. kill 함수 실행 후 , 다시 setString 함수를 실행시키면  Kill에 의해 해당 컨트랙이 삭제된 상태이기 때문에  TypeError 가 발생하는 것을 확인할 수 있습니다.

 


[3-4] kill 함수 호출 후 결과

 

잠시 이해를 돕기 위해 컴파일된 컨트랙의 상세 정보에 대해 살펴보겠습니다. <<iContract details (bytecode, interface etc.)>> 링크를 클릭하면 컨트랙의 상세 정보를 볼 수 있습니다. 상세 정보 중 다음의 주요한 것들만 정리해 봅니다.

 

- Bytecode & Runtime Bytecode

 

60606040526000357c0100000000000000000000000000000000000000000000000000000000900………………<중략생략>………………………....……………………………………………………………………...……11561039a576000816000905550600101610382565b5090565b905600a165627a7a723058203a53b67dee629a7ee6cd6008427d8043c1a2c8d30759ffba5d3b68e055e3c91e0029

 

솔리디티로 작성된 프로그램은 컴파일된 후 아래와 같은 형태의 바이트코드로 변환이 됩니다. 이 바이트 코드는 블록체인을 통해 각 노드에 배포가 됩니다. 배포후에는 주소(address)가 생성이 됩니다. 

 

이 바이트코드 중 가령, setGreeting 이라는 함수가 실행이 되면 이 실행 내용이 블록체인의 각 노드에 이 사실이 전파됩니다. 그리고 해당 트랜젝션이 문제가 없다면 블록에 해당 내용이 추가됩니다. 이렇게 수행중인 바이트코드가 Runtime Bytecode 입니다.

 

Interface


[{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newgreeting","type":"string"}],"name":"setGreeting","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"greet","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"inputs":[{"name":"_greeting","type":"string"}],"payable":false,"type":"constructor"}]

 

Greeter 컨트랙의 터페이스입니다. 인터페이스는 구현된 코드가 없는 추상화된 명세입니다. 이 니터페이스가 Web3.js를 통해 바이너리에 접근할 때 사용하는 ABI(Application Binary Interface)로 활용됩니다.  가령, 자바 스크립트 언어 상에서 var greeterABI = [{"constant" … "constructor"}]; 으로 지정 후 web3.eth.contract(greeterABI) 이렇게 호출하여 인터페이스를 사용합니다. 

 

 

Web3 deploy

 

지난 글에서도 설명했듯이 컨트랙은 Web3.js를 통해서 JSON RPC를 통해 자바 스크립트로 제어가 가능합니다. 이를 위해서는 Greeter 컨트랙이 Web3.js에서 접근할 수 있도록 deploy가 되어야 합니다.

 

var _greeting = /* var of type string here */ ;
var localhost_solidity-baby-steps_contracts_05_greeter_sol_greeterContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":...................<<중략>>
var localhost_solidity-baby-steps_contracts_05_greeter_sol_greeter = localhost_solidity-baby-steps_contracts_05_greeter_sol_greeterContract.new(
   _greeting,
   {
     from: web3.eth.accounts[0], 
     data: '

60606040526000357c0100000000000000000000000000000000000000000000000000000000900………………<중략생략>………………………....……………………………………………………………………...……11561039a576000816000905550600101610382565b5090565b905600a165627a7a723058203a53b67dee629a7ee6cd6008427d8043c1a2c8d30759ffba5d3b68e055e3c91e0029

', 
     gas: '4300000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

 

 

Meta data & Meta data location

 

{"compiler":{"version":"0.4.14+commit.c2215d46"},"language":"Solidity","output":{"abi":[{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getBlockNumber","outputs":[{"name":"","type":"uint256"}]......중략.......,"libraries":{},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"localhost/solidity-baby-steps/contracts/05_greeter.sol":{"keccak256":"0x5fe8bd1258cf319ac111e904ea5954e93630b1c77a66fd77095a4587670454af","urls":["bzzr://9a434bbf17d4662bd0bc822cd9cba702cc5c6828f608fa9c3cc26b086e49b1c7"]}},"version":1}

 

솔리디티 컴파일러는 자동으로 JSON 타입으로 해당 컨트랙의 메타 데이타를 생성한다. 이 메타 데이타 파일은 컴파일러의 버전, 사용된 개발 언어, ABI 등 해당 바이트 코드의 안정성을 체크하고 컨트랙과 보안 안전하게 상호작용하기 위해 필요한 정보들이 담겨 있다. 메타 데이타 파일은 Swarm 이라는 이더리움 분산 파일 시스템에 저장되고 조회될 수 있다. Swarm 에 접근할 때 사용하는 URL 이 bzzr이다.  메타파일의 마지막에는 Swarm에 접근하는 프로토콜이 선언되어 있다. -["bzzr://9a434bbf17d4662bd0bc822cd9cba702cc5c6828f608fa9c3cc26b086e49b1c7"]

 

 

Opcodes와 Assembly

 

컨트랙은 컴파일 후 바이트코드로 변환이 됩니다실제 이 바이트 코드는 1바이트 크기의 OpCode들로 분해되어 EVM의 스택에 쌓인 후 EVM에 의해서 실행이 됩니다. 다시 말해, 모여진 OpCode들의 실행이 바로 해당 컨트랙의 트랜젝션입니다. 현재 OpCode들은 Stop과 산술연산자, 각종 로직 연산자 , SHA3 , 환경 정보 , 블럭 정보, Stack/Memory/Storage와 플로우 연산자 , push 연산자, duplication 연산자 , Exchange 연산자 , 시스템 연산자 , 앞서 kill()에서 사용한 suicide 같은 self descruct연산자 등 많은 OpCode가 있습니다.

 

 [3-5] Opcodes와 Assembly

 

각 OpCode들을 실행을 하려면 실행 대가로 Gas를 제공해야 합니다. Gas를 제공하는 이유는 앞서도 설명했지만 해당 OpCode의 이상유무를 체크하고 이상이 없다면 블록에 등록하는 절차를 수행하는 마이너들에 대한 대가이자 , DDos 공격 등을 막기 위한 방법으로 사용됩니다. 지난 2016년 9월 해커들이 이더리움 네트웍에 지속적인 DDos 공격을 하여 정상적인 운영이 안된 문제가 있었습니다. 해커들이 서로 다른 계좌에 다수의 빈 트렌젝션을 발생시키고 사용하지 않는 빈 어카운트를 무한정 생성하여 이더리움 메모리를 소비해 버리는 공격이었습니다. 

  이 때 , 이더리움은 공격에 사용된 일부 OpCode의 Gas 비용을 높이고 발신자가 소비하는 리소스에 비례하여 강제로 수수료를 지불하게 하는  메이져 업그레이드(하드포크)를 한 적이 있습니다.  보다 자세한 OpCode와 Gas 등에 대한 내용은 Gavin Wood가 작성한 Ethereum Yellow Page를 참조하기 바랍니다.


마치는 말

 

새로운 컨트랙 기능 추가가 될 때 마다 OpCode가 추가될 터이고 , 이중 분명히 문제가 있는 OpCode들이 있을 것 입니다. 이 취약한 OpCode들로 인해 문제가 발생할 때 마다 메이저 업그레이드가 생긴다면 이더리움의 기술 신뢰성과 가치에 큰 영향을 줄 것 같습니다. 본질적으로 이더리움의 성능 향상을 위한 이더리움 커뮤니티의 노력이 필요한 것 같습니다. 이러한 부분에 관심이 많다면 Qtum과 EOS 프로젝트를 살펴보면 많은 새로운 관점을 얻으실 것 입니다. 그리고 최근에 MS에 발표한 Coco 프레임웍도 현재 이더리움에서 해결해야 할 여러 이슈들을 해결하는 데 주안점을 두고 있습니다. 나중 기회가 되면 함께 살펴보면 좋을 것 같습니다.


이번에는 컨트랙 프로그램의 기초에 대해 살펴보았습니다. 다음에는 좀 더 복잡하고 솔리티디의 다양한 언어적 특성을 이해할 수 있는 예를 분석해보겠습니다. 참고로 , 모두 아시다시피 개발 언어는 직접 작은 것이라도 하나를 직접 작성해 보면서 경험을 늘리는 것이 가장 중요합니다.

     









Posted by 박재현
,