How Ethereum Transaction Works

이더리움 트랜잭션 작동원리


  1. EOA(사용자 계정)가 트랜잭션을 발생(발생한 트랜잭션에는 nonce/to/value/gasLimit/gasPrice/data 등이 들어감)
  2. 발생한 트랜잭션을 사용자가 가지고 있는 지갑의 pk로 전자서명(Sign)을 함(전자 서명 암호화 방식)
  3. Sign된 트랜잭션을 이더리움 네트워크의 노드들에게 보냄(트랜잭션을 브로드캐스트)
  4. 노드(채굴자)들은 해당 트랜잭션의 유효성 검증을 함
    1. 해당 트랜잭션이 문법에 맞게 구성되었는지
    2. 사용자의 공개 키를 사용하여 해당 전자 서명이 유효한지
    3. 사용자 어카운트에 있는 Nonce와 맞는지
  5. 해당 트랜잭션이 유효하면, 해당 트랜잭션은 트랜잭션 풀(Tx pool)에 보관됨
  6. 채굴자들은 수수료가 높은 순서대로 트랜잭션을 처리함
  7. 여러 트랜잭션이 블록에 포함되고 블록이 생성되면, 모든 노드들에게 신규 블록이 전달됨(신규 블록 브로드캐스트)


Ethereum API를 활용한 트랜잭션 만드는 방법


Ethereum API를 활용해서 다음과 같은 기능을 만들어 보자.

  • 이더리움 지갑 및 key 생성
  • balance 조회
  • transfer
  • 토큰 정보 가져오기

위 기능들을 React에서 만들어 보자.


1. 이더리움 지갑 및 key 생성


이더리움의 지갑을 만들기 위해서는 이더리움에서 제공하는 ‘ethereumjs-wallet’ 라이브러리를 사용해야 한다.

‘ethereumjs-wallet’ 설치

1
npm install ethereumjs-wallet

리액트에서 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Wallet from 'ethereumjs-wallet';

class MultiWallets extends Component {

constructor(props) {
super(props);
}

generateWallet = () => {
let wallet = Wallet.generate();
let address = '0x' + wallet.getAddress().toString('hex');
let privKey = wallet.getPrivateKey().toString('hex');
let pubKey = wallet.getPublicKey().toString('hex');
}

render() {
return (
<div className="MultiWallets">
<button onClick={() => this.generateWallet()}>Generate a Wallet</button>
</div>
)
}

}

위 코드는 button을 클릭하면, wallet과 wallet의 address, private key, 그리고 public key를 생성할 수 기능이다.

  • generate() 함수를 사용하여, wallet을 생성한다. 생성한 wallet의 getAddress() 함수를 이용하여 주소값을 만드는데, 이더리움은 ‘0x’를 포함한 hex string으로 값을 만든다. 그래서, toString(‘hex’)와 앞에 ‘0x’를 붙여 address 값을 만든다.
  • 생성한 wallet의 getPrivateKey() 함수와 getPublicKey() 함수를 이용해서 private key와 public key를 생성한다.


2. 지갑의 balance 조회


이더리움 지갑의 balance를 조회하기 위해서는 이더리움에서 제공하는 web3 라이브러리를 사용해야 한다.

web3 설치

1
npm install web3

리액트에서 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Wallet from 'ethereumjs-wallet';
import Web3 from 'web3';

class MultiWallets extends Component {

constructor(props) {
super(props);
}

componentWillMount() {
window.web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/API_KEY'))
}

getBalance = () => {
// 여기서 account는 위에서 만든 address이다.
const { account } = this.state;
window.web3.eth.getBalance(account).then(console.log)
window.web3.eth.getBalance('0xBF345977799290F574dB970366CF1712AdCd0632').then(console.log)
}

render() {
return (
<div className="MultiWallets">
<button onClick={() => this.getBalance()}>Get Balance</button>
</div>
)
}

}

위 코드는 버튼을 클릭하면, balance를 조회하는 기능이다.

  • web3를 사용하기 위해서는 우선, new 연산자를 이용해 web3 인스턴스를 생성하고, HttpProvider를 이용해서 이더리움 네트워크와 연결해야 한다.(위 코드는 이더리움의 테스트넷인 Ropsten의 infura API를 사용하여 infura에서 얻은 API_KEY로 노드를 연결했다.)
  • 만약, Private 블록체인으로 연결하고 싶다면, localhost:8545로 연결하면 된다.
  • web3.eth의 getBalance() 함수를 이용해 내가 가진 지갑주소의 balance를 확인할 수 있다.

*infura

  • infura는 개발자가 API KEY만으로도 이더리움의 메인넷 또는 테스트넷에 접근을 가능하게 한다.
  • infura를 로그인하면 API KEY와 ENDPOINT를 제공해준다.
  • web3의 HttpProvider를 사용해서 이더리움 네트워크에 연결할 때, infura에서 제공하는 ENDPOINT를 사용하면 된다.


3. transfer


transfer(송금) 기능을 만들기 위해서는, transaction을 생성하고, 서명하여 전송하면 된다.

리액트에서 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import Wallet from 'ethereumjs-wallet';
import Web3 from 'web3';

class MultiWallets extends Component {

constructor(props) {
super(props);
}

componentWillMount() {
window.web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/API_KEY'))
}

sendTransaction = () => {
const { account } = this.state;
const sendAccount = '0xBF345977799290F574dB970366CF1712AdCd0632';

// 1. check the number of nonce
window.web3.eth.getTransactionCount(sendAccount, (err, txCount) => {

// 2. create transaction
const txObject = {
nonce: window.web3.utils.toHex(txCount),
to: account,
gasLimit: window.web3.utils.toHex(1000000),
gasPrice: window.web3.utils.toHex(window.web3.utils.toWei('10', 'gwei')),
value: window.web3.utils.toHex(window.web3.utils.toWei('0.01', 'ether'))
};

const tx = new Tx(txObject)

// 3. Sign transaction
// a private key of the sendAccount
const privateKey = 'BE43625EA38CF1FA2D09D057F2E0AB36899E28338E2BCB698CFA6F066EDDF04C';
const _privateKey = Buffer.from(privateKey, 'hex');
tx.sign(_privateKey)

// 4. Send transaction
const serializedTx = '0x' + tx.serialize().toString('hex');
window.web3.eth.sendSignedTransaction(serializedTx, function(err, txId) {
console.log('serializedTx:', serializedTx)
if(!err) {
console.log('txId:', txId)
} else {
console.log('err:', err)
}
})

})
}

render() {
return (
<div className="MultiWallets">
<button onClick={() => this.sendTransaction()}>Send Transaction</button>
</div>
)
}

}

위 코드는 버튼을 클릭하면, transaction을 발생시키는 기능이다.

transaction을 발생시키기 위한 단계는 다음과 같다.

  1. nonce 값 구하기
  2. transaction 생성
  3. transaction 서명
  4. transaction 보내기


1. nonce 값 구하기


1
2
3
window.web3.eth.getTransactionCount(sendAccount, (err, txCount) => {

}

getTransactionCount() 함수를 사용하여 nonce값을 구하는데, getTransactionCount() 함수 안에 인자 txCount가 nonce 값이다. nonce값이란, 해당 transaction을 발생시키는 account가 발생시킨 총 transaction의 수를 말한다.


2. transaction 생성


1
2
3
4
5
6
7
8
9
10
11
12
13
import Tx from 'ethereumjs-tx';

// 1. transaction 생성에 필요한 txObject 생성
const txObject = {
nonce: window.web3.utils.toHex(txCount),
to: account,
gasLimit: window.web3.utils.toHex(1000000),
gasPrice: window.web3.utils.toHex(window.web3.utils.toWei('10', 'gwei')),
value: window.web3.utils.toHex(window.web3.utils.toWei('0.01', 'ether'))
};

// 2. 'ethereumjs-tx' 라이브러리를 이용해 tx 인스턴스 생성
const tx = new Tx(txObject)

transaction을 생성하기 위해 필요한 txObject를 만들어야 하는데, 이 txObject에는 위와 같은 값들이 들어간다. web3의 utils에 있는 toHex() 함수를 이용해 값들을 hex 값으로 만들었는데, 위 코드의 값들은 예시로 작성한 것이다.

참고로, toWei() 함수는 인자로 들어간 값을 Wei 단위로 변환한다는 뜻이다. 예를 들어, window.web3.utils.toWei(‘10’, ‘gwei’)이면, 10 gwei 값을 wei 값으로 변환하라는 뜻이다.

위에서 만든 txObject를 가지고 transaction을 생성하기 위해서는 ethereumjs-tx 라이브러리를 사용해 tx를 생성한다.


3. transaction 서명


이더리움에서 transaction을 서명(=== 전자서명)할 때는, 수신자의 private key를 가지고 transaction을 서명한다.

1
2
3
4
// a private key of the sendAccount
const privateKey = 'PK';
const _privateKey = Buffer.from(privateKey, 'hex');
tx.sign(_privateKey)

private key로 서명하기 위해서는 private key 값을 buffer 타입으로 변경하여 사용해야 한다.

buffer 타입이란, Unit8Array 타입으로, 값들이 배열(‘[]’)에 들어가 있다. 처음에 generate() 함수로 wallet을 생성했을 때, 그 wallet 안에 들어있는 private key 값의 타입은 Unit8Array 타입으로 되어있다. 이 private key 값을 위에서 hex 값으로 변경해서 사용했었는데, 이 값을 다시 buffer 타입으로 변경해서 Sign 할 때 사용해야 한다.


4. transaction 보내기


서명된 transaction을 serialize하여 sendSignedTransaction() 함수를 이용해 transaction을 보낸다.

1
2
3
4
5
6
7
8
9
10
11
12
// 1. serialize transaction
const serializedTx = '0x' + tx.serialize().toString('hex');

// 2. send signed transaction
window.web3.eth.sendSignedTransaction(serializedTx, function(err, txId) {
console.log('serializedTx:', serializedTx)
if(!err) {
console.log('txId:', txId)
} else {
console.log('err:', err)
}
})

Serialization이란, 네트워크를 통해 데이터를 주고 받을 때, 서로간에 공유하는 규칙이 있는데, 이 규칙에 맞게 데이터를 출력하는 것을 말한다.

sendSignedTransaction() 함수를 사용해 얻은 txId 값은, 이 transaction의 txHash값이 되고, 즉 transaction id가 된다. 이 txHash값으로 Ether Scan에서 이 transaction에 대한 거래내역을 확인할 수 있다.

참고: Serialization


4. 토큰 정보 가져오기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Web3 from 'web3';
import { erc20Abi } from '../constant/abi';
import { ethToken } from '../constant/ethToken'

class MultiWallets extends Component {

constructor(props) {
super(props);
}

addToken = () => {
// Omisego token
const tokenAddress = '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07';
let token = window.web3.eth.Contract(erc20Abi, tokenAddress)

// get token info
const tokenInfo = ethToken.filter((token) => {
return token.address.toLowerCase() === tokenAddress.toLowerCase()
});
console.log('tokenInfo:', tokenInfo)
console.log('symbol:', tokenInfo[0].symbol)
console.log('decimal:', tokenInfo[0].decimal)
}


render() {
return (
<div className="MultiWallets">
<button onClick={() => this.addToken()}>Add Token</button>
</div>
)
}


}

위 코드는 addToken 버튼을 클릭하면, 해당 토큰의 정보를 가져올 수 있는 기능이다.

각 토큰은 토큰의 address를 가지고 있다. web3의 Contract() 함수를 사용하면 토큰의 정보를 가져올 수 있다.

1
new web3.eth.Contract(jsonInterface, address, options)

Contract() 함수안에 jsonInterface는 abi 파일로써, contract가 인스턴스화하기에 필요한 json interface 파일이다.(abi 파일)

위 코드의 ethToken는 이더리움의 모든 토큰에 대한 정보를 모아놓은 파일이다. 즉, 선택한 토큰이 이 파일안에 있는 모든 이더리움 토큰과 비교해 해당 토큰을 찾아 그 토큰에 대한 정보를 가져오는 방식으로 코드를 작성했다.