5. Coinstack Basic

1. Blockchain과 Block

블록체인은 분산 데이터베이스 기술의 하나로, 운영자나 사용자라 할지라도 저장된 데이터를 임의로 조작하지 못하도록 고안되었습니다. 일반적인 데이터베이스와는 달리 블록체인은 자발적으로 참여하는 수 십, 수 백, 또는 수 천 개의 노드들에 의하여 운영되며, 이를 제어하기 위한 중앙 관리자가 존재하지 않습니다. 블록체인은 거래(transaction)들이 저장되는 거대한 분산 장부로서, 거래는 블록(block)에 담겨서 노드간에 공유되고 이 블록들이 사슬처럼 연결된 구조가 바로 블록체인입니다.

1.1. Blockchain 상태 조회

앞서 Getting Started 장에서 사용한 getBestBlockHash 메소드를 통해 블록 체인의 최신 상태 정보를 조회할 수 있습니다.

JAVA

String[] blockchainStatus = client.getBlockchainStatus();

REST API

YOUR_API_TOKEN_KEY="YOUR_API_TOKEN_KEY"

curl https://mainnet.cloudwallet.io/blockchain \
    -H "apiKey: $YOUR_API_TOKEN_KEY"

결과값은 JSON으로 반환됩니다.

{
  "best_block_hash": "000000000000000007e5724849ad23a76aa9db734e9671b2b08c075ab017fb6c",
  "best_height": 370353
}

Node.js

client.getBlockchainStatus(function(err, status) {
    console.log(status.best_block_hash, status.best_height);
});

Meteor

// server side
var status = client.getBlockchainStatusSync()
console.log(status.best_block_hash, best_height)

// client side
client.getBlockchainStatus(function(err, status) {
    console.log(status.best_block_hash, status.best_height);
});

1.2. Block 조회

특정 블록의 정보를 얻기 위해서는 블록의 ID인 블록 해시 값이 필요합니다. CoinStackClient 객체의 getBlock 메소드를 호출하면 입력한 블록 ID에 해당하는 블록의 정보가 담겨 있는 Block 객체를 반환합니다. Block 객체의 getBlockId, getParentId, getHeight 메소드를 통해 각각 블록 해시, 이전 블록의 해시, 블록 번호를 확인할 수 있습니다.

JAVA

Block block = client.getBlock("BLOCK_ID");
System.out.println("blockId: " + block.getBlockId());
System.out.println("parentId: " + block.getParentId());
System.out.println("height: " + block.getHeight());
System.out.println("time: " + block.getBlockConfirmationTime());

수행 결과는 다음과 같습니다.

blockId: 0000000000000000017363cc0f6562f4e4552b94032c49ce37f5fb1309062fc6
parentId: 00000000000000000428ec876f074bc7b34824bb11f8b00ec764622b3f54861d
height: 433254
time: Fri Oct 07 15:25:34 KST 2016

REST API

BLOCK_ID = "BLOCK_ID"

curl https://mainnet.cloudwallet.io/blocks/$BLOCK_ID \
    -H "apiKey: $YOUR_API_TOKEN_KEY"

수행 결과는 다음과 같습니다.

{
  "block_hash": "0000000000000000035bce787da99a937602b7b47367c1b4b026149eb699df72",
  "height": 433254,
  "confirmation_time": "2016-10-07T06:25:34Z",
  "parent": "00000000000000000428ec876f074bc7b34824bb11f8b00ec764622b3f54861d",
  "transaction_list": [
    "d855dea8da20fad1b6f0d1654f4fea0d933809f315216e4ea60b984ee947faed",
    "b613a39216eab6af00f29ef584bd4dd057497d4b8410a0dc97651d1a18b44878",
    "...."
  ]
}

Node.js

client.getBlock("BLOCK_ID", function(err, result) {
    console.log('result', result);
});

Meteor

// server
var result = client.getBlock("BLOCK_ID") {
    console.log(result.block_hash, result.height);
});

// client
client.getBlock("BLOCK_ID", function(err, result) {
    console.log('result', result);
});

2. Addresses

블록체인 서비스나 비트코인을 사용하기 위해서는 주소를 생성해야 합니다. 주소는 비트코인 네트워크 상에서 자신의 신원을 나타냅니다. 아래 설명할 개인키와 달리, 비트코인 주소는 다른 사람들과 공유하여 자신의 주소로 비트코인을 받기도 하고 주소에 담겨있는 비트코인을 다른 주소로 보내기도 합니다. 블록체인 응용 서비스에서는 좀 더 다양하게 주소를 활용하기도 하는데, TSA(Time Stamp Authority)라 불리는 문서 증명의 경우 이 주소를 문서 hash값을 담는 주소로 활용합니다. 개인 인증으로 활용할 경우는 이 주소를 인증서의 보관 장소로 활용할 수 있습니다. 비트코인 주소는 ECDSA(Eliptic Curve Digital Signature Algorithm)를 기반으로 하고 있으며, 그 주소 자체가 암호화 성격을 가지고 있으므로 다양한 분야에 적용할 수 있습니다.

2.1. Address 생성

비트코인 주소를 생성하기 위해서는 ECDSA 알고리즘 기반의 공개키와 개인키를 먼저 생성해야 합니다. 생성 순서는 다음과 같습니다.

Random Number Generation -> 개인키 생성 -> 공개키 생성 -> 비트코인 주소 생성

코인스택에서는 사용자가 위 과정을 이해하고 직접 수행할 필요 없이 손쉽게 주소를 생성할 수 있는 API를 제공합니다.

2.1.1. 개인키 생성

1에서 2^256 사이의 숫자 중 무작위로 정수 하나를 고르게 되는데 이것이 바로 개인키가 됩니다. 개인키는 보통 Base58Check으로 인코딩하여 사용하는데, 이것을 비트코인에서는 Private Key WIF (wallet import format)라고 부릅니다. WIF 형으로 변화하면 그 길이가 짧아지고 혼동 하기 쉬운 문자열 (1 또는 i)이 제거되어 편리한 형태가 됩니다.

개인키는 유출되지 않도록 주의하여야 합니다. 개인키가 유출되면 내 지갑의 비트코인을 도난당할 수 있습니다.

다음은 코인스택이 제공하는 개인키 생성 메소드입니다. 이처럼 코인스택을 사용하면 임의의 새로운 개인키를 빠르고 간편하게 생성할 수 있습니다.

Java

// create a new private key
String newPrivateKeyWIF = ECKey.createNewPrivateKey();
System.out.println("private key: " + newPrivateKeyWIF);

결과값은 문자열로 반환됩니다.

private key: L3nWhxhA68enYiXa2D1r4dj6bGDqXECEDoTgsgd4smBf8xrtnFED

2.1.2. 공개키 및 비트코인 주소 생성

공개키는 ECDSA 함수 중 하나인 secp256k1에 개인키를 입력값으로 한 결과 좌표 (x,y)로 표현됩니다. 우리가 사용하는 공개키의 형식은 이 좌표값 x와 y를 연결한 후 접두어를 붙인 형태입니다.

Java

// derive a public key
String newPublicKey = Hex.encodeHexString(ECKey.derivePubKey(newPrivateKeyWIF));
System.out.println("public key: " + newPublicKey);

결과값은 문자열로 반환됩니다.

public key: 04ed0f040740a2d2e60a9910b3bd94c92217d387a6bec8e9c78e246b78160fa64e9a4f1a82ec07ed7a9070cb26e32c3f770d31783a8f8456113c2d76f95d1166c2

비트코인 주소는 이 공개키를 RIPEMD160(SHA256(K)) 함수로 다시 인코딩하여 생성합니다. 다음은 코인스택을 사용하여 개인키에서 비트코인 주소를 생성하는 예시입니다.

Java

// derive an address
String your_wallet_address = ECKey.deriveAddress(newPrivateKeyWIF);
System.out.println("address: " + your_wallet_address);

결과값은 문자열로 반환됩니다.

address: 1MG4Y8JwqrMyNHpot9xvj62v12aPwuBB6W

2.4. Address Balance 조회

주소는 비트코인을 담고 있거나 OP_RETURN data를 담고 있습니다. 흩어져 있는 UTXO(Unspent output)의 비트코인 수량을 합산하여 보여주는 기능은 다음과 같습니다. (e.g. 0.0001 BTC = 10000 satoshi)

Java

// get a remaining balance
long balance = client.getBalance(your_wallet_address);
System.out.println("balance: " + balance);

결과값은 사토시 단위이며 Long 타입으로 반환됩니다.

balance: 566368820

REST API

YOUR_WALLET_ADDRESS="YOUR_WALLET_ADDRESS"

curl https://mainnet.cloudwallet.io/addresses/$YOUR_WALLET_ADDRESS/balance \
    -H "apiKey: $YOUR_API_TOKEN_KEY"

결과값은 사토시 단위이며 JSON 형식으로 반환됩니다.

{"balance":566368820}

Node.js

coinstackclient.getBalance("YOUR_WALLET_ADDRESS", function(err, balance) {
    console.log(balance);
});

Meteor

// server
var balance = coinstackclient.getBalanceSync("YOUR_WALLET_ADDRESS");
console.log(balance);

// client
coinstackclient.getBalance("YOUR_BLOCKCHAIN_ADDRESS", function(err, balance) {
    console.log(balance);
});

2.5. Address별 Transaction 조회

앞서 살펴본 바와 같이 주소는 비트코인을 담고 있거나 OP_RETURN 데이터를 담고 있습니다. 다음은 특정 주소에서 일어난 비트코인 거래 내역 또는 OP_RETURN 데이터의 내용을 조회하는 기능입니다.

Java

// print all transactions of a given wallet address
String[] transactionIds = client.getTransactions(your_wallet_address);
System.out.println("transactions");
for (String txId : transactionIds) {
  System.out.println("txIds[]: " + txId);
}

결과 값은 문자열의 배열로 반환됩니다.

transactions
4fe42d98bdede1eedb504c48a7670b18b1d3691ae140d313e529ab92d53c7aa0
ed7b57daba7621e726eab433823faa107cc20fdfe6c53c322288cb4161d850c1

REST API

curl https://mainnet.cloudwallet.io/addresses/YOUR_WALLET_ADDRESS/history \
 -H "apiKey: $YOUR_API_TOKEN_KEY"

결과 값은 JSON 형식으로 반환됩니다.

["4fe42d98bdede1eedb504c48a7670b18b1d3691ae140d313e529ab92d53c7aa0",
"ed7b57daba7621e726eab433823faa107cc20fdfe6c53c322288cb4161d850c1",
...
]

Node.js

coinstackclient.getTransactions("YOUR_WALLET_ADDRESS", function(err, result) {
    console.log(result);
});

Meteor

// server
var result = coinstackclient.getTransactionsSync("YOUR_WALLET_ADDRESS");
console.log(result);


// client
coinstackclient.getTransactions("YOUR_WALLET_ADDRESS", function(err, result) {
    console.log(result);
});

2.6. Address별 unspent outputs 조회

블록체인은 주소 별로 잔고를 관리하고 있는 것이 아니라 단순히 거래 내역을 기록하고 있을 뿐입니다. 자신의 주소에 해당하는 Transaction output은 두 가지 종류로 나눌 수 있는데, 한 가지는 Spent ouput, 다른 한 가지는 일반적으로 UTXO라 불리는 Unspent output입니다. 새로운 거래를 만들기 위해서는 이 UTXO를 input으로 입력해야 하는데, 코인스택에서는 이를 위해 특정 주소와 연관된 UTXO들을 조회하는 기능을 제공합니다.

Java

//print all utxos
Output[] outputs = client.getUnspentOutputs(your_wallet_address);
System.out.println("unspent outputs"); 
for (Output utxo: outputs) { 
    System.out.println(utxo.getValue());
}

결과값은 Output 객체의 배열로 반환됩니다. 예제에서는 Output이 가진 잔고를 출력합니다.

unspent outputs
7259033307
299990000
961900000

REST API

curl https://mainnet.cloudwallet.io/addresses/YOUR_WALLET_ADDRESS/unspentoutputs \
 -H "apiKey: $YOUR_API_TOKEN_KEY"

결과값은 JSON 형식으로 반환됩니다.

[
 {
 "transaction_hash": "f2020daaf62e03deb2c6f8a94988a6cca3d66273dc6fa2169e88f02b288520ea",
 "index": 1,
 "value": "7259033307",
 "script": "76a914a13ca67f70cc4afefd4057f87d1e433dab05a20788ac",
 "confirmations": 2509
 },
 {
 "transaction_hash": "9b30059da6517a08034c0faf3bd347a7a24189285fa80ede850ecc552d165620",
 "index": 1,
 "value": "299990000",
 "script": "76a914a13ca67f70cc4afefd4057f87d1e433dab05a20788ac",
 "confirmations": 99
 },
 {
 "transaction_hash": "9ac9ab9285ebb5107bfc2e086d35c3ebefd9a899722039fac667e6ece3b20c90",
 "index": 1,
 "value": "961900000",
 "script": "76a914a13ca67f70cc4afefd4057f87d1e433dab05a20788ac",
 "confirmations": 91
 }
]

Node.js

coinStackClient.getUnspentOutputs("YOUR_WALLET_ADDRESS", function(err, result) {
    console.log('result', result);
});

Meteor

// server
var result = coinstackclient.getUnspentOutputsSync("YOUR_WALLET_ADDRESS");
console.log('result', result);

// client
coinstackclient.getUnspentOutputs("YOUR_WALLET_ADDRESS", function(err, result) {
    console.log('result', result);
});

3. Transactions

3.1. 트랜잭션 생성

트랜잭션을 이용하여 특정 블록체인 주소로 비트코인을 전송하거나, 데이터를 저장할 수 있습니다.

3.1.1. Unspent Output 조회

새로운 트랜잭션을 만들기 위해서는 일단 input으로 사용할 unspent output을 조회하여 사용 가능한 잔고를 확인해야 합니다. SDK에 포함된 transaction builder를 이용하면 트랜잭션 생성시 자동으로 unspent output을 조회하여 최적의 unspent output을 선택해 줍니다.

3.1.2. 트랜잭션 생성 및 서명

3.1.1.1. 일반 트랜잭션

일반적으로 트랜잭션은 내 주소의 잔여 비트코인을 타 주소로 전송하기 위해 사용됩니다. 이 경우 비트코인을 전송받을 주소와 전송할 금액, 사용할 unspent output을 지정해야 합니다. (unspent output은 SDK가 자동적으로 지정해 줍니다.) 그리고 개인키로 서명하여 지정한 unspent output의 소유권을 증명합니다.

java

// create a target address to send
String toPrivateKeyWIF = ECKey.createNewPrivateKey();
String toAddress = ECKey.deriveAddress(toPrivateKeyWIF);

// create a transaction
long amount = io.blocko.coinstack.Math.convertToSatoshi("0.0002");
long fee = io.blocko.coinstack.Math.convertToSatoshi("0.0001");

TransactionBuilder builder = new TransactionBuilder();
builder.addOutput(toAddress, amount);
builder.setFee(fee);

// sign the transaction using the private key
String rawSignedTx = client.createSignedTransaction(builder, "YOUR_PRIVATE_KEY");
System.out.println(rawSignedTx);

결과값은 문자열로 반환됩니다.

01000000 01 be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d5396 00000000 8c 49 3046022100cf4d7571dd47a4d47f5cb767d54d6702530a3555726b27b6ac56117f5e7808fe0221008cbb42233bb04d7f28a715cf7c938e238afde90207e9d103dd9018e12cb7180e 01 41 042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9 ffffffff 01 23ce010000000000 19 76 a9 14 a2fd2e039a86dbcf0e1a664729e09e8007f89510 88 ac 00000000

Node.js

var txBuilder = coinstackclient.createTransactionBuilder();
txBuilder.addOutput("TO_ADDRESS", CoinStack.Math.toSatoshi("0.0001"));
txBuilder.setInput("YOUR_WALLET_ADDRESS");
txBuilder.setFee(CoinStack.Math.toSatoshi("0.0001"));

txBuilder.buildTransaction(function(err, tx) {
    tx.sign("YOUR_PRIVATE_KEY")
    var rawSignedTx = tx.serialize()
    console.log(rawSignedTx)
});

Meteor

// server
var txBuilder = coinstackclient.createTransactionBuilder();
txBuilder.addOutput("TO_ADDRESS", CoinStack.Math.toSatoshi("0.0001"));
txBuilder.setInput("YOUR_WALLET_ADDRESS");
txBuilder.setFee(CoinStack.Math.toSatoshi("0.0001"));

var tx = coinstackclient.buildTransactionSync(txBuilder);
tx.sign("YOUR_PRIVATE_KEY");
var rawSignedTx = tx.serialize();
console.log(rawSignedTx)

// client
var txBuilder = coinstackclient.createTransactionBuilder();
txBuilder.addOutput("TO_ADDRESS", CoinStack.Math.toSatoshi("0.0001"));
txBuilder.setInput("YOUR_WALLET_ADDRESS");
txBuilder.setFee(CoinStack.Math.toSatoshi("0.0001"));

txBuilder.buildTransaction(function(err, tx) {
    tx.sign("YOUR_PRIVATE_KEY");
    var rawSignedTx = tx.serialize();
    console.log(rawSignedTx)
});

비트코인 클라이언트에 따라 다르지만, 너무 적은 액수의 비트코인을 전송하는 트랜잭션은 dust threshold라는 정책에 의해 전송이 거부당할 수 있습니다.

Coinstack SDK의 transaction builder를 사용하는 경우 너무 적은 액수의 비트코인을 송금하려고 하면 SDK에서 오류가 발생합니다. 현재 코인스택 SDK에 기본으로 설정된 최소 전송 가능 액수는 5460 사토시이며, allowDustyOutput(true) 함수를 사용하면 이 설정을 비활성화하여 더 적은 양의 비트코인도 전송할 수 있습니다.

그러나 코인스택 SDK와는 별개로 각 블록체인 서버에 상이한 dust threshold 정책이 존재할 수 있으므로 접속하시는 서버의 정책 또한 확인하여 사용하시기 바랍니다. 현재 코인스택에서 제공하는 비트코인 클라우드 서비스의 dust threshold는 546 사토시이며, 구축형 모델에서는 이 값을 변경할 수 있습니다.

builder.allowDustyOutput(true);

3.1.1.2. Data 트랜잭션

비트코인을 전송하는 대신 OP_RETURN 이라는 output script를 사용하여 트랜잭션에 데이터를 저장할 수 있습니다. 코인스택 SDK에 포함된 transaction builder에서 제공하는 인터페이스를 사용하면 블록체인에 데이터를 손쉽게 저장할 수 있습니다. 코인스택에서 제공하는 stamping 및 Open Assets 관련 기능은 바로 이 OP_RETURN을 이용하여 구현되었습니다.

builder.setData("DATA_AT_OP_RETURN".getBytes());

3.2. 트랜잭션 전송

서명된 트랜잭션은 이제 네트워크에 전송될 수 있습니다. 만약 전송 과정에서 문제가 발생하거나, 트랜잭션 자체에 오류가 있으면 관련된 오류 코드로 원인을 확인할 수 있습니다. 하지만 트랜잭션 자체에 오류가 없는 경우에도 블록체인 네트워크의 특성상 일시적으로 트랜잭션이 거부되는 경우가 있습니다. 이 때는 네트워크에 성공적으로 전파될 때까지 재전송하면 됩니다.

Java

// send the signed transaction
client.sendTransaction(rawSignedTx);

Node.js

txBuilder.buildTransaction(function(err, tx) {
    tx.sign("YOUR_PRIVATE_KEY")
    var rawSignedTx = tx.serialize()
    // send transaction
    coinstackclient.sendTransaction(rawSignedTx, function(err) {
        if (null != err) {
            console.log("failed to send tx");
        }
    });
});

Meteor

// server
try {
    // send tx
    coinstackclient.sendTransactionSync(rawSignedTx);
} catch (e) {
    console.log("failed to send tx");
}

// client
txBuilder.buildTransaction(function(err, tx) {
    tx.sign("YOUR_PRIVATE_KEY");
    var rawSignedTx = tx.serialize();
    // send tx
    coinstackclient.sendTransaction(rawSignedTx, function(err) {
        if (null != err) {
            console.log("failed to send tx");
        }
    });
});

3.3. 트랜잭션 조회

생성한 트랜잭션의 ID를 조회하는 방법은 다음과 같습니다.

Java

String transactionId = TransactionUtil.getTransactionHash(rawSignedTx);

Node.js

var transactionId = CoinStack.Transaction.fromHex(rawSignedTx).getHash();

Meteor

var transactionId = CoinStack.Transaction.fromHex(rawSignedTx).getHash();

트랜잭션이 네트워크에 성공적으로 반영된 경우, 다음과 같이 해당 ID로 트랜잭션 정보를 조회할 수 있습니다.

Java

// print transaction
Transaction tx = client.getTransaction("YOUR_TRANSACTION_ID");
System.out.println(tx.getConfirmationTime())

결과 값은 Transaction 객체로 반환됩니다. 예제에서는 Transaction이 확정된 시간을 출력합니다.

Mon Oct 10 18:13:34 KST 2016

REST API

curl https://mainnet.cloudwallet.io/transactions/YOUR_TRANSACTION_ID \
    -H "apiKey: $YOUR_API_TOKEN_KEY"

결과값은 JSON으로 반환됩니다.

{
 "transaction_hash": "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
 "block_hash": [
 {
 "block_hash": "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
 "block_height": 100000
 }
 ],
 "coinbase": true,
 "inputs": [
 {
 "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000",
 "output_index": -1,
 "address": [
 ],
 "value": ""
 }
 ],
 "outputs": [
 {
 "index": 0,
 "address": [
 "1HWqMzw1jfpXb3xyuUZ4uWXY4tqL2cW47J"
 ],
 "value": "5000000000",
 "script": "41041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac",
 "used": false
 }
 ],
 "time": "2010-12-29T11:57:43Z",
 "broadcast_time": "0001-01-01T00:00:00Z",
 "addresses": [
 "1HWqMzw1jfpXb3xyuUZ4uWXY4tqL2cW47J"
 ]
}

Node.js

coinstackclient.getTransaction("YOUR_TRANSACTION_ID", function(err, result) {
    console.log('result', result);
});

Meteor

// server
var result = coinstackclient.getTransactionSync("YOUR_TRANSACTION_ID");
console.log(result.transaction_hash, result.inputs);

// client
coinstackclient.getTransaction("YOUR_TRANSACTION_ID", function(err, result) {
    console.log('result', result);
});

Last updated