6 장. 트랜잭션 — 이더리움을 활용한 결제 시스템

birdgang
50 min readJul 8, 2024

--

트랜잭션은 블록체인 네트워크의 핵심입니다. 이 장에서는 트랜잭션의 기본 개념, 이더리움 네트워크에서의 트랜잭션 처리 과정, 트랜잭션 수수료, 스마트 계약을 이용한 트랜잭션, 그리고 보안과 관련된 주요 사항들을 다룹니다. 이더리움 블록체인은 트랜잭션을 통해 네트워크를 운영하고, 탈중앙화된 애플리케이션을 실행하며, 스마트 계약을 발동합니다. 트랜잭션의 이해는 이더리움 생태계를 탐구하는 데 필수적입니다.

1. 트랜잭션 구조

이더리움 네트워크에서 트랜잭션은 매우 중요한 역할을 하며, 모든 트랜잭션은 특정 구조를 따릅니다. 이 구조는 트랜잭션의 유효성을 검증하고 블록체인에 안전하게 기록하기 위해 필수적 입니다. 이더리움 트랜잭션의 구조를 자세히 살펴보고, 각 필드의 의미와 역할을 설명 해 보도록 하겠습니다.

  • Nonce : 각 계정에서 발행한 트랜잭션의 고유 번호입니다. 특정 계정이 동일한 트랜잭션 을 두 번 발송하는 것을 방지하는 역할을 합니다.
  • Gas Price : 트랜잭션 을 처리하는 데 드는 비용을 나타내며, 트랜잭션이 블록체인에 포함되기 위해 마이너 들에게 지불되는 금액 입니다. Gwei 단위로 표시됩니다.
  • Gas Limit : 트랜잭션 이 소비할 수 있는 최대 가스의 양을 나타냅니다. 이 값은 트랜잭션이 수행할 연산의 복잡성에 따라 결정 됩니다.
  • To : 트랜잭션의 수신자 주소를 나타냅니다. 이 필드는 160 비트 크기의 이더리움 주소로, 보통 0x 로 시작하는 40 자리의 16진수 문자열 입니다.
  • Value : 트랜잭션 에서 전송되는 이더(ETH)의 양을 나타냅니다. 이 값은 Wei 단위로 표시됩니다. (1 ETH = 10¹⁸ Wei)
  • Data : 트랜잭션 과 함께 전송되는 임의의 데이터를 포함 합니다. 스마트 계약을 호출하거나 실행할 때 사용 됩니다.
  • v, r, s : 이 필드들은 트랜잭션 의 디지털 서명입니다. 서명은 트랜잭션의 무결성을 보장하고, 보낸 사람의 신원을 검증하는 데 사용 됩니다.

예시

{
"nonce": "0x15",
"gasPrice": "0x09184e72a000",
"gasLimit": "0x2710",
"to": "0x recipient_address",
"value": "0x9184e72a",
"data": "0x6f2f2d7",
"v": "0x1c",
"r": "0x5e1d3a76fbf824220e32e465a978800b2c2a0e9c",
"s": "0x5d1d3a76fbf824220e32e465a978800b2c2a0e9b"
}
  • nonce : 0x15 (21)
  • gasPrice : 0x09184e72a000 (10000000000000 Wei)
  • gasLimit : 0x2710 (10000)
  • to : 0x recipient_address (받는 사람 주소)
  • value : 0x9184e72a (25000000000000000 Wei 또는 0.025 ETH)
  • data : 0x6f2f2d7 (스마트 계약 실행 시 포함되는 데이터)
  • v, r, s : 트랜잭션의 디지털 서명

2. 트랜잭션 논스

트랜잭션 논스(Nonce)는 각 계정의 트랜잭션을 유일하게 식별하는 숫자 입니다. 간단히 말해, 특정 계정에서 발생하는 트랜잭션의 순서를 추적하고 중복을 방지하기 위해 사용됩니다. 논스는 계정이 생성한 트랜잭션 의 수를 나타내는 값으로, 첫 번째 트랜잭션은 논스가 0, 두 번째는 1, 세 번째는 2가 됩니다.

a. 트랜잭션 논스 의 중요성

  • 순서 보장 : 트랜잭션 논스는 동일한 계정에서 발생하는 트랜잭션의 순서를 보장 합니다. 이는 스마트 계약의 정확한 실행을 위해 필수적 입니다.
  • 재사용 방지 : 논스는 동일한 트랜잭션 이 여러 번 사용되지 않도록 방지합니다. 즉, 재생 공격(replay attack)을 막는 역할을 합니다.
  • 트랜잭션 병렬 처리 : 이더리움 네트워크는 여러 트랜잭션 을 동시에 처리할 수 있지만, 각 계정의 트랜잭션 은 논스를 통해 순차적으로 처리 됩니다.

예시 1 : 단순한 트랜잭션

Alice 가 이더리움 네트워크에서 두 번의 트랜잭션 을 발생 시킨다고 가정해 봅시다.

1) 첫 번째 트랜잭션 : Alice 는 Bob 에게 1 ETH 를 전송 합니다.
- Alice의 현재 논스 : 0
- 트랜잭션 논스 : 0

2) 두 번째 트랜잭션 : Alice 는 Charlie 에게 2 ETH를 전송 합니다.
- Alice의 현재 논스 : 1
- 트랜잭션 논스 : 1

이 경우, 두 번째 트랜잭션 은 첫 번째 트랜잭션 이 블록체인 에 기록된 후에야 처리될 수 있습니다.

예시 2 : 스마트 계약 실행

Alice 가 스마트 계약을 통해 특정 기능을 호출한다고 가정 합니다.

1. 첫 번째 트랜잭션: Alice는 스마트 계약 A에 함수 f1을 호출합니다.
- Alice의 현재 논스 : 0
- 트랜잭션 논스 : 0

2. 두 번째 트랜잭션: Alice는 스마트 계약 A에 함수 f2를 호출합니다.
- Alice의 현재 논스 : 1
- 트랜잭션 논스 : 1

이 경우, 스마트 계약은 논스 순서에 따라 함수 f1 을 먼저 실행하고, 그 후에 f2 를 실행합니다. 이를 통해 함수 호출 순서가 보장되고, 의도한 대로 계약이 수행 됩니다.

b. 논스 관련 문제 및 해결 방안

  • 논스 충돌 : 여러 트랜잭션 이 동시에 제출될 때 논스 충돌이 발생할 수 있습니다. 이를 방지하기 위해 이더리움 클라이언트는 트랜잭션 을 제출하기 전 논스를 미리 계산하여 중복을 방지 합니다.
  • 논스 미스매치 : 논스가 맞지 않으면 트랜잭션 이 실패할 수 있습니다. 이를 해결하기 위해 클라이언트는 최신 논스 를 자동으로 추적하고 사용 합니다.

c. 결론

트랜잭션 논스는 이더리움 블록체인 에서 중요한 역할을 합니다. 트랜잭션 의 순서와 유일성 을 보장하며, 재생 공격을 방지하는 등 다양한 기능을 수행 합니다. 위에서 살펴본 예시와 같이 논스는 트랜잭션 의 정확하고 안전한 처리를 위해 필수적인 요소입니다.

3. 논스 추적

논스를 추적하는 것은 이더리움 네트워크에서 매우 중요합니다. 이를 위해 다음과 같은 단계를 따릅니다.

1. 계정의 현재 논스 조회

이더리움 계정의 현재 논스를 조회하기 위해, 이더리움 클라이언트나 라이브러리를 사용합니다. 예를 들어, ‘web3.js’ 라이브러리를 사용할 수 있습니다.

const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

async function getNonce(address) {
const nonce = await web3.eth.getTransactionCount(address);
console.log(`Current nonce for address ${address}: ${nonce}`);
}

getNonce('0xYourEthereumAddress');

이 코드는 특정 이더리움 주소의 현재 논스 를 반환 합니다.

2. 트랜잭션 생성 시 논스 설정

트랜잭션을 생성할 때, 올바른 논스를 설정하는 것이 중요합니다. 트랜잭션 생성 시 논스를 명시적으로 설정하지 않으면, 클라이언트가 자동으로 현재 계정의 논스를 설정합니다.

async function sendTransaction(fromAddress, toAddress, value) {
const nonce = await web3.eth.getTransactionCount(fromAddress);
const tx = {
from: fromAddress,
to: toAddress,
value: web3.utils.toWei(value, 'ether'),
gas: 21000,
nonce: nonce
};

const signedTx = await web3.eth.accounts.signTransaction(tx, 'YOUR_PRIVATE_KEY');
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log(`Transaction receipt: ${receipt}`);
}

sendTransaction('0xYourEthereumAddress', '0xRecipientAddress', '0.1');

3. 논스 관리 및 트랜잭션 재시도

네트워크 문제나 기타 이유로 트랜잭션 이 실패할 경우, 동일한 논스로 다시 시도할 수 있습니다. 실패한 트랜잭션의 논스를 추적하고, 동일한 논스를 사용하여 트랜잭션을 재시도 하는 것이 중요 합니다.

async function resendTransaction(txHash) {
const tx = await web3.eth.getTransaction(txHash);
if (tx) {
const receipt = await web3.eth.sendTransaction(tx);
console.log(`Resent transaction receipt: ${receipt}`);
} else {
console.log(`Transaction with hash ${txHash} not found.`);
}
}

resendTransaction('0xFailedTransactionHash');

4. 논스의 간격, 중복 논스 및 확인

논스 간격의 의미, 중복 논스의 문제, 그리고 이를 확인하는 방법에 대해 설명 합니다.

a. 논스의 간격

논스는 계정별로 순차적으로 증가 합니다. 즉, 한 계정에서 발생한 첫 번째 트랜잭션의 논스는 0, 두 번째는 1, 세 번째는 2와 같이 증가합니다. 이 논스의 간격을 올바르게 유지하는 것이 중요합니다. 논스의 간격이 맞지 않으면 트랜잭션이 실패할 수 있습니다.

예를 들어, Alice 가 트랜잭션 을 3번 발생 시키려 한다고 가정해 봅시다. 
첫 번째 트랜잭션의 논스는 0, 두 번째는 1, 세 번째는 2가 됩니다.
Alice 의 현재 논스가 0 일 때, 두 번째 트랜잭션의 논스를 2로 지정하면 어떻게 될까요 ?


1. Alice 의 논스 0 트랜잭션 -> 성공
2. Alice 의 논스 2 트랜잭션 -> 실패 (논스 1 트랜잭션이 아직 전송되지 않았기 때문에)

이 경우 Alice 는 논스의 간격을 유지하지 못해 두 번째 트랜잭션이 실패하게 됩니다. 따라서, 각 트랜잭션의 논스는 순차적으로 지정해야 합니다.

b. 중복 논스

중복 논스 란 동일한 논스를 가진 트랜잭션이 여러 개 있는 경우를 말합니다. 이는 트랜잭션이 의도치 않게 여러 번 실행되거나, 하나의 트랜잭션 만 유효하게 처리되고 나머지는 무시되게 만듭니다.

Alice 가 논스 0을 가진 트랜잭션을 두 번 보내는 경우 :

1. Alice의 논스 0 트랜잭션 1 -> 성공
2. Alice의 논스 0 트랜잭션 2 -> 실패 (이미 사용된 논스)

이 경우 첫 번째 트랜잭션만 유효하고 두 번째는 중복 논스로 인해 실패 합니다. 따라서 논스는 항상 고유해야 하며, 한 번 사용된 논스는 다시 사용할 수 없습니다.

5. 동시 실행, 트랜잭션 생성

이더리움 네트워크에서는 다수의 트랜잭션 이 동시에 발생할 수 있습니다. 그러나 블록체인 특성상 트랜잭션 은 결국 순차적으로 블록에 기록 됩니다. 이를 위해 이더리움은 여러 트랜잭션 을 동시에 처리하기 위해 가스 와 트랜잭션 수수료 메커니즘을 사용 합니다. 채굴자는 가스 가격이 높은 트랜잭션 을 우선 처리하여 수익을 극대화 하려고 합니다.

  • 가스 가격(Gas Price) : 사용자가 트랜잭션에 대해 지불할 의사가 있는 단위 가스당 가격.
  • 가스 한도(Gas Limit) : 트랜잭션이 소비할 수 있는 최대 가스량.

예를 들어, 두 사용자가 동시에 트랜잭션 을 생성 하면 채굴자는 더 높은 가스 가격을 제시한 트랜잭션 을 우선 처리 합니다.

두 사용자가 각각 1 ETH를 전송하는 트랜잭션을 생성합니다.

User A: gasPrice = 20 Gwei, gasLimit = 21000, to = 0xRecipientAddress, value = 1 ETH
User B: gasPrice = 50 Gwei, gasLimit = 21000, to = 0xRecipientAddress, value = 1 ETH

마이너는 User B의 트랜잭션을 먼저 처리합니다(가스 가격이 더 높음).

트랜잭션 생성

이더리움에서 트랜잭션 은 다음과 같은 필드를 포함합니다 :

  • nonce : 해당 계정에서 발생한 트랜잭션의 순서 번호.
  • gasPrice : 단위 가스당 지불할 가격.
  • gasLimit : 트랜잭션이 소비할 수 있는 최대 가스량.
  • to : 수신자의 주소.
  • value : 전송할 이더(ETH) 양.
  • data : 실행할 코드 또는 전달할 데이터.
  • v, r, s : 트랜잭션 서명을 위한 필드.

트랜잭션 생성 과정은 다음과 같습니다 :

1. 사용자는 트랜잭션 을 작성하고 서명 합니다.
2. 서명된 트랜잭션 은 네트워크로 브로드 캐스트 됩니다.
3. 마이너는 트랜잭션 을 수신하고 유효성을 검사 합니다.
4. 유효한 트랜잭션 은 블록에 포함되어 블록체인 에 추가 됩니다.

계정 A가 계정 B로 1 ETH를 전송하는 트랜잭션 을 생성합니다.

{
"nonce": 0,
"gasPrice": "0x09184e72a000",
"gasLimit": "0x2710",
"to": "0xRecipientAddress",
"value": "0x1BC16D674EC80000",
"data": "",
"v": "0x1b",
"r": "0x5e1d3a76fbf824220e13c939a3d3e3f1bc1b4a5c7d2e2b58e3c5abf29c6d3a4",
"s": "0x4ba816b0dbf88a3e44e1a9421a3d5b6f5d4e3e4a4b7c1d1c4b3b4b7c3e2a5a7"
}

6. 트랜잭션 가스 란 ?

이더리움에서 트랜잭션 을 실행 하려면 가스라는 수수료를 지불해야 합니다. 가스는 이더리움 네트워크의 연산 리소스를 사용하기 위한 비용을 의미하며, 이더(ETH)로 지불 됩니다. 각 트랜잭션 에는 일정량의 가스가 필요하며, 이는 트랜잭션 의 복잡성에 따라 다릅니다.

가스는 두 가지 주요 요소로 구성됩니다.

1. 가스 한도(Gas Limit) : 트랜잭션이 소비할 수 있는 최대 가스 양을 설정합니다. 사용자는 트랜잭션을 보낼 때 이 값을 설정 합니다.

2. 가스 가격(Gas Price) : 가스 단위당 지불할 금액을 의미 합니다. 가스 가격은 Gwei 단위로 표시 됩니다 (1 Gwei = 0.000000001 ETH).

가스 비용 계산

  1. 가스 비용 = 가스 한도 × 가스 가격
  2. 간단한 이체 :
  • 보통의 이더 전송 : 21,000 가스
  • 가스 가격 : 20 Gwei
  • 가스 비용 = 21,000 가스 * 20 Gwei = 0.00042 ETH

2. 스마트 계약 실행 :

  • 복잡한 스마트 계약 호출: 100,000 가스
  • 가스 가격: 50 Gwei
  • 가스 비용 = 100,000 가스 * 50 Gwei = 0.005 ETH

가스 한도와 가스 가격 설정

사용자는 트랜잭션을 보낼 때 가스 한도와 가스 가격을 설정할 수 있습니다. 가스 한도를 너무 낮게 설정하면 트랜잭션이 실패할 수 있으며, 너무 높게 설정하면 불필요한 비용을 지출하게 됩니다. 가스 가격은 네트워크의 혼잡도 에 따라 다르며, 높은 가스 가격을 설정하면 트랜잭션 이 더 빨리 처리될 가능성이 높습니다.

네트워크 혼잡과 가스 가격

이더리움 네트워크의 혼잡도 는 가스 가격에 큰 영향을 미칩니다. 네트워크가 혼잡할 때는 가스 가격이 상승하고, 여유로울 때는 가스 가격이 낮아집니다. 이를 효과적으로 관리하기 위해, 사용자는 네트워크 상황을 모니터링하고 적절한 가스 가격을 설정 해야 합니다.

가스 최적화

스마트 계약을 설계할 때 가스 비용을 최적화하는 것이 중요합니다. 효율적인 코드 작성과 불필요한 연산을 줄이는 방법으로 가스 비용을 절감할 수 있습니다. 이는 사용자에게 더 낮은 비용으로 서비스를 제공하는 데 도움이 됩니다.

7. 트랜잭션 수신자

이더리움 네트워크에서 트랜잭션이 발생하면, 이는 송신자에서 수신자로 암호화폐(주로 ETH) 또는 토큰이 전송되는 것을 의미 합니다. 트랜잭션 수신자는 트랜잭션의 목적지로, 수신자의 이더리움 주소로 송금이 이루어집니다. 따라서 트랜잭션 수신자의 역할과 기능, 그리고 관련된 주요 개념을 살펴보겠습니다.

1. 트랜잭션 수신자의 역할

  • 자산 수신 : 수신자는 송신자가 보낸 이더리움 또는 토큰을 받습니다. 이때, 트랜잭션은 블록체인 에 기록되며 수신자는 자신의 지갑에서 자산을 확인 할 수 있습니다.
  • 스마트 계약 실행 : 수신자는 단순한 지갑 주소일 수도 있지만, 종종 스마트 계약 주소일 수도 있습니다. 스마트 계약 주소로 트랜잭션 이 보내질 경우, 스마트 계약의 특정 기능이 실행 됩니다. 예를 들어, 탈 중앙화 거래소(DEX)에서 토큰을 교환하거나 디앱(dApp)에서 서비스를 이용할 때 이러한 방식이 사용 됩니다.
  • 이더리움 주소 관리 : 수신자는 유효한 이더리움 주소를 가지고 있어야 합니다. 이는 0x로 시작하는 40자 길이의 문자열로, 개인키에 의해 관리됩니다.

2. 트랜잭션 수신자의 종류

  • 개인 사용자 : 일반적인 사용자로, 개인 지갑을 통해 ETH 나 토큰을 수신 합니다.
  • 기업 : 기업 지갑으로 트랜잭션 을 수신하여 다양한 사업 활동에 이용 합니다.
  • 스마트 계약 : 자동으로 특정 작업을 수행하는 코드로, 주로 dApp 과 연동 되어 사용됩니다.

3. 트랜잭션 수신 과정

  • 트랜잭션 생성 : 송신자가 트랜잭션 을 생성 합니다. 여기에는 수신자의 주소, 송금할 금액, 가스비 등이 포함 됩니다.
  • 트랜잭션 전파 : 트랜잭션 은 네트워크로 전파되어 여러 노드 에 의해 검증 됩니다.
  • 블록에 포함 : 검증된 트랜잭션 은 블록에 포함되어 블록 체인 에 기록 됩니다.
  • 수신 확인 : 수신자는 블록 체인 을 통해 자신의 트랜잭션 을 확인하고, 자산이 도착했는지 확인합니다.

4. 예시 : 이더리움 트랜잭션 수신

예를 들어, Alice 가 Bob 에게 1 ETH 를 보내는 상황을 가정해 봅시다. 이 경우 Alice가 송신자, Bob 이 수신자가 됩니다.

1. Alice 는 자신의 지갑에서 트랜잭션 을 생성 합니다. 트랜잭션 에는 다음 정보가 포함됩니다.

수신자 주소 : Bob 의 이더리움 주소
송금할 금액 : 1 ETH
가스비 : 트랜잭션 을 처리하기 위한 가스비

2. 트랜잭션 전송 : Alice 는 트랜잭션 을 네트워크에 전송 합니다.
트랜잭션은 여러 노드에 의해 검증되고, 최종적으로 블록에 포함 됩니다.

3. Bob의 확인 : Bob은 자신의 지갑에서 트랜잭션을 확인 합니다.
블록체인에 기록된 트랜잭션을 통해 1 ETH가 자신의 지갑으로 도착 했음을 확인할 수 있습니다.

트랜잭션 수신자는 이더리움 네트워크에서 중요한 역할을 합니다. 수신자는 단순히 자산을 받는 것을 넘어서, 스마트 계약을 통해 다양한 자동화된 작업을 실행할 수 있습니다. 이는 탈중앙화된 금융 시스템과 애플리케이션에서 중요한 구성 요소로 작용하며, 이더리움의 유연성과 확장성을 보여주는 중요한 예시입니다.

8. 트랜잭션 결과 데이터 란 ?

트랜잭션 결과 데이터는 블록체인에서 트랜잭션이 처리된 후 생성되는 다양한 정보를 포함합니다. 이 데이터는 트랜잭션의 성공 여부, 가스 사용량, 이벤트 로그 등을 포함합니다. 트랜잭션 결과 데이터는 트랜잭션을 추적하고 디버깅하는 데 매우 유용합니다.

주요 요소

1. 트랜잭션 해시

  • 트랜잭션이 블록체인에 기록될 때 생성되는 고유한 식별자입니다.
    예시: `0x1234abcd…`

2. 상태

  • 트랜잭션이 성공했는지 실패했는지를 나타냅니다. 성공이면 `1`, 실패면 `0`으로 표시됩니다.

3. 가스 사용량

  • 트랜잭션이 처리되는데 사용된 가스의 양입니다. 가스는 이더리움 네트워크에서 작업을 수행하는 데 필요한 연료로, 트랜잭션 비용을 계산하는 데 사용됩니다.

4. 로그

  • 스마트 계약에서 발생하는 이벤트와 관련된 데이터입니다. 각 이벤트는 트랜잭션의 실행 중에 발생한 특정 동작을 나타냅니다.

5. 블록 번호

  • 트랜잭션이 포함된 블록의 번호입니다.
    예시: `1234567`

6. 트랜잭션 수수료

  • 트랜잭션을 처리하기 위해 지불한 총 비용입니다. 이는 가스 사용량과 가스 가격의 곱으로 계산됩니다.

다음은 트랜잭션 결과 데이터의 예시입니다

{
"transactionHash": "0x1234abcd5678efgh9101ijklmnopqrstuvwx",
"status": 1,
"blockNumber": 1234567,
"gasUsed": 21000,
"logs": [
{
"address": "0xabcd1234efgh5678ijkl9101mnopqrstuvwx",
"topics": [
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
],
"data": "0x"
}
],
"transactionFee": "0.0021 ETH"
}

위 예시는 성공적으로 처리된 트랜잭션의 결과 데이터입니다. 트랜잭션 해시는 `0x1234abcd5678efgh9101ijklmnopqrstuvwx`이며, 블록 번호 `1234567`에 포함되어 있습니다. 트랜잭션은 21,000 가스를 사용했으며, 트랜잭션 수수료는 `0.0021 ETH`입니다. 로그에는 스마트 계약 주소와 이벤트 데이터가 포함되어 있습니다.

트랜잭션 결과 데이터 활용

트랜잭션 결과 데이터는 다음과 같은 목적으로 활용될 수 있습니다:

  • 디버깅 및 문제 해결 : 실패한 트랜잭션의 원인을 분석하여 문제를 해결할 수 있습니다.
  • 트랜잭션 추적 : 특정 트랜잭션이 블록체인에서 어떻게 처리되었는지 추적할 수 있습니다.
  • 스마트 계약 분석 : 스마트 계약의 실행 결과를 분석하여 올바르게 동작하는지 확인할 수 있습니다.

이처럼 트랜잭션 결과 데이터는 이더리움 블록체인에서 중요한 정보를 제공하며, 이를 통해 보다 신뢰성 있는 결제 시스템을 구축할 수 있습니다.

9. EOA 및 컨트랙트에 값 전달

1. 외부 소유 계정(EOA)에 값 전달

  • Alice가 Bob에게 1 ETH를 전송하는 트랜잭션을 생성합니다.
  • Alice는 자신의 개인 키를 사용하여 트랜잭션에 서명합니다.
  • 서명된 트랜잭션은 네트워크에 브로드캐스트되고, 마이너에 의해 처리됩니다.
  • 트랜잭션이 블록체인에 포함되면 Bob의 계정은 1 ETH가 증가하고, Alice의 계정은 1 ETH와 가스 비용만큼 감소합니다.
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const fromAddress = 'Alice의 주소';
const toAddress = 'Bob의 주소';
const privateKey = 'Alice의 개인 키';

const sendTransaction = async () => {
const nonce = await web3.eth.getTransactionCount(fromAddress, 'latest');

const transaction = {
to: toAddress,
value: web3.utils.toWei('1', 'ether'),
gas: 21000,
nonce: nonce,
gasPrice: web3.utils.toWei('20', 'gwei')
};

const signedTx = await web3.eth.accounts.signTransaction(transaction, privateKey);
await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
};

sendTransaction();

2. 스마트 컨트랙트에 값 전달

  • 스마트 컨트랙트는 특정 조건에 따라 동작하는 자율적인 계약 코드입니다. 스마트 컨트랙트로 값을 전달하면 특정 함수가 실행될 수 있습니다.
  • Alice가 특정 스마트 컨트랙트에 2 ETH를 전송합니다.
  • 스마트 컨트랙트는 수신된 이더를 특정 로직에 따라 처리합니다.
// ExampleContract.sol
pragma solidity ^0.8.0;

contract ExampleContract {
event Received(address, uint);

receive() external payable {
emit Received(msg.sender, msg.value);
}

function getBalance() public view returns (uint) {
return address(this).balance;
}
}
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const fromAddress = 'Alice의 주소';
const contractAddress = '스마트 컨트랙트 주소';
const privateKey = 'Alice의 개인 키';

const sendTransaction = async () => {
const nonce = await web3.eth.getTransactionCount(fromAddress, 'latest');

const transaction = {
to: contractAddress,
value: web3.utils.toWei('2', 'ether'),
gas: 30000,
nonce: nonce,
gasPrice: web3.utils.toWei('20', 'gwei')
};

const signedTx = await web3.eth.accounts.signTransaction(transaction, privateKey);
await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
};

sendTransaction();

이 예제에서는 Alice가 스마트 컨트랙트에 2 ETH를 전송하고, 스마트 컨트랙트는 ‘receive’ 함수를 통해 이를 수신합니다. 컨트랙트는 받은 이더를 이벤트로 기록하고, ‘getBalance’ 함수를 통해 컨트랙트의 잔액을 확인할 수 있습니다.

10. 데이터 페이로드란 ?

데이터 페이로드는 트랜잭션과 함께 전송되는 데이터입니다. 이 데이터는 스마트 계약의 특정 함수를 호출하거나, 컨트랙트에 특정 명령을 전달하는 데 사용됩니다. 페이로드는 트랜잭션의 일부분으로, 이더리움 가상 머신(EVM)이 해석하여 실행합니다.

데이터 페이로드 전달 과정

1. 함수 서명

  • 스마트 계약의 함수를 호출하기 위해서는 함수 서명이 필요합니다. 예를 들어, `transfer(address to, uint256 value)` 함수의 서명은 `transfer(address,uint256)`의 Keccak-256 해시의 첫 4바이트입니다.

2. 인코딩

  • 함수 서명 뒤에 함수 인자를 ABI(Application Binary Interface) 규격에 따라 인코딩하여 페이로드를 생성합니다.

3. 트랜잭션 생성 및 전송

  • 생성된 페이로드를 포함하여 트랜잭션을 생성한 후, 네트워크에 전송합니다.

스마트 계약에서 ‘transfer’ 함수를 호출하여 특정 주소로 토큰을 전송하는 예시를 살펴보겠습니다.

스마트 계약의 `transfer` 함수 :

function transfer(address to, uint256 value) public returns (bool);

다음은 `0x1234…` 주소로 `100` 토큰을 전송하는 트랜잭션의 페이로드를 생성하는 과정입니다.

  1. 함수 서명 계산
  • 함수 서명 : ‘transfer(address,uint256)’
  • Keccak-256 해시 : ‘a9059cbb000000000000000000000000’
  • 첫 4바이트 : ‘a9059cbb’

2. 인코딩

  • ‘to’ 주소 : ‘0x1234567890abcdef1234567890abcdef12345678’ (왼쪽 패딩 32바이트)
  • ‘value’ : ‘100’ (16진수: ‘64’, 왼쪽 패딩 32바이트)
  • 인코딩된 페이로드 : ‘a9059cbb0000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000000000000000000000000000000000000000000064’

3. 트랜잭션 생성

  • 생성된 페이로드를 트랜잭션의 ‘data’ 필드에 포함하여 트랜잭션을 생성합니다.
{
"from": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"to": "0x1234567890abcdef1234567890abcdef12345678",
"data": "0xa9059cbb0000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000000000000000000000000000000000000000000064",
"gas": 200000,
"gasPrice": "20000000000",
"value": 0
}

데이터 페이로드 전달의 중요성

데이터 페이로드 전달은 스마트 계약과의 상호작용을 가능하게 하여 다양한 디앱(DApp)과 서비스를 구현할 수 있게 합니다. 이를 통해 이더리움 네트워크에서 더 복잡하고 유연한 결제 시스템을 구축할 수 있습니다. 트랜잭션 페이로드의 정확한 작성과 해석은 스마트 계약의 안정성과 보안에 있어서도 매우 중요합니다.

11. 특별 트랜잭션: 컨트랙트 생성

컨트랙트 생성 트랜잭션은 새로운 스마트 컨트랙트를 블록체인에 배포 하는 특별한 형태의 트랜잭션 입니다. 이 트랜잭션은 일반적인 트랜잭션과 다르게 받는 주소(to address)가 없고, 대신 컨트랙트 코드가 포함됩니다.

컨트랙트 생성 과정

1. 스마트 컨트랙트 코드 작성 : 스마트 컨트랙트 코드는 주로 솔리디티(Solidity) 언어로 작성됩니다.

2. 컴파일 : 솔리디티 코드를 컴파일하여 바이트코드로 변환합니다. 이 바이트코드는 이더리움 가상 머신(EVM)에서 실행됩니다.

3.트랜잭션 작성 : 컨트랙트 생성 트랜잭션을 작성합니다. 이 트랜잭션은 바이트코드를 데이터 필드에 포함하고, 받는 주소(to address)는 비워둡니다.

4. 트랜잭션 서명 및 전송 : 생성된 트랜잭션을 서명하고, 네트워크에 전송합니다.

5. 컨트랙트 배포 : 트랜잭션이 블록체인에 포함되면, 컨트랙트가 배포되고, 컨트랙트 주소가 생성됩니다.

다음은 간단한 스마트 컨트랙트를 작성하고 배포하는 예시입니다.

  1. 스마트 컨트랙트 코드 작성 :
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
uint256 private data;

function set(uint256 _data) public {
data = _data;
}

function get() public view returns (uint256) {
return data;
}
}

2. 컴파일 :

위 코드를 솔리디티 컴파일러를 사용하여 컴파일합니다. 컴파일 결과로 바이트코드와 ABI(Application Binary Interface)가 생성됩니다.

3. 트랜잭션 작성 :

바이트코드를 포함하는 트랜잭션을 작성 합니다. 이 예시는 Web3.js 라이브러리를 사용하여 작성 되었습니다.

const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const account = '0xYourAccountAddress';
const privateKey = '0xYourPrivateKey';
const bytecode = '0xYourCompiledContractBytecode';

const tx = {
from: account,
gas: 2000000,
data: bytecode
};

web3.eth.accounts.signTransaction(tx, privateKey).then(signed => {
web3.eth.sendSignedTransaction(signed.rawTransaction)
.on('receipt', console.log);
});

4. 트랜잭션 서명 및 전송 :

위 코드에서 ‘web3.eth.accounts.signTransaction’ 메서드를 사용하여 트랜잭션을 서명하고, ‘web3.eth.sendSignedTransaction’ 메서드를 사용하여 전송합니다.

5. 컨트랙트 배포 확인 :

트랜잭션이 성공적으로 블록체인에 포함되면, 로그를 통해 생성된 컨트랙트 주소를 확인할 수 있습니다.

12. 디지털 서명이란 무엇인가 ?

디지털 서명은 전자 문서나 메시지의 진위와 무결성을 확인하기 위해 사용되는 암호화 기술입니다. 이는 공개 키 암호화 방식을 사용하여 특정 개인이 특정 트랜잭션을 승인했음을 증명합니다. 디지털 서명은 다음과 같은 두 가지 주요 기능을 합니다.

1. 무결성 보장 : 서명된 메시지나 트랜잭션이 변경되지 않았음을 보장합니다.
2. 인증 : 서명자가 누구인지 확인할 수 있습니다.

디지털 서명의 작동 원리

  1. 해싱 : 트랜잭션 데이터가 해시 함수에 입력되어 고정된 길이의 해시 값이 생성됩니다. 해시 함수는 SHA-256 과 같은 암호화 해시 함수를 사용합니다.
  2. 서명 생성 : 생성된 해시 값은 개인 키를 사용하여 암호화됩니다. 이 암호화된 해시 값이 디지털 서명이 됩니다.
  3. 서명 검증 : 서명 검증을 위해 서명된 해시 값은 서명자의 공개 키를 사용하여 복호화됩니다. 복호화된 해시 값이 원본 트랜잭션 데이터의 해시 값과 일치하면 서명이 유효하다고 판단 됩니다.

다음은 Alice가 Bob에게 1 ETH를 보내는 트랜잭션의 디지털 서명 과정입니다.

  1. 트랜잭션 생성 : Alice는 다음과 같은 트랜잭션을 생성 합니다.
{
"from": "Alice의 이더리움 주소",
"to": "Bob의 이더리움 주소",
"amount": 1,
"nonce": 0,
"gasPrice": 20000000000,
"gasLimit": 21000
}

2. 트랜잭션 해싱 : 이 트랜잭션 데이터를 해시 함수에 입력하여 해시 값을 생성 합니다.

해시 값 = SHA-256(트랜잭션 데이터)

const txHash = keccak256(JSON.stringify(tx));

3. 개인 키로 서명 : Alice는 자신의 개인 키를 사용하여 이 해시 값을 암호화하여 디지털 서명을 생성합니다.

디지털 서명 = 개인 키로 암호화(해시 값)

const privateKey = "0xAlice의 개인키";
const signedTx = sign(txHash, privateKey);

4. 트랜잭션 전송 : Alice는 트랜잭션 데이터와 디지털 서명을 이더리움 네트워크에 전송합니다.

sendSignedTransaction(signedTx);

5. 서명 검증 : 네트워크의 노드들은 Alice의 공개 키를 사용하여 서명을 검증합니다. 트랜잭션 데이터의 해시 값과 복호화된 해시 값이 일치하면, 트랜잭션이 유효하다고 판단되어 블록체인에 기록됩니다.

복호화된 해시 값 = 공개 키로 복호화(디지털 서명)

const { keccak256 } = require('js-sha3');
const { sign } = require('ethereumjs-util');
const Web3 = require('web3');

const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const tx = {
nonce: web3.utils.toHex(1),
gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
gasLimit: web3.utils.toHex(21000),
to: '0xBob의주소',
value: web3.utils.toHex(web3.utils.toWei('1', 'ether')),
data: '0x'
};

const txHash = keccak256(JSON.stringify(tx));
const privateKey = Buffer.from('Alice의개인키', 'hex');
const { v, r, s } = sign(txHash, privateKey);

const signedTx = {
...tx,
v: v,
r: r,
s: s
};

web3.eth.sendSignedTransaction(signedTx)
.on('receipt', console.log)
.on('error', console.error);

이 예제는 트랜잭션을 생성하고, 해시를 계산한 후 개인 키로 서명한 다음, 서명된 트랜잭션을 이더리움 네트워크에 전송하는 과정을 보여줍니다.

디지털 서명은 이더리움 트랜잭션의 신뢰성을 보장하는 중요한 기술입니다.

1. 보안 강화 : 트랜잭션이 전송되는 과정에서 변조되지 않도록 보호합니다.
2. 부인 방지 : 서명자는 트랜잭션을 승인한 후 이를 부인할 수 없습니다.
3. 신뢰성 제공 : 네트워크 참여자들이 서로를 신뢰할 수 있게 합니다.

13. 타원 곡선 암호학 (Elliptic Curve Cryptography, ECC)

타원 곡선 암호학(ECC)은 기존의 RSA 알고리즘보다 더 적은 키 길이로 높은 보안성을 제공하는 암호화 방식입니다. ECC는 타원 곡선 상의 수학적 연산을 사용하여 안전한 키 쌍을 생성합니다. ECC의 한 가지 중요한 구현인 ECDSA(Elliptic Curve Digital Signature Algorithm)를 통해 서명과 검증 과정을 살펴보겠습니다.

ECDSA 의 구성 요소

  1. 타원 곡선 : y² = x³ + ax + b 형태의 타원 곡선을 사용합니다.
  2. 기준점(G) : 타원 곡선 위의 한 점으로, 서명 및 검증 과정에서 중요한 역할을 합니다.
  3. 키 쌍 : 비밀 키와 공개 키로 구성 됩니다. 비밀 키는 개인이 비밀로 유지해야 하는 값이고, 공개 키는 네트워크에 공개되는 값입니다.

ECDSA 서명 생성 과정

  1. 메시지 해싱 : 서명하려는 메시지(M)를 해시 함수(H)를 통해 해시 값(z)으로 변환 합니다.
  2. 임시 키 생성 : 임시 비밀 키(k)를 랜덤하게 생성 합니다.
  3. 점 계산 : 타원 곡선의 기준점(G)을 k 와 곱하여 점(R)을 계산 합니다. 이때, R 의 x 좌표를 r 로 설정 합니다.
  4. 서명 값 계산 : 서명 값(s)을 다음과 같이 계산 합니다.
s = k − 1 × ( z + r × d ) mod n

여기서 d는 비밀 키입니다.

5. 서명 쌍 : (r, s) 서명 쌍을 생성합니다.

서명 생성 예시

1. 메시지(M): "Hello, Ethereum!"
2. 해시 값(z): H(M) = 0x123456789abcdef
3. 비밀 키(d): 0x1a2b3c4d5e6f7g8h9i
4. 임시 키(k): 0xf1e2d3c4b5a69788
5. 기준점(G)의 x좌표: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
6. R = kG = (0x1b2b3c4d5e6f7a8b, 0x9c8d7e6f5a4b3c2d)
7. r = 0x1b2b3c4d5e6f7a8b
8. s = k^{-1}(z + r * d) mod n = 0x7c6d5e4f3b2a1c9e

서명 쌍: (r, s) = (0x1b2b3c4d5e6f7a8b, 0x7c6d5e4f3b2a1c9e)

ECDSA 서명 검증 과정

  • 메시지 해싱 : 서명된 메시지(M)를 해시 함수(H)를 통해 해시 값(z)으로 변환합니다.
  • 유효성 검증 : 서명 값(r, s)가 유효한 범위에 있는지 확인합니다.
  • 검증 값 계산 : 검증 값(u1, u2)을 다음과 같이 계산 합니다.
u1 = z × s⁻¹ mod n
u2 = r × s⁻¹ mod n
  • 점 계산
R′ = u1 × G + u2 × Q

여기서 Q는 공개 키입니다.
  • 서명 검증 : R′의 x 좌표가 r과 일치하는지 확인 합니다. 일치하면 서명이 유효 합니다.

서명 검증 예시

1. 메시지(M): "Hello, Ethereum!"
2. 해시 값(z): H(M) = 0x123456789abcdef
3. 서명 값(r, s): (0x1b2b3c4d5e6f7a8b, 0x7c6d5e4f3b2a1c9e)
4. 공개 키(Q): 0x3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
5. u1 = z × s−1 mod n = 0x1c2d3e4f5a6b7c8d
6. u2 = r × s−1 mod n = 0x9e0f1a2b3c4d5e6f
7. R′ = u1G + u2Q = (0x1b2b3c4d5e6f7a8b,0x9c8d7e6f5a4b3c2d)
8. R′의 x좌표 = 0x1b2b3c4d5e6f7a8b

r 과 일치하므로 서명이 유효합니다.

14. 트랜잭션 서명이란 ?

트랜잭션 서명은 사용자가 자신의 개인 키를 사용하여 트랜잭션을 인증하는 과정입니다. 이는 트랜잭션이 원본임을 보증하며, 네트워크 참여자들이 트랜잭션의 유효성을 검증할 수 있도록 합니다.

트랜잭션 서명의 중요성

  1. 무결성 보장 : 서명된 트랜잭션은 중간에 변경되지 않았음을 보장합니다.
  2. 신원 확인 : 서명은 트랜잭션을 생성한 사용자의 신원을 확인하는 데 사용됩니다.
  3. 보안 강화 : 트랜잭션 서명은 부인 방지를 제공하며, 트랜잭션을 생성한 후 서명을 삭제할 수 없게 합니다.

서명 절차

1. 트랜잭션 생성

먼저, 트랜잭션의 주요 요소를 정의합니다. 예시로, Alice 가 Bob 에게 1 ETH를 전송하는 트랜잭션을 만들어보겠습니다.

{
"nonce": 1,
"gasPrice": "20000000000",
"gasLimit": "21000",
"to": "0xBob의주소",
"value": "1000000000000000000", // 1 ETH
"data": ""
}

2. 해싱 (Hashing)

트랜잭션의 해시값을 생성합니다. 이는 트랜잭션의 무결성을 보장하기 위해 사용됩니다.

const txHash = keccak256(JSON.stringify(tx));

3. 개인 키로 서명 (Signing)

개인 키를 사용하여 트랜잭션 해시에 서명합니다. 여기서 `sign` 함수는 트랜잭션 해시와 개인 키를 인자로 받아 서명된 트랜잭션을 반환합니다.

const privateKey = "0xAlice의개인키";
const signedTx = sign(txHash, privateKey);

4. 서명된 트랜잭션 보내기

서명된 트랜잭션을 네트워크에 전송합니다. 이를 통해 트랜잭션이 블록체인에 기록됩니다.

sendSignedTransaction(signedTx);

트랜잭션 서명 절차의 예시입니다.

const { keccak256 } = require('js-sha3');
const { sign } = require('ethereumjs-util');
const Web3 = require('web3');

const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const tx = {
nonce: web3.utils.toHex(1),
gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
gasLimit: web3.utils.toHex(21000),
to: '0xBob의주소',
value: web3.utils.toHex(web3.utils.toWei('1', 'ether')),
data: '0x'
};

const txHash = keccak256(JSON.stringify(tx));
const privateKey = Buffer.from('Alice의개인키', 'hex');
const { v, r, s } = sign(txHash, privateKey);

const signedTx = {
...tx,
v: v,
r: r,
s: s
};

web3.eth.sendSignedTransaction(signedTx)
.on('receipt', console.log)
.on('error', console.error);

이 예제는 트랜잭션을 생성하고, 해시를 계산한 후 개인 키로 서명한 다음, 서명된 트랜잭션을 이더리움 네트워크에 전송하는 과정을 보여줍니다.

15. 서명 접두어 값(v)란 무엇인가 ?

이더리움 트랜잭션 서명에서 ‘v’, ‘r’, ‘s’는 서명의 세 가지 주요 구성 요소 입니다. ‘v’ 값은 서명 검증 및 트랜잭션의 체인 ID를 포함하여 서명에 대한 메타 정보를 제공 합니다. EIP-155 가 도입되면서 ‘v’값은 체인 ID를 포함하도록 변경되었습니다. 이는 재생 공격을 방지하는 데 중요한 역할을 합니다.

서명 접두어 값(v)의 계산

v = chainId × 2 + 35 + recoveryId

여기서 ‘recoveryId’ 는 서명에서 계산된 ‘v’값의 최하위 비트로, 서명한 후 얻은 값에서 27 또는 28 을 뺀 값입니다.

공개키 복구

트랜잭션을 서명할 때, 개인 키로 서명된 트랜잭션에서 공개키를 복구할 수 있습니다. 이를 통해 트랜잭션이 실제로 특정 주소의 소유자가 서명 했는지 검증 할 수 있습니다.

예시 코드

아래는 JavaScript 와 ‘ethereumjs-util’ 라이브러리를 사용하여 트랜잭션 서명에서 ‘v’ 값을 계산하고, 공개키를 복구하는 예제 입니다.

const EthereumTx = require('ethereumjs-tx').Transaction;
const { bufferToHex, ecrecover, pubToAddress, toBuffer } = require('ethereumjs-util');
const Common = require('ethereumjs-common').default;
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID');

const privateKey = Buffer.from('YOUR_PRIVATE_KEY', 'hex');
const fromAddress = 'YOUR_FROM_ADDRESS';
const toAddress = 'RECEIVER_ADDRESS';

async function signTransaction() {
const nonce = await web3.eth.getTransactionCount(fromAddress);
const gasPrice = await web3.eth.getGasPrice();

const txParams = {
nonce: web3.utils.toHex(nonce),
gasPrice: web3.utils.toHex(gasPrice),
gasLimit: web3.utils.toHex(21000), // 기본값으로 설정
to: toAddress,
value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')),
chainId: 1 // 메인넷
};

const customCommon = Common.forCustomChain(
'mainnet',
{
name: 'mainnet',
chainId: 1,
networkId: 1,
},
'petersburg'
);

const tx = new EthereumTx(txParams, { common: customCommon });
tx.sign(privateKey);

const serializedTx = tx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');

console.log(`Signed transaction: ${rawTx}`);
return tx;
}

async function recoverPublicKey(tx) {
const msgHash = tx.hash(false);
const { v, r, s } = tx;

const chainId = tx.common.chainId();
const recoveryId = v - 35 - chainId * 2;

const publicKey = ecrecover(msgHash, recoveryId, r, s);
const sender = bufferToHex(pubToAddress(publicKey));

console.log(`Recovered public key address: ${sender}`);
}

(async () => {
const tx = await signTransaction();
await recoverPublicKey(tx);
})();

16. 트랜잭션 전파

서명된 트랜잭션은 네트워크의 노드 중 하나에 제출됩니다. 이 노드는 트랜잭션을 검증한 후, 자신과 연결된 다른 노드들에게 전파 합니다. 이 과정이 반복되면서 트랜잭션은 네트워크 전체로 확산됩니다.

트랜잭션 전파

  1. 트랜잭션 제출 : 사용자가 트랜잭션을 노드 A에 제출 합니다.
  2. 검증 : 노드 A는 트랜잭션의 유효성을 검증 합니다.
  3. 전파 : 노드 A는 자신과 연결된 노드 B, C, D 에게 트랜잭션을 전파 합니다.
  4. 반복 : 노드 B, C, D는 트랜잭션을 검증한 후, 자신과 연결된 다른 노드들에게 전파합니다. 이 과정은 트랜잭션이 네트워크 전체에 전파될 때까지 계속됩니다.

트랜잭션 확인

  1. 트랜잭션 전파 완료 : 트랜잭션이 네트워크 전체에 전파됩니다.
  2. 마이닝 : 마이너들은 트랜잭션을 블록에 포함시키기 위해 경쟁합니다.
  3. 블록 추가 : 트랜잭션이 포함된 블록이 체인에 추가되면 트랜잭션은 확인된 것으로 간주됩니다. 일반적으로 트랜잭션이 여러 블록에 의해 확인될 때까지 기다리는 것이 좋습니다(예: 6번의 확인).

예시 : Alice가 Bob에게 1 ETH 전송

1. 트랜잭션 생성 : Alice는 1 ETH를 Bob에게 전송하는 트랜잭션을 생성하고 서명하여 노드 A에 제출합니다.
2. 트랜잭션 전파 : 노드 A는 트랜잭션의 유효성을 검증한 후, 노드 B, C, D에게 전파합니다.
이 과정은 네트워크 전체에 전파될 때까지 반복됩니다.
3. 마이닝 및 블록 추가 : 마이너들이 트랜잭션을 블록에 포함시키기 위해 경쟁하고,
최종적으로 트랜잭션이 포함된 블록이 체인에 추가됩니다.
4. 트랜잭션 확인: 블록이 체인에 추가되면 트랜잭션은 확인됩니다.

트랜잭션이 블록체인에 기록되는 과정

  1. 트랜잭션 생성 및 브로드캐스트: 사용자가 트랜잭션을 생성하고 네트워크에 전송합니다.
  2. 검증: 노드가 트랜잭션을 검증하여 유효성을 확인합니다.
  3. 메모리 풀 저장: 검증된 트랜잭션은 메모리 풀에 저장됩니다.
  4. 블록 생성: 채굴자가 메모리 풀에서 트랜잭션을 선택하여 블록에 포함시킵니다.
  5. 블록체인 추가: 채굴된 블록이 블록체인에 추가되고 네트워크 전체에 전파됩니다.

트랜잭션 상태 확인

트랜잭션이 블록체인에 기록되었는지 확인하려면 트랜잭션 해시(Transaction Hash)를 사용하여 이더스캔(Etherscan)과 같은 블록 탐색기(Block Explorer)에서 조회할 수 있습니다. 이 예시는 트랜잭션이 성공적으로 블록체인에 기록되었는지 여부를 확인하는 방법을 보여줍니다.

import requests

tx_hash = "0xTransactionHashHere"
etherscan_api = "https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash={tx_hash}&apikey=YourApiKey"

response = requests.get(etherscan_api)
print(response.json())

17. 다중 서명 트랜잭션이란 ?

다중 서명 트랜잭션(멀티시그 트랜잭션)은 여러 명의 사용자(서명자)가 트랜잭션을 승인해야만 이행되는 트랜잭션 입니다. 이더리움에서 다중 서명은 스마트 계약을 통해 구현되며, 이를 통해 자금의 보다 안전한 관리와 승인을 보장 합니다.

다중 서명 트랜잭션의 필요성

  1. 보안 강화 : 여러 명이 서명을 해야 자금이 이동하므로, 개인 키의 분실이나 도난으로 인한 자금 손실을 예방할 수 있습니다.
  2. 권한 분산 : 한 명의 사용자에게 모든 권한이 집중되지 않도록 하여, 공동 관리와 투명성을 확보할 수 있습니다.
  3. 업무 처리 : 회사나 단체에서 여러 명의 승인이 필요한 결제를 처리하는 데 유용합니다.

다중 서명 트랜잭션의 작동 원리

  1. 다중 서명 지갑 생성 : 여러 명의 서명자가 함께 소유하는 지갑을 생성 합니다.
  2. 트랜잭션 생성 : 서명자 중 한 명이 트랜잭션을 생성하고, 나머지 서명자들이 이를 검토 합니다.
  3. 서명 수집 : 지정된 수의 서명자가 트랜잭션을 승인하고 서명 합니다.
  4. 트랜잭션 실행 : 충분한 서명이 수집되면 트랜잭션이 이더리움 네트워크에 전송되어 실행 됩니다.

스마트 계약을 통한 다중 서명 구현

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MultiSigWallet {
address[] public owners;
mapping(address => bool) public isOwner;
uint public requiredSignatures;

struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint numConfirmations;
}

mapping(uint => mapping(address => bool)) public isConfirmed;
Transaction[] public transactions;

event Deposit(address indexed sender, uint amount, uint balance);
event SubmitTransaction(address indexed owner, uint indexed txIndex, address indexed to, uint value, bytes data);
event ConfirmTransaction(address indexed owner, uint indexed txIndex);
event ExecuteTransaction(address indexed owner, uint indexed txIndex);
event RevokeConfirmation(address indexed owner, uint indexed txIndex);

modifier onlyOwner() {
require(isOwner[msg.sender], "Not owner");
_;
}

modifier txExists(uint _txIndex) {
require(_txIndex < transactions.length, "tx does not exist");
_;
}

modifier notExecuted(uint _txIndex) {
require(!transactions[_txIndex].executed, "tx already executed");
_;
}

modifier notConfirmed(uint _txIndex) {
require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
_;
}

constructor(address[] memory _owners, uint _requiredSignatures) {
require(_owners.length > 0, "owners required");
require(_requiredSignatures > 0 && _requiredSignatures <= _owners.length, "invalid number of required signatures");

for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "invalid owner");
require(!isOwner[owner], "owner not unique");

isOwner[owner] = true;
owners.push(owner);
}

requiredSignatures = _requiredSignatures;
}

receive() external payable {
emit Deposit(msg.sender, msg.value, address(this).balance);
}

function submitTransaction(address _to, uint _value, bytes memory _data) public onlyOwner {
uint txIndex = transactions.length;

transactions.push(Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
numConfirmations: 0
}));

emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
}

function confirmTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) {
Transaction storage transaction = transactions[_txIndex];
transaction.numConfirmations += 1;
isConfirmed[_txIndex][msg.sender] = true;

emit ConfirmTransaction(msg.sender, _txIndex);
}

function executeTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
Transaction storage transaction = transactions[_txIndex];
require(transaction.numConfirmations >= requiredSignatures, "cannot execute tx");

transaction.executed = true;

(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "tx failed");

emit ExecuteTransaction(msg.sender, _txIndex);
}

function revokeConfirmation(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");

Transaction storage transaction = transactions[_txIndex];
transaction.numConfirmations -= 1;
isConfirmed[_txIndex][msg.sender] = false;

emit RevokeConfirmation(msg.sender, _txIndex);
}
}

위 코드에서는 기본적인 다중 서명 지갑의 기능을 구현 합니다. 여러 소유자가 트랜잭션을 제출하고, 서명을 통해 승인하며, 충분한 서명이 모이면 트랜잭션이 실행 됩니다.

[5 장. 지갑 - 이더리움을 활용한 결제 시스템](https://birdgang82.medium.com/5-%EC%9E%A5-%EC%A7%80%EA%B0%91-%EC%9D%B4%EB%8D%94%EB%A6%AC%EC%9B%80%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B2%B0%EC%A0%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-0b0c84d96013)

--

--