블록체인 기술을 공부하는 가장 기본적인 목표인 Transaction 발생을 해보도록 하겠다.
이 Transaction 발생은 어떤 블록체인이건 개념이해 후 가장 첫 걸음을 떼는 단계이므로 잘 따라해보도록 하자.
Transaction 을 발생하는것을 포함하여 Ethereum 과 Interaction 을 할 수 있는 방법에 대한 설명과 실제 간단한 Transaction 을 JSON-RPC 를 통해 발생시키는 방법을 알아보겠다.
이후 모든 Ethereum 과 관련한 내용은 Geth 를 기준으로 하겠다.
Eth 만 있는 기능인 경우 Eth 를 언급하겠으나 별도 언급이 없으면 모두 Geth 환경이다.
▌Ethereum 에서 개발하는 방법
이더리움을 블록체인 플랫폼의 코어 엔진으로 생각했을 때, 이 코어 엔진을 활용하여 응용을 개발할 수 있는 방법에는 아래 세가지가 있다.
- HTTP 기반 JSON RPC API 사용
REST API Test 프로그램이나 Curl 등으로 즉시 테스트해볼 수 있으며, 원격 응용 프로그램과 통신하기에 적합한 인터페이스. - JavaScript 기반의 web3.js API 를 사용
Ethereum 이 Dapp 개발을 위해 기본으로 제공하는 JavaScript API 로, Browser, Node.JS, Mist 등으로 실행할 수 있는 인터페이스 이다. 내부적으로는 JSON RPC 를 사용한다. - 응용에 엔진을 라이브러리 형태로 활용하는 방법
소스코드 형태로 Ethereum 을 사용하며, 가장 응용과 Tight 하게 동작할 수 있다. 그러나 그래야 할 확실한 이유가 있지않는 한 추천하고싶지 않은 방법이다.
위 세가지 방법 중 1,2 번은 비교적 쉬운 방법이나, 3번의 경우 Ethereum 의 코드 분석 없이는 활용이 거의 불가능하다고 봐야하며, 이 코드를 분석하는 데만도 수개월 이상의 노력이 필요하다.
나도 C++ Ethereum 을 3번 형태로 응용하는것을 목표로 한적이 있으나, 200,000 라인에 육박하는 코드에, 난무하는 Template Class 와 상호 Dependency 가 높고 boost 로 발라놓은 C++ 11 스펙의 코드를 해석하기에는 시간이 매우 부족하였다.
따라서, 1,2번 방법이 Ethereum 을 "활용"하기 가장 적합한 방법이라고 생각되며, Transaction 발생과 같은 매우 간단한 API 사용부터 시작하는게 응용을 위한 이해의 첫걸음이라 하겠다.
▌JSON RPC 인터페이스 기본 이해
ETH 와 GETH 모두 HTTP 기반의 JSON RPC 2.0 스펙을 지원한다. JSON RPC API 는 여기 에서 확인할 수 있으며 사용법은 매우 간단한 편이다.
Listen Port : JSON RPC 의 기본 Listen Port 는 8545 번이다. (Geth, Eth 공통)
실행 옵션 :
--rpc : rpc 를 사용함
--rpcaddr : RPC 를 Listen 하는 IP Address Bining (기본: 127.0.0.1)
--rpcport : RPC Listen Port (기본:8545)
--rpcapi : RPC 를 통해 사용 가능한 API 셋 (기본: db, eth, net, web3) (이외에 admin, debug, miner, shh, txpool, personal 을 , 로 구분하여 추가 가능)
--rpccorsdomain : Browser 에서 web3.js 사용 시 Cross Domain 문제가 생기지 않도록 Accept 할 Document 의 Origin Domain.
테스트 환경을 위해 두개의 Node Instance 를 아래와 같이 구성하였다
[Node 1]
- Miner 동작
- RPC Listen Address Binding : ALL
- RPC Listen Port : 8541
- RPC API : admin, db, eth, debug, miner, net, shh, txpool, personal, web3
- Geth Listen Port : 3031
- JavaScript Console 켜기
일단, 아래 커맨드로 Account 를 하나 생성한다
$ geth --datadir $ETH_HOME/db/p1_net_node1 account new
그리고 나서 Miner 인 Node 1을 실행시킨다
$ geth --datadir $ETH_HOME/db/p1_net_node1 --genesis $ETH_HOME/res/p1_net/genesis.json --rpc --rpcaddr "0.0.0.0" --rpcport "8541" --rpcapi "admin,db,eth,debug,miner,net,shh,txpool,personal,web3" --port 3031 --nodiscover --mine console
[Node 2]
- Miner 동작 안함
- RPC Listen Address Binding : ALL
- RPC Listen Port : 8542
- RPC API : Node 1 과 동일
- Geth Listen Port : 3032
- JavaScript Console 켜기
Node 2 용 Account 도 하나 생성한다
$ geth --datadir $ETH_HOME/db/p1_net_node2 account new
$ geth --datadir $ETH_HOME/db/p1_net_node2 --genesis $ETH_HOME/res/p1_net/genesis.json --rpc --rpcaddr "0.0.0.0" --rpcport "8542" --rpcapi "admin,db,eth,debug,miner,net,shh,txpool,personal,web3" --port 3032 --nodiscover $@
[genesis.json]
{
"alloc": {
"d1c8b6e81af8ef16a7dbcd410234f12e10f1579d": {
"balance": "5000000000000000000000000000"
},
"2c6191a4792a3a33cbd2f8b7b7e583a61fb33b23": {
"balance": "5000000000000000000000000000"
}
},
"nonce": "0x0000000000000000",
"difficulty": "0x010000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x1000000000"
}
"alloc": {
"d1c8b6e81af8ef16a7dbcd410234f12e10f1579d": {
"balance": "5000000000000000000000000000"
},
"2c6191a4792a3a33cbd2f8b7b7e583a61fb33b23": {
"balance": "5000000000000000000000000000"
}
},
"nonce": "0x0000000000000000",
"difficulty": "0x010000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x1000000000"
}
위 alloc 의 Account Address 는 각 Node 1, 2 를 위해 생성한 Account 들이다
[static-nodes.json]
위 CLI 옵션에서 보이듯 --nodiscover 옵션을 주었으므로 자동으로 Peer 를 찾지 않을것이다. Node 2 의 <datadir>/static-nodes.json 파일에 static node 로 Node 1 을 추가해준다.
[
"enode://258a9b4558c163fdaa48f4f7111b8479d87360bc61c77680269b34204dadc293fccdf2c915acb446953712c12ab8a040d2f7b79ef1e066da60b65a88a7bb382d@[::]:3031"
]
"enode://258a9b4558c163fdaa48f4f7111b8479d87360bc61c77680269b34204dadc293fccdf2c915acb446953712c12ab8a040d2f7b79ef1e066da60b65a88a7bb382d@[::]:3031"
]
두 Peer 를 실행시키면 Node 1 은 DAG 파일을 생성한 후 Mining 을 시작하고, Node 2 를 띄우면 Node 1 에 접속하여 Block 을 sync 하기 시작한다.
이제 테스트할 환경이 만들어졌다.
▌JSON RPC 테스트환경 만들기
JSON RPC API 를 테스트하기 전에 curl 대신 좀더 쉽게 테스트할 수 있는 환경을 만들어보자.
Chrome 을 사용하고있다면, Post Man 이라는 REST 테스트 도구를 사용할 수 있다. Plugin 을 설치하고 실행하면 아래와 같은 화면이 보인다.
JSON RPC 로 테스트 하여 성공한다면, 이후 어떠한 언어로든 Ethereum Blockchain 을 사용하는 응용을 만들 수 있게 되는것이다.
위 화면에서, Collection 을 하나 만들어두자
이름은 Geth 로 해 두고, 이제 화면 우측 상단의 "Manage Environment" 를 클릭한다.
Add 버튼을 눌러 Environment 이름을 적당히 준다.
key 에 "url" 이라고 입력하고, value 에 Geth 의 JSON RPC Listen IP 주소와 Port 를 입력한다
나는 node 2 (Miner 아닌 Node) 를 대상으로 해보겠다.
이렇게 등록한 Environment Variable 은 향후에 {{url}} 과 같이 사용할 수 있다.
▌Client Version 가져오기
위 모든 과정들을 마쳤다면 이제 JSON RPC 테스트 환경이 끝난것이다. 아주 간단한 Client Version 을 요청하는 API 부터 사용해본다.
Client 의 Version 을 가져오는 API 이름은 web3_clientVersion 이다.
Spec 상, Parameter 는 없고, Return 은 현재 Client 의 버전을 담은 정보가 들어오게 되어있다.
위 처럼 새로운 요청을 만들고, Request Type 은 "raw" 로 하고, 데이터 포맷은 "JSON (application/json)" 으로 한다.
Send 를 누르면, 아래와 같은 응답이 온다.
{
"id": 100,
"jsonrpc": "2.0",
"result": "Geth/v1.3.5-cab802b4/linux/go1.6"
}
"id": 100,
"jsonrpc": "2.0",
"result": "Geth/v1.3.5-cab802b4/linux/go1.6"
}
성공이다. 이제 원하는 API 를 무엇이든 응용하여 사용할 수 있다.
▌JSON RPC 를 통한 Block 정보 가져오기
하나만 더 해보자. Node 1 에서 Miner 가 돌고 있으므로, Block 이 계속 생성되고 있을 것이다. Difficulty 가 매우 낮으므로 빠른 속도로 생성되고 있을 것이다.
Node 2 가 Import 한 최신 Block 의 정보를 가져와보도록 하겠다.
[Request]
{
"jsonrpc":"2.0",
"method":"eth_getBlockByNumber",
"params":["latest",true],
"id":100
}
"jsonrpc":"2.0",
"method":"eth_getBlockByNumber",
"params":["latest",true],
"id":100
}
params 의 첫번째 파라미터는 "최신 블록"을 가져오란 이야기 이고,
params 의 두번째 파라미터 true 는, Transaction 데이터 전체를 가져오겠다는 이야기이다. (그러나 Transaction 을 발생시키지 않았으므로, 이 배열값은 비어있다)
[Response]
{
"id": 100,
"jsonrpc": "2.0",
"result": {
"number": "0x1ff",
"hash": "0x1c9dd4d3191be60a30a01cd8423f05ee203617a03c60d330f76805be2d8df0de",
"parentHash": "0x267128da87431da30d30bf280efa1ef41f5c5e7a2dee846f3d2c94b89947adfd",
"nonce": "0x7652646a68725a33",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0x9ad2a3c192a39ec9653915f6935c3502fd46d9e6e2805309d80408ea3227e1b4",
"receiptRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"miner": "0xd1c8b6e81af8ef16a7dbcd410234f12e10f1579d",
"difficulty": "0x27be0",
"totalDifficulty": "0x4788d6e",
"size": "0x219",
"extraData": "0xd583010305844765746885676f312e36856c696e7578",
"gasLimit": "0x9b62bb0db",
"gasUsed": "0x0",
"timestamp": "0x56ed6d1b",
"transactions": [],
"uncles": []
}
}
"id": 100,
"jsonrpc": "2.0",
"result": {
"number": "0x1ff",
"hash": "0x1c9dd4d3191be60a30a01cd8423f05ee203617a03c60d330f76805be2d8df0de",
"parentHash": "0x267128da87431da30d30bf280efa1ef41f5c5e7a2dee846f3d2c94b89947adfd",
"nonce": "0x7652646a68725a33",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0x9ad2a3c192a39ec9653915f6935c3502fd46d9e6e2805309d80408ea3227e1b4",
"receiptRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"miner": "0xd1c8b6e81af8ef16a7dbcd410234f12e10f1579d",
"difficulty": "0x27be0",
"totalDifficulty": "0x4788d6e",
"size": "0x219",
"extraData": "0xd583010305844765746885676f312e36856c696e7578",
"gasLimit": "0x9b62bb0db",
"gasUsed": "0x0",
"timestamp": "0x56ed6d1b",
"transactions": [],
"uncles": []
}
}
Block Number 는 0x1ff (511) 이다.
sha3Uncles, logsBloom, stateRoot, receiptRoot, totalDifficulty, extraData, gasLimit, gasUsed, uncles 등이 기존 Bitcoin 을 했던 사람들은 생소하게 보일것이다. 이건 차츰 알아가보도록 하고, 일단 정보가 잘 왔다면 가장 기본적인 API 를 성공적으로 실행 한 것이니 박수를 치자.
반응형
'Blockchain > Ethereum' 카테고리의 다른 글
Ethereum 응용 개발 - web3.js 사용하기 (0) | 2016.07.03 |
---|---|
Ethereum 응용 개발 - JSON RPC 로 Transaction 발생 시키기 (2) | 2016.03.26 |
이더리움 Smart Contract - 개념 (1) | 2016.03.13 |
C+++ Ethereum, Private Network 구성하기 #3 - eth 실행하기 (2) | 2016.03.06 |
C+++ Ethereum, Private Network 구성하기 #2 - Genesis 정보 만들기 (0) | 2016.03.02 |