6. 트랜잭션(Tx)

트랜잭션(Tx)

외부 소유 계정(EOA)에서 생성되어 이더리움 블록체인에 기록된 서명된 메시지

EVM에서 상태변경을 유발하거나 컨트랙트를 실행할 수 있는 유일한 방법

(트랜잭션을 통해서만 이더(ether)를 전송하거나, 이더리움 가상 머신(EVM)에 있는 컨트랙트를 실행 할 수 있다.)

 트랜잭션 구조

트랜잭션은 다음과 같은 필드로 구성된다.

nonce 발신 EOA에 의해 발행되어 메시지 재사용을 방지하는 데 사용되는 일련번호.
gas price 발신자가 지급하는 가스 가격
gas limit 이 트랜잭션을 위해 사용할 가스의 최대 사용량
to 수신자 주소
value 수신자에게 보내는 이더(ether) 개수
data 가변길이의 바이너리 데이터(payload)
v, r, s EOA의 ECDSA 디지털서명의 3가지 구성 요소

트랜잭션 메세지의 구조는 RLP(Recursive Length Prefix)인코딩 체계를 사용하여 시리얼라이즈 된다.

(이더리움의 모든 숫자는 8비트 배수 길이의 빅엔디안 정수로 인코딩)

 

트랜잭션에는 발신자 주소를 의미하는 from 필드(=데이터)를 포함하지 않는다.
발신자의 공개키는 공개키를 ECDSA 서명의 구성요소(v,r,s)로부터 알아낼 수 있기 때문이다.
그리고 공개키에서 주소를 계산할 수 있다. (즉, 주소는 공개키에서 파생될 수 있다.)

Nonce

발신 주소의 속성, 단지 발신주소의 컨트랙트에서만 의미를 갖는다.

논스는 명시적으로 블록체인 계정상태에 저장되지않고 해당 주소에서 발생한 확인된 트랜잭션 건수를 세어서 동적으로 계산되는 값이다.

 

EOA가 생성하여 블록체인에 기록된 트랜잭션 개수이다. nonce트랜잭션의 중복 전송을 방지하는데 사용된다.

예를 들면, 10 ether를 가지고 있는 계정이 있다.

그리고 이 계정에서 8 ether를 소비하는 두 개의 트랜잭션 nonce 1과 트랜잭션 nonce 2에 서명하여 전송한다.

이더리움 노드는 트랜잭션을 수신한 순서가 아닌 nonce의 순서로 처리한다.

트랜잭션 nonce 1이 성공적으로 처리되면 계정의 잔액을 2 ether로 줄인다.

그리고 트랜잭션 nonce 2는 잔액이 부족하므로 유효하지 않은 것으로 간주한다.

러나 트랜잭션 nonce 2가 먼저 수신되면 노드는 이를 멤풀에 유지하고 트랜잭션 nonce 1이 도착할 때 까지 트랜잭션의 유효성을 검사하지 않는다.

 

논스 값을 사용하면 동일한 수신자 주소에 동일한 양의 이더를 여러번 보내는 경우에도 각각의 개별 트랜잭션은 고유하다.

따라서 트랜잭션의 일부로 논스를 증가시켜 누군가가 지급한 금액을 복제하는 행위를 막을 수 있다.

 

아래와 같이 web3를 사용하여 nonce를 조회 할 수 있다.

const address = "0x83565088DC95Af0C5BC1DA6F029F7525dFaA5a87"

web3.eth.getTransactionCount(address, function(error, result) {
  console.log(result)
})

 

새로운 트랜잭션을 생성할 때, 다음 nonce를 조회하여 트랜잭션에 포함해야 한다. 

그러나 트랜잭션이 블록체인에 기록(채굴)되기 전까지는 getTransactionCount() 함수의 합계에 포함되지 않는다. 

그래서 블록체인에 기록되기 전인 pending 상태의 트랜잭션은 프로그램상에서 계산하여 저장하고 있어야 한다

 

Gas price

가스(gas)는 이더(ether)와는 별도의 가상 화폐다. 

가스는 개방형(튜링완전) 계산 모델에게 DDoS 공격이나 악의적으로 자원을 소모하게 하는 트랜잭션을 방지하기 위해 사용된다.

 

트랜잭션에 포함되는 gasPrice 필드는 트랜잭션 생성자가 가스 가격을 설정할 수 있다. 가스 가격은 GWei 단위를 사용한다.

현재 이더리움 네트워크의 가스 정보는 https://ethgasstation.info/ 에서 확인가능하다.

트랜잭션의 가스 가격을 높게 설정하면 블록체인에 더 빨리 기록된다.

설정할 수 있는 최소 가스 가격은 0이며, 이것은 수수료가 없는 트랜잭션을 의미한다.

채굴자 입장에서는 보상받을 수 있는 가스비가 높은 순으로 블록체인에 기록하기 때문에, 가스비가 낮은 트랜잭션은 우선순위가 낮다.

 

아래와 같이 web3을 사용하여 이더리움 네트워크 평균 가스 가격을 조회할 수 있다. 이것은 말 그대로 네트워크에서 소비된 가스의 평균 가격이다.

web3.eth.getGasPrice(function(err, res) {
  console.log(res.toNumber())
})

Gas Limit

가스와 관련된 두 번째 중요한 값은 gasLimit이다.

gasLimit는 트랜잭션을 처리하는데 사용할 최대 가스 개수를 설정한다.

단순하게 하나의 EOA에서 다른 EOA로 이더(ether)를 전송하는 트랜잭션에 필요한 가스량은 21,000개로 고정되어 있다.

따라서 이더 전송에 소비되는 수수료를 계산하려면 gasPrice에 21,000를 곱하면 된다.

web3.eth.getGasPrice(function(err, res) {
  console.log(res * 21000)
})

트랜잭션 목적지가 컨트랙트 주소인 경우, 필요한 가스량을 추정할 수는 있지만 정확하게 계산할 수는 없다.

(컨트랙트가 다른 실행경로로 이어지는 조건을 가질 수 있기 떄문)

 

컨트랙트 호출에 필요한 가스량은 컨트랙트 실행이 완료된 후에 결정된다.

만약 gasLimit를 컨트랙트가 실행하는데 필요한 가스량보다 적게 설정한 경우, 컨트랙트는 실행되지 못하고 가스 비용만 소비하게 된다.

대부분은 gasLimit를 초과하여 설정한다.

왜냐하면 컨트랙트가 실행되면 실제로 사용된 가스 비용만 차감하기 때문이다

To

수신자(도착지) 주소를 의미한다.  |  수신자 주소는 EOA 또는 컨트랙트 주소이다.

이더리움 네트워크에서는 주소를 검증하지 않는다.

~> 잘못된 주소로 전송된 이더는 영원히 사용할 수 없게 된다.

Value data

트랜잭션의 주요 "payload" value data라는 두 개의 필드에 의해 사용된다.

 

트랜잭션은 다음과 같이 네 가지 조합이 가능하다.

value와 data 둘다 사용 지급(payment) & 호출(invocation)
value만 사용 value만 있는 트랜잭션은 지급(payment)
data만 사용 data만 있는 트랜잭션은 컨트랙트 호출(invocation)
value와 data 둘다 없음 value도 data도 없는 트랜잭션은 그냥 가스 낭비

Value 전송

value을 포함하는 트랜잭션은 지급(payment)과 동일하다.

EOA 주소 또는 컨트랙트 주소로 value를 포함한 트랜잭션을 전송하면 해당 계정의 ether 잔액에 value가 추가된다.
하지만, 이 트랜잭션은 EOA 주소와 컨트랙트 주소에서는 다르게 작동한다.

 

대상 주소(to)가 컨트랙트 주소인 경우에는 EVM이 컨트랙트를 실행하고 트랜잭션의 data에 지정된 함수를 호출하려고 시도한다.

트랜잭션에 data
불포함
 EVM에서 대상 컨트랙트의 이름없는(fallback) 함수를 호출한다.
그리고 해당 함수가 송금가능(payable)인 경우에는 다음에 수행할 작업을 결정하기 위해 해당 함수를 실행한다.
송금가능(payable) 함수가 성공적으로 실행완료되면 컨트랙트의 ether 잔액에 송금된 금액을 반영하여 컨트랙트 상태가 업데이트된다.
트랜잭션에 data
포함
이 트랜잭션은 컨트랙트 주소로 처리될 가능성이 크다. 
트랜잭션에 포함된 데이터는 EVM에 의해 컨트랙트 함수 호출(function invocation) 로 사용되고 data에 지정된 함수가 호출된다.
명명된 함수를 호출하고 인코딩된 인수를 함수에 전달한다.

ABI호환 컨트랙트(모든 컨트랙트라고 가정할 수 있음)로 전송된 데이터 페이로드는 다음 2가지를 16진수로 시리얼라이즈한 인코딩이다.
- function selector : 함수 프로토타입의 Keccak-256해시의 처음 4바이트이다.
                                  이렇게하면 컨트랙트에서 호출할 함수를 정확하게 식별할 수 있다
- function argument : ABI사양에 정의된 다양한 기본 유형에 대한 규칙에 따라 인코딩된다.

 

  디지털 서명 작동 방법

디지털 서명은 두 단계로 구성된 수학적 체계이다

1. 트랜잭션에서 개인키(서명키)를 사용하여 서명을 만드는 알고리즘

2. 누구나 트랜잭션과 공개키만 사용하여 서명을 검증할 수 있게 해주는 알고리즘

 

 

 

디지털서명(ECDSA, Elliptic Curve Digital Signature Algorithm)

이더리움은 디지털 서명에 ECDSA(Elliptic Curve Digital Signature Algorithm) 알고리즘을 사용한다. 

ECDSA는 타원 곡선 개인/공개키를 기반으로하는 디지털 서명에 사용하는 알고리즘이다. 

디지털 서명은 이더 소유를 증명하고 부인 방지하는데 사용된다.

 

이더리움의 서명된 메세지(=트랜잭션)는 RLP로 인코딩된 트랜잭션 데이터의 Keccak256 해시이고 서명키는 EOA의 개인키이다.

코드로 나타내면 다음과 같다.

Sig = sign(keccak256(rlp.encode(tx.raw)), privateKey)

Sig = 결과 서명 / Keccak-256 해시함수 / RLP인코딩된 트랜잭션


sign
함수는 일반적으로 R S값으로 구성된 서명 Sig를 생성한다.
Sig = (R, S)

 

이더리움 트랜잭션 서명은 다음 순서로 진핸된다.

1.      nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0 9개 필드를 포함하는 트랜잭션 데이터 구조를 만든다.

T = { nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0 }

이 중 맨 끝의 3개 { chainID, 0, 0 }은 EIP-155에 의해 추가된다.

EIP-155는 Simple Replay Attack Protection으로 chainID를 포함하여 다른 네트워크 체인에서 해당 트랜잭션이 replay될 수 없도록 한다

(밑에 EIP-155 참고!)

2.      트랜잭션 데이터 구조를 RLP 인코딩하여 serialize된 메세지를 생성한다.

3.      serialize 메시지의 Keccak256 해시를 계산한다.

4.      원래 EOA의 개인키로 해시에 서명하여 ECDSA 서명을 계산한다.

5.      ECDSA 서명의 계산된 v,r,s값을 트랜잭션에 추가한다.

 

특수서명변수 v는 두가지를 나타내는데, ECDSArecover 함수가 서명을 확인하는 데 도움이 되는 복구 식별자와 chainID이다.

v는 27또는28중 하나로 계산되거나, chainID의 두배에 35또는36이 더해져 계산된다.

(chainID에 대한 내용은 EIP-155참고)

트랜잭션 재생 방지(트랜잭션이 하나의 네트워크에서 다른 네트워크로 전파 및 재생되는 것을 방지)를 포함하는 새로운 서명체계가 EIP-155에 명시되어있고 도입되었다.
이 변경은 트랜잭션의 형식과 서명에 영향을 미치므로, 두가지 형태 중 하나를 취하고 해싱되는 트랜잭션 메세지에 포함된 데이터 필드를 나타내는 세가지 서명 변수 중 첫번째 변수인 v에 주의를 기울여야한다.

 

정리

트랜잭션에 서명하시오 = RLP 시리얼라이즈 된 트랜잭션 데이터의 Keccak-256해시에 서명하시오 라는 뜻

서명은 트랜잭션 자체가 아니라 트랜잭션 테이터의 해시에 적용된다!!!

 

EIP-155 _단순 재생 공격 방지 (Simple Relay Attack Protection)

서명하기 전에 트랜잭션 데이터 내부에 chainID를 포함하여 재생 공격 방지가 가능한 트랜잭션 인코딩을 지정한다.

이렇게 하면 하나의 블록체인(예를들어 이더리움네트워크)에 대해 생성된 트랜잭션이 다른 블록체인(예를들어 이더리움클래식)에서 유효하지 않다.

하나의 네트워크에서 브로드캐스트되는 트랜잭션을 다른 네트워크에서 반복하여 사용할 수 없기 때문에 "재생 공격 방지(replay attack protection)"라고 한다.

따라서 표준의이름 그대로 한 네트워크에서 전파된 트랜잭션은 다른네트워크에서 재생될 수 없다.

 

EIP-155는 트랜잭션 데이터 구조의 주요 6개필드에 chainID,0,0의 3개 필드를 추가한다.

이 세 필드는 인코딩되고 해싱되기 전에 트랜잭션 테이터에 추가된다

따라서 트랜잭션의 해시가 변경되어 나중에 서명이 적용된다.

chainID가 서명된 데이터에 포함됨으로써 트랜잭션 서명은 chainID가 수정되면 서명이 무효화되어 데이터변경을 식별할 수 있다.

따라서 EIP-155는 서명의 유효성이 chainID에 의존하기 때문에 다른 체인에서 트랜잭션을 재생할 수 없다.

 

결과로 생성되는 트랜잭션 구조는 RLP로 인코딩되고 해싱되고 서명된다.

서명 알고리즘은 v접두어에 chainID를 인코딩하기 위해 약간 수정된다.

 

자세한 내용은 EIP-155사양을 참고하자

 

  서명 접두어 값(v) 및 공개키 복구

발신자의 공개키가 ECDSA서명을 통해 직접 계산될 수 있기에 트랜잭션 메세지는 발신자(from)필드를 포함하지 않는다고 하였다.

공개키가 있으면 쉽게 주소를 계산할수있다

- 공개키 복구 = 서명자의 공개키를 복구하는 프로세스

 

계산된 값 r과 s가 주어지면 2개의 가능한 공개키를 계산할 수 있다.

 

1. 서명에 있는 x좌표인 r값에서 2개의 타원곡선 점 R과 R'를 계산한다

(타원곡선은 x축에 대칭하므로 어떤값x에 대해서도 곡선에 2개의 가능한 값이 있기에 2개의 점이있다)

2. r에서 r의 곱셈 역함수인 r의 -1승을 계산한다.

3. 메세지 해시의 n 최하위 비트인 z를 계산한다. (여기서 n은 타원곡선의 차수다)

 

 

좀 더 효율적으로 하기 위해, 트랜잭션 서명에는 2개의 가능한 R값 중 임시 공개키가 무엇인지 알려주는 접두어값 v가 포함된다

v가 짝수이면 R은 올바른 값이고, v가 홀수이면 R'이다.

그런식으로  R에 대해 하나의 값만, K에 대해 하나의 값만 계산해야한다.

 

  서명 및 전송 분리(오프라인 서명)

트랜잭션이 서명되면, 트랜잭션은 이더리움 네트워크로 전송할 준비가 된다.

트랜잭션 생성, 서명, 브로드캐스트는 일반적으로 단일작업(web3.eth.sendTransaction)에 의해 처리된다.

그러나, 서명된 트랜잭션이 있으면 web3.eth.sendSignedTransaction을 사용하여 트랜잭션을 16진수로 인코딩하고 서명해서 이더리움 네트워크에 전송할 수 있다

 

이처럼 서명과 전송을 분리하려는 가장 큰 이유는 보안문제이다.

트랜잭션을 서명하는 컴퓨터에는 개인키가 메모리에 로드되어있어야한다

전송을 수행하는 컴퓨터는 인터넷에 연결되어 있어야 하며, 이더리움 클라이언트를 실행해야 한다

 

이 두 기능이 하나의 컴퓨터에 있으면 온라인 시스템에 개인키가 있게되므로 매우 위험한 상황이 된다

그래서 서명 및 전송기능을 분리하여 각기 다른 시스템(오프라인 및 온라인 장치 각각)에서 수행하는것을 오프라인서명이라고 한다

 

1. 현재이 논스 및 사용가능한 자금을 검색할 수 있는 계정에서 서명되지 않은 트랜잭션을 온라인 컴퓨터에 만든다.

2. 서명되지 않은 트랜잭션을 QR코드 또는 USB플래시 드라이브를 통해 트랜잭션 서명을 위한 에어갭 오프라인 장치로 전송한다.

3. 이더리움 블록체인에 브로드캐스트하기 위해, 서명된 트랜잭션을 QR코드 또는 USB플래시 드라이브를 통해 온라인 장치로 전송한다

 

 

 

 

✅ 트랜잭션 처리 순서

 A -> B 로 트랜잭션을 보낸다.
  1. 해당 트랜잭션에 대해 A가 자신의 개인키로 ECDSA 전자 서명 암호화한다.
  2. 이더리움 클라이언트(노드)는 해당 트랜잭션을 네트워크에 연결되어 있는 모든 노드에 브로드캐스팅 한다.
  3. 마이너에서 유효성을 검증한다.
    트랜잭션이 문법에 맞는지
    A의 공개키를 이용해 해당 전자 서명이 유효한지 (신원 검증)
    A의 Account에 있는 Nonce와 일치하는지 (이중 지불 방지)
    가스 비용을 계산하고, B의 어카운트 주소를 확인한다.
    (모든 20바이트 값은 유효한 것으로 간주함 - 이더리움은 올바르게 파생되었는지 여부를 판단할 수 없다.)
  4. 모든 검증 작업이 완료되면 최종 실행을 위해 해당 트랜잭션을 트랜잭션 풀에 등록한다.
  5. 마이너가 트랜잭션 풀에서 트랜잭션을 처리 시, 가스 실행 비용이 높은 순으로 트랜잭션을 선택한다.
    이더 전송 : B에 송금
    B가 contract : contract code 실행

✅ 마이너의 트랜잭션 처리 과정

  1. 블록에 담을 트랜잭션들을 Transaction Pool에서 가져온다.
  2. 트랜잭션들에 대해 Nonce, Gas Price로 순서를 정한다.
  3. 마이너가 트랜잭션에 설정된 값 (Gas_Limit * Gas_Price) 만큼을 발신 주소로부터 tx fee로 가져온다.
  4. 트랜잭션을 실행하며, 명령어에 따라 가스를 소모한다.
    남는 가스는 해당 트랜잭션 발신 주소로 다시 돌려준다.
    가스가 모자라는 경우 트랜잭션이 Revert되며, 가스는 다시 돌려주지 않는다. (Out Of Gas)
  5. 실행된 트랜잭션들을 블록에 담는다. (개별 Gas Limit의 총합은 Block Gas Limit를 넘길 수 없다.)

 

  트랜잭션 전파

이더리움 네크워크는 '플러드 라우팅' 프로토콜을 사용
노드는 P2P네트워크에 연결되어 참여하는 이더리움 클라이언트를 지칭한다.


트랜잭션 전파는 서명된 트랜잭션을 생성(또는 오프라인에서 수신)한 이더리움 노드에서 시작한다.

➡️ 트랜잭션이 검증된 후에 트랜잭션을 생성한 '직접'연결된 다른 모든 이더리움 노드로 전송

➡️ 각 이더리움 노드는 이웃이라고 불리는 적어도 13개의 다른 노드(이웃 노드)와 연결을 유지하고, 그 노드들은 트랜잭션을 수신하자마자 유효성을 검사한다

➡️ 동의하면 사본을 저장하고 모든 이웃에게 전파한다

➡️ 결국 이웃 노드들에 전파가 되면서 트랜잭션이 모든 노드에 전파됨

➡️ 모든 노드가 사본을 가질 때까지 원래노드에서 바깥쪽으로 물결치며 퍼진다(flooding)

  블록체인에 기록

유효한 트랜잭션이 결국 트랜잭션 블록에 포함되어 이더리움 블록체인에 기록된다

트랜잭션이 블록으로 채워지면 계정의 잔액을 수정하거나 내부상태를 변경하는 컨트랙트를 호출하여 이더리움 싱글톤 상태를 수정한다.

이러한 변경사항은 트랜잭션 영수증 형식으로 트랜잭션과 함께 기록된다.

 

생성, EOA에 의한 서명, 전파, 채굴까지 완료된 트랜잭션은

싱글톤의 상태를 변경하고 블록체인에서 지울 수 없는 기록을 남긴다.

 

 

 

 

728x90

'5. Blockchain > Mastering Ethereum' 카테고리의 다른 글

5. 지갑  (0) 2022.07.14
4. 암호학  (0) 2022.07.14
2. 이더리움 기초  (0) 2022.07.14
1. 이더리움이란 무엇인가?  (0) 2022.07.14
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유