본문으로 건너뛰기

1 Transaction

  • 트랜잭션은 외부 소유 계정(EOA)에 의해 서명된 메시지 인데, 이더리움 네트워크에 의해 전송되고 이더리움 블록체인에 기록된다.
  • 트랜잭션은 EVM에서 상태 변경을 유발하거나 컨트랙트를 실행할 수 있는 유일한 방법이다.
  • 컨트랙트는 독자적으로 실행되지 않는다. 또한 이더리움도 자율적으로 실행되지 않는다. 모든 것은 트랜잭션으로부터 시작된다.
  • 이더리움은 글로벌 싱글톤 상태 머신이며, 트랜잭션은 이 상태 머신을 움직여서 상태를 변경할 수 있도록 만든다.

2 Transaction의 구조

  • 트랜잭션은 아래의 필드를 포함하는 RLP(Recursive Lenght Prefix) 인코딩 체계를 사용해서 직렬화된 바이너리 메시지다.
  • 이더리움의 모든 숫자는 8비트 배수 길이의 빅엔디안 정수로 인코딩 된다.

2.1 Nonce

  • 발신 EOA에 의해 발행되어 메시지 재사용을 방지하는 데 사용되는 일련번호

    • 블록의 논스와 트랜잭션 논스는 다른 것
  • 해당 주소에서 보낸 트랜잭션 건수와 동일한 스칼라 값

  • 컨트랙트 생성 트랜잭션도 포함

  • EOA가 트랜잭션을 보내는 경우 트랜잭션의 nonce는 현재 EOA의 nonce값이 된다.

2.1.1 논스 추적

  • 논스는 각 계정에서 발생한 확인된 트랜잭션 건수에 대한 최신 통계
  • 논스는 0부터 시작한다.
  • 논스는 블록체인 계정 상태로 저장되지 않기 때문에 논스를 구하려면 해당 주소로 전송되어 확정된 트랜잭션 개수를 세어서 구해야 한다.
  • 트랜잭션 개수는 web3.eth.getTransactionCount과 같이 web3를 사용해서 구할 수 있다

web3.eth.getTransactionCount 사용시 주의 사항

  • web3.eth.getTransactionCount 은 확정된 트랜잭션 개수만 세고 보류 중인 트랜잭션을 세지 않는다
  • 사용자가 빠르게 트랜잭션을 생성한다면 중복된 논스가 발생할 수 있다.

2.1.2 논스의 간격, 중복 논스 및 확인

  • 이더리움 네트워크는 논스에 따라 트랜잭션을 순차적으로 처리한다.
  • 여러 트랜잭션을 순서대로 생성하고 그중 하나가 공식적으로 모든 블록에 포함되지 않으면 이후의 모든 트랜잭션이 ‘멈추고’ 누락된 논스를 기다린다.
    • 논스가 0인 트랜잭션을 전송한 다음 논스가 2인 트랜잭션을 전송하면 두 번째 트랜잭션은 어떤 블록에도 포함되지 않는다.
    • 논스 1인 트랜잭션이 나타날 때 까지 논스 2인 트랜잭션은 멤풀에 저장된다.
  • 논스가 같지만 수신자나 값이 다른 2개의 트랜잭션이 발생하면 하나는 확정되고 하나는 거부된다.
    • 어떤 트랜잭션이 확정되는지는 첫 유효 노드에 도달하는 순서에 따라 결정된다.
  • 유효하지 않거나 가스가 모자란 트랜잭션은 논스 시퀀스에 의도치 않게 ‘갭’을 만들 수 있다.
  • 다시 트랜잭션이 계속되게 하려면 누락된 논스가 있는 유효한 트랜잭션을 전송해야 한다.
  • ‘누락’된 논스가 있는 트랜잭션이 네트워크에 의해 유효성이 검증되면, 이후의 논스가 있는 모든 브로드캐스트된 트랜잭션이 차례대로 유효해진다는 점도 똑같이 염두에 두어야 한다.
  • 트랜잭션을 회수(recall)하는 것은 불가능하다!

2.1.3 동시 실행, 트랜잭션 생성 및 논스

  • 동일한 주소에서 트랜잭션을 생성하는 여러 개의 독립적인 지갑 애플리케이션이 있을 때 동시에 출금을 수행하면 동시 실행 문제가 발생할 수 있다.

해결책

  • 단일 컴퓨터를 사용하여 트랜잭션에 서명하는 컴퓨터에 선착순으로 논스를 할당하는 방법
    • 이 단일 컴퓨터가 단일 실패 지점이 된다.
    • 즉 이 컴퓨터가 장애가 나면 해당 주소에서 출금을 할 수 없다.
  • 트랜잭션을 생성하고 논스를 할당하지 않는 방법
    • 서명되지 않은 이 트랜잭션들을 한 노드의 대기열에 올려서 이 노드가 트랜잭션을 서명하고 논스를 관리할 수 있게 하는 것
    • 이 노드가 단일 실패 지점이 된다.
    • 서명되지 않은 트랜잭션 생성 작업을 병렬 처리할 수 있다.

구현 솔루션

  • 대부분의 구현 솔루션들이 동시 실행을 피하고 거래소에서 출금 트랜잭션을 처리하는 단일 프로세스를 만드는 것처럼 병목 지점을 어쩔 수 없이 받아드린다.
  • 또는 독립적으로 작동하는 다수의 출금 담당 핫 월렛을 설치하고 중간중간에 각 지갑의 밸런스를 다시 채워주는 형식으로 해결하게끔 만든다.

2.2 gasPrice

  • 트랜잭션의 발신자가 지급하는 가스의 가격(웨이)

Gas

  • 가스란 이더리움의 연료이다

    • EVM의 각 명령어는 가스 단위로 미리 정해진 비용이 있다.
    • 거래 데이터의 1바이트당 5가스의 수수료가 발생한다.
  • 가스는 이더가 아니라 이더에 대한 자체 환율을 가진 가상 화폐이다

가스의 용도

  • 가스는 트랜잭션이 사용할 수 있는 자원의 양을 제어한다.
  • 튜링 완전 개발 모델이 DoS 공격이나 실수로 막대한 자원을 소모하는 트랜잭션을 피하기 위해 미터링이 필요하다.
  • 이더 가치의 변동성으로 부터 시스템을 보호한다.
  • 가스는 네트워크의 과부하를 막으며, 이더리움 플랫폼이 계속 운영되도록 하는 인센티브 역할을 한다

Gas Price 설정하기

  • 트랜잭션 생성자는 원하는 가스 가격을 지정할 수 있다.
    • 예를 들어 ‘가스 당 3Gwei를 지불할 용의가 있다.’라고 설정했을 때, 트랜잭션이 1,000,000가스를 소비하고 가스 가격을 3Gwei로 설정하면 해당 트랜잭션에 대한 수수료로 3,000,000Gwei를 지불하게 된다.
  • 대부분의 채굴자들은 가스 가격이 높은 트랜잭션을 선택하여 블록에 포함시키기 때문에 설정한 가스 가격이 높을수록 트랜잭션은 더 빨리 처리된다.
  • 가격을 낮게 책정해도 결국 트랜잭션이 블록에 포함되기는 하지만 그 대기 시간이 상당히 길어질 가능성도 있다.
  • 가스 가격은 일반 사람들에 의하여 무작위로 책정되지는 않고 가스 가격을 측정해주는 사이트가 있다.
    • 이더리움 가스스테이션이라는 사이트인데, 이곳에서 적당한 가스 가격을 선택할 수 있다.
    • 사용되는 가스의 평균값이라고 보면 된다.
    • 빠른 처리를 원할 경우 여기 나온 금액보다 높은 가격을 측정하면 된다.
  • web3는 여러 블록에 걸친 중간 가격을 계산해서 제공하는 getPrice 함수를 제공합니다.

2.3 gasLimit

  • 이 트랜잭션을 위해 구입할 가스의 최대량
  • 가스 한도는 작업 중단 시점을 보장함으로써 무제한으로 이더를 사용하는 것을 방지할 수 있다.
  • 사용자는 트랜잭션을 실행하기 위해 사용할 가스의 최대 금액을 나타내는 가스 한도(Gas Limit)를 설정한다.
  • 가스 한도는 요청하는 작업량의 추측이다. 하지만 추측은 쉬운 일이 아니다.
    • 일반적으로 21,000가스의 한도는 대부분의 거래를 만족한다고 알려져 있다.
  • 한도가 낮으면 작업이 완료되지 않고 거래는 실패하며 그 시점까지 사용된 이더가 손실된다.
  • 한도를 너무 높게 설정하여 한도 전에 작업이 끝나더라도 작업에 사용되지 않은 모든 이더는 다시 되돌려 받을 수 있다

2.4 to(수신자)

  • 목적지 이더리움 주소(20바이트)
  • 주소는 EOA 또는 CA의 주소
  • 이더리음은 이 필드를 검증하지 않는다
  • 트랜잭션을 없는 주소로 보내면 이더가 연소되어 영구적으로 사용할 수 없다
    • 이더 연소를 위한 주소: 0x000000000000000000000000000000000000dEaD

2.5 value(값)

  • 목적지에 보낼 이더의 양
  • 컨트랙트 생성의 경우 새로 생성되는 컨트랙트 계정의 잔액에 예치된다.

2.6 v, r, s

  • EOA의 ECDSA 디지털 서명의 세 가지 구성요소
  • 트랜잭션의 서명에 해당하는 값들이며
  • 트랜잭션 필드에 트랜잭션의 발신자 정보가 없는데 EOA의 공개키를 ECDSA 서명의 v, r, s 구성요소로 부터 알아낼 수 있기 때문이다.
    • 트랜잭션에 발신자 필드가 보인다면 소프트웨어에 의해 추가된 것

2.7 data

  • 가변 길이 바이너리 데이터 페이로드
  • 컨트랙트를 실행할 때 컨트랙트의 어떤 함수를 어떤 인수들을 가지고 실행할 것인지를 표현할 때 사용한다.
  • 상식적으로 값(value)를 보내기위한 트랜잭션이라면 데이터 필드가 필요없다.

3 Transaction Value and Data

  • 트랜잭션의 주요 페이로드는 값과 데이터라는 2개의 필드에 포함된다.

    • 페이로드는 실제로 전달되어야 하는 것을 의미한다고 보면 된다.
  • 값만 있는 트랜잭션은 지급이고 데이터만 있는 트랜잭션은 호출이다.

  • 값과 데이터를 같이 사용하면 지급과 동시에 호출이다.

  • 값과 데이터 둘다 사용하지 않는 것이 가능하나 가스 낭비다.

3.1 EOA 및 CA에 값 전달

  • 값을 포함하는 트랜잭션을 구성하면 지급에 해당한다.

EOA에 값 전달

  • 이러리움은 상태 변경을 기록하여 주소 잔액에 보낸 값을 추가한다

CA에 값 전달

  • 목적지 주소(to)가 컨트랙트라면 EVM은 컨트랙트를 실행하고 트랜잭션 데이터 페이로드에 지정된 함수를 호출한다.
  • 데이터가 없으면 EVM은 풀백 함수를 호출한다.
  • 풀백 함수가 없다면 트랜잭션의 효과는 지갑에 지급하는 것과 마찬가지로 컨트랙트의 잔액을 늘린다.

Fallback Function

  • 트랜잭션에 데이터가 없거나 선언된 함수 이름이 없는 경우 호출되는 컨트랙트의 기본 함수입니다.
  • 컨트랙트는 한 개의 Fallback Function을 가질 수 있다.
  • Fallback Function은 이름이 없고 파라미터와 반환 값이 없다.
contract Faucet {
function () public payable {}

// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// Limit withdrawal amount
require(withdraw_amount <= 100000000000000000);

// Send the amount to the address that requested it
msg.sender.transfer(withdraw_amount);
}
}

3.1 EOA 및 CA에 데이터 페이로드 전달

  • 트랜잭션에 데이터가 포함되어 있으면 받는 주소는 컨트랙트 주소일 가능성이 크다
    • 데이터가 포함된 트랜잭션을 EOA에게 보낼 수 없는 것은 아니다
    • 이러한 경우 데이터의 해석은 EOA에 접근하는데 사용하는 지갑에 달려있다
    • 대부분의 지갑은 트랜잭션에서 수신된 모든 데이터를 무시한다
    • EOA의 데이터 페이로드 해석은 이더리움 합의 규칙이 적용되지 않는다

CA에 데이터 페이로드 전달

  • EVM에 의해 컨트랙트 호출로서 해석된다.
    • 명명된 함수를 호출하고 인코딩된 인수를 함수에 전달한다
  • 데이터 페이로드(32 바이트)는 16진수로 시리얼라이즈한 인코딩이다
  • 페이로드는 함수 선택기와 함수 인수로 구성된다.

함수 선택기

  • 함수 프로토타입의 Keccak-256해시의 처음 4바이트
  • function withdraw(uint withdraw_amount) public {}
    • 이 함수의 함수 프로토타입은 withdraw(uint256)이다.
    • web3.sha3("withdraw(uint256)");
      • 함수 프로토 타입의 Keccak-256해시 -> '0x2e1a7d4d13322e...'
    • 따라서 함수 선택기는 해시의 처음 4바이트 0x2e1a7d4d 이다

함수 인수

  • 함수 인수는 ABI 사양에 정의된 다양한 기본 유형에 대한 규칙에 따라 인코딩된다
  • withdraw_amount 인수로 0.01을 전달해보자
  • withdraw_amount = web3.toWei(0.01, "ether");
    • 16진수로 시리얼라이즈된 부호 없는 빅엔디안 256비트 정수로 인코딩하여 웨이로 표현한 것
    • '10000000000000000'
  • withdraw_amount_hex = web3.toHex(withdraw_amount);
    • '0x2386f26fc10000'

트랜잭션의 데이터 페이로드

  • 이제 함수 선택기를 함수 인수에 추가한다
  • 0x2e1a7d4d0000000000000...2386f26fc10000(32바이트)
  • 이것이 트랜잭션의 데이터 페이로드이다
    • withdraw함수를 호출하고 0.01 이더를 withdraw_amount로 요청함

4 컨트랙트 생성 트랜잭션

  • 컨트랙트 생성 트랜잭션은의 to 필드가 0x0인 트랜잭션을 말한다.
    • 0x0을 제로 어드레스라고 부르며 컨트랙트 작성이라는 특별한 의미로 사용된다
  • 컨트랙트 생성 트랜잭션은 컨트랙트를 생성할 컴파일된 바이트 코드를 데이터 페이로드에 포함하면 된다.

4.1 컴파일

  • 데이터에 담기위해 솔리디티로 작성한 컨트랙트를 컴파일한다.
$ solc --bin Faucet.sol
Binary:
6060604052341561000f57600080fd5b60e58061001d6000396000f30060606040526004361060...

4.2 컨트랙트 생성 트랜잭션 생성

  • to: 0x0(트랜잭션 생성 주소)
  • data: 컴파일된 코드
> src = web3.eth.accounts[0];
> faucet_code = \
"0x6060604052341561000f57600080fd5b60e58061001d6000396000f300606...f0029";
> web3.eth.sendTransaction({from: src, to: 0, data: faucet_code, \
gas: 113558, gasPrice: 200000000000});

"0x7bcc327ae5d369f75b98c0d59037eec41d44dfae75447fd753d9f2db9439124b"

Etherscan showing the contract successfully mined

4.3 receipt 확인

  • 컨트랙트 생성 트랜잭션의 트랜잭션 해시로 receipt을 확인한다.
  • receipt에는 생성된 컨트랙트의 주소가 포함되어 있다.
web3.eth.getTransactionReceipt("0x7bcc327ae5d369f75b98c0d59037eec41d44dfae75447fd753d9f2db9439124b");

{
blockHash: "0x6fa7d8bf982490de6246875deb2c21e5f3665b4422089c060138fc3907a95bb2",
blockNumber: 3105256,
contractAddress: "0xb226270965b43373e98ffc6e2c7693c17e2cf40b",
cumulativeGasUsed: 113558,
from: "0x2a966a87db5913c1b22a59b0d8a11cc51c167a89",
gasUsed: 113558,
logs: [],
logsBloom: \
"0x00000000000000000000000000000000000000000000000000...00000",
status: "0x1",
to: null,
transactionHash: \
"0x7bcc327ae5d369f75b98c0d59037eec41d44dfae75447fd753d9f2db9439124b",
transactionIndex: 0
}