336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
지난 포스트에선 이더리움 스마트 컨트랙트의 출력 수단 중 하나인 이벤트에 대해 알아 보았습니다. 호출된 이벤트는 로그라는 이름으로 Transaction Receipt에 남아 이더리움 블록체인에 기록됩니다. 블록체인에 기록된 것은 블록을 다운로드 받을 수만 있다면 항상 접근할 수 있기 때문에 일종의 스토리지라고 생각할 수도 있습니다. 그러나 이 스토리지는 무한정 쓸 수는 없습니다. Gas라고 불리는 것에 의해 쓰는 만큼 비용을 내야 하고, 트랜잭션 당 쓸 수 있는 양에 제한이 있기 때문입니다.


Gas란?
Gas(이하 가스)는 이더리움이라는 거대한 컴퓨터를 쓰기 위한 일종의 연료입니다. 수수료는 가스 사용량과 가스 가격의 곱으로 결정됩니다. 이전 포스트에서 Solidity가 EVM이라는 스택머신의 바이트코드로 컴파일되고, 이더리움은 그 바이트코드를 실행한다고 언급했었습니다. EVM Opcode 마다 정해진 가스 사용량이 있습니다. 예를 들면 ADD 연산에 3가스, 곱하기 연산에 5가스, 트랜잭션에 21000가스, log 출력 시 바이트당 8가스 등등 입니다. 트랜잭션에서 발생한 모든 Opcode의 가스 사용량을 합하고 사용한 양에 1가스에 지불할 이더를 곱하면 해당 트랜잭션의 수수료가 계산됩니다.

트랜잭션 수수료 = 가스 사용량 * 가스 가격



Opcode에 따른 가스 사용량은 버전에 따라 조금씩 다르지만 중요한 것은 블록에 무언가 기록하는 Opcode가 비싸다는 것입니다. 블록에 기록된 내용은 이더리움의 모든 풀 노드가 저장해야 하기 때문입니다. 따라서 Solidity를 EVM 바이트코드로 변환할 때 중요한 것은 실행 속도가 아닙니다. 실행이 느리게 되더라도 바이트코드 사이즈를 줄여서 배포 시 발생하는 수수료를 줄이고, 가스 비용이 비싼 특정 Opcode들을 최소화하는 것이 좋겠지요.


Gas 가격의 결정과 지불
Gas 가격은 트랜잭션을 생성하는 사람이 1가스 당 지불할 wei를 결정합니다(wei는 현재 이더의 최소 단위입니다). 마이너가 트랜잭션을 처리하고 블록을 마이닝하면 그 블록에 수수료 지불내역이 남습니다. 

현재 가스의 적정 시세는 1 가스 당 40 Gwei(= 40000000000 wei = 0.00000004 ETH) 입니다. 트랜잭션에 21000 가스가 발생하니 기본 트랜잭션에 0.00084 ETH가 필요합니다. 1 ETH를 100만원으로 계산하면 기본 트랜잭션에 840원이 수수료로 발생하는 셈입니다. 송금앱들이 500원 수수료를 요구하는 것에 비해 더 비싸지만 비트코인 트랜잭션 수수료에 비하면 훨씬 합리적이네요.

가스 시세를 보여주는 ethgasstation 사이트에 따르면 1 가스당 40 Gwei를 지불하면 블록에 포함되는 시간이 1분 이내인 반면에 38 Gwei를 지불하면 약 30분 정도 걸릴 수 있다고 합니다.
https://ethgasstation.info/

트랜잭션을 생성할 때 가스 가격(GasPrice)과 같이 가스 제한(GasLimit)을 정해두어야 합니다. EVM은 튜링머신이기 때문에 실행해보기 전까지는 트랜잭션 처리가 종료될 지 알 수 없습니다. 가스 제한이 없다면 무한 루프 등으로 처리가 끝나지 않는 트랜잭션을 발생시켜 마이너들에게 DoS 공격을 가할 수 있습니다. 만약 트랜잭션이 제한된 가스 사용량을 넘기게 되면 그 트랜잭션은 실패된 채로 수수료가 지불됩니다. 가스 제한은 트랜잭션 뿐만 아니라 블록에도 존재하기 때문에 누군가 수수료를 많이 낼 수 있다고 해서 가스를 마음껏 쓸 수 있는 것은 아닙니다.

비트코인의 스크립트 언어가 튜링 완전하지 않기 때문에 종료가 보장되어 이러한 Gas 개념이 필요하지 않습니다.


Solidity 프로그래밍 내 Gas 제어 이슈
스마트 컨트랙트 프로그래밍에선 이 Gas 개념 때문에 고려해야 할 점들이 있습니다.

Solidity 프로그래밍 (1) 포스트에서 언급한 것과 같이 이더를 보낼 때 다른 Contract의 함수를 호출하게 되는 경우도 있습니다. 이더를 보낼 때 call을 호출해서 현재 내가 쓰고 남은 가스를 보낼 수 있는 방식과 send 혹은 transfer를 호출해서 남은 가스를 보내지 않는 방식을 구분해서 써야 합니다. 외부의 임의의 Contract가 악의적인 코드일 가능성을 염두에 두어 Gas를 필요한 만큼만 주는 것이 바람직합니다. 아래 링크는 Solidity 공식 문서의 re-entrancy 예제입니다.


6번 라인의 msg.sender.call.value(x)()는 x만큼 msg.sender에게 이더를 보내고 남은 가스를 넘겨줍니다. 만약 msg.sender가 악의적으로 작성된 스마트 컨트랙트일 경우 withdraw()를 다시 호출하는 re-entracy 공격을 할 수 있는데, 이 경우 코드에 따르면 msg.sender에게 계속 이더를 보내게 되고, 가스가 부족해지거나 Fund에 잔고가 다 떨어졌다고 판단되면 더 이상 withdraw()를 호출하지 않고 정상 종료하여 트랜잭션을 성공시킬 수 있습니다. 360만 이더가 탈취된 DAO 해킹이 이 취약점이 공격된 사례입니다.



두 번째는 Solidity 공식 문서의 Ballot 예제를 일부 따왔습니다. 코드 전체를 이해할 필요는 없고, 동적 길이의 배열과 그 배열 크기만큼 반복하는 for문에 대한 얘기입니다. winningProposal()이 view function 이여서 적합한 예제는 아니지만, 어느 정도 상상력을 가지고 보시면 되겠습니다. 누군가 ProposalNames를 길게 하여 여러번 Ballot을 호출했다고 가정해봅시다. 그 다음 winningProposal()을 호출하면 상당히 긴 for문을 돌아야 할 지도 모릅니다. 이 때 문제는 proposals의 길이를 줄일 수 있는 코드가 없고, 이미 길어진 proposals를 처리하는데 들어가는 가스가 블록 가스 제한을 넘어버리면, winningProposal()은 더 이상 호출할 수 없는 코드가 되어버립니다. DoS 공격이 가능해지는 것이지요.





이더리움의 가스와 관련된 내용에 대해 알아봤습니다. EVM이 가진 튜링완전 특성으로 인해 불가피하게 도입되었을텐데, 스마트 컨트랙트 프로그래밍 입장에선 상당히 저수준의 내용을 염두에 두고 있어야 해서 불편한 부분이기도 합니다. 다음 포스팅에서는 MetaCoin이라는 예제를 기반으로 이더리움 기반의 코인이란 어떤 것인지 알아보도록 하겠습니다.


+ Recent posts