BTC, ETH, XRP 체인별 트랜잭션 파싱

707·2023년 12월 19일
1

블록체인

목록 보기
10/10
post-thumbnail

1. ETH

RPC 메소드

이더리움에서 트랜잭션 조회 시 사용되는 JSON-RPC 메소드는 다음과 같습니다.

  1. eth_getblockbynumber

    {
    	"method":"eth_getBlockByNumber",
    	"params":["0x1b4", true]
    }

    블록넘버를 16진수로 변환하여 params로 넣어줘야합니다.

조회절차

해당 블록 정보에서 확인 가능한 트랜잭션의 구조는 다음과 같습니다.

type Transaction struct {
	Hash             string
	Nonce            int
	BlockHash        string
	BlockNumber      *int
	TransactionIndex *int
	From             string
	To               string
	Value            big.Int
	Gas              int
	GasPrice         big.Int
	Input            string
}

Transaction 내에서 바로 FromTo, Value, Hash를 조회할 수 있습니다.

2. XRP

RPC 메소드

리플에서 트랜잭션 조회 시 사용되는 JSON-RPC 메소드는 다음과 같습니다

  1. ledger

    {
        "method": "ledger",
        "params": [
            {
                "ledger_index": 123456,
                "accounts": false,
                "full": false,
                "transactions": true,
                "expand": true,
                "owner_funds": false
            }
        ]
    }

    params의 transactionsexpand항목을 true로 지정해야 상세한 트랜잭션 내역이 포함된 블록(리플에서는 렛저)정보를 가져올 수 있습니다.

조회절차

리플의 트랜잭션은 TransactionType을 가지고 있습니다. (타입으로 Payment, Offer, OfferCancel, NFT Mint 등등이 있음)

본 스캐너에서 확인해야 하는 것은 단순한 XRP의 지갑→지갑 전송이므로 이에 해당하는 (TransactionType == “Payment” ) 조건을 먼저 확인합니다.

+) 리플의 지갑 주소에는 Destination과 DestinationTag 시스템이 있기때문에 입금 확인 시 주소와 태그까지 모두 일치하는지 확인하고 있습니다.

리플의 트랜잭션 구조는 다음과 같습니다.

type Transaction struct {
	TransactionType string `json:"TransactionType"`
	Hash string `json:"hash"`
	Account string `json:"Account"`
	Destination string `json:"Destination"`
	DestinationTag int `json:"DestinationTag"`
	Amount interface{} `json:"amount"`
	Fee interface{} `json:"fee"`
}

이 외의 항목이 더 있는데 너무 많은 데이터를 담고 있어 필요한 항목만 Unmarshal 하도록 하였습니다.
Account(From), Destination(To), DestinationTag(To), Amount, Hash를 조회할 수 있습니다.

여기서 Amount는 interface{}로 any타입으로 지정해두었는데, 그 이유는
리플의 Payment에는 Direct XRP PaymentsCross-Currency Payments 두가지 종류가 있기 때문입니다.

cross-currency payment는 이더리움으로 말하면 ERC20토큰의 transfer와 유사한 개념인데 리플에서는 이를 단순히 payment 트랜잭션으로 처리하고 있습니다.

이 타입의 트랜잭션에서는 Amount 항목에 수량 대신

{
	currency:MRM, 
	issuer:rNjQ9HZYiBk1WhuscDkmJRSc3gbrBqqAaQ,
	value:50000
}

와 같은 맵이 들어있습니다.

따라서 리플을 사용한 Direct XRP Payment 만을 조회하기 위해 이러한 맵 구조의 Amount를 무시하는 조건을 넣어주었습니다.


3. BTC

RPC 메소드

비트코인에서 제공되는 JSON-RPC 메소드 중 현재 스캐너서버에서 이용하는 메소드는

  1. getblockhash(블록넘버로 블록해시 조회)

    {"method": "getblockhash", "params": [1000]}
  2. getblock(블록해시로 블록데이터 조회)

    {"method": "getblock", "params": ["00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"]}
  3. getrawtransaction(트랜잭션해시로 트랜잭션데이터 조회)

    {"method": "getrawtransaction", "params": ["mytxid", true]}

세가지가 있습니다.

조회절차

비트코인은 블록넘버로 블록 정보를 바로 조회할 수 있는 api를 제공하고 있지 않기 때문에

  1. 블록넘버로 블록해시 조회
  2. 블록해시로 블록데이터 내 트랜잭션 조회

과정을 거쳐서 트랜잭션을 확인하고 있습니다.

트랜잭션의 구조는 다음과 같습니다.

type RawTx struct{
  Txid string `json:"txid"`
  Hash string `json:"hash"`
  Version int `json:"version"`
  Size int `json:"size"`
  Vsize int `json:"vsize"`
  Weight int `json:"weight"`
  Locktime int `json:"locktime"`
  Vin []Vin `json:"vin"`
  Vout []Vout `json:"vout"`
  Hex string `json:"hex"`
  BlockHash string `json:"blockhash"`
  Confirmations int `json:"confirmations"`
  Time int `json:"time"`
  Blocktime int `json:"blocktime"`
}

type Vin struct {
  Txid string `json:"txid"`
  Vout int `json:"vout"`
  ScriptSig interface{} `json:"scriptSig"` // 서명과 공개키로 이루어짐
  Txinwitness []string `json:"txinwitness"`
  Sequence int `json:"sequence"`
}

type Vout struct {
  Value float64 `json:"value"`
  N int `json:"n"`
  ScriptPubKey ScriptPubKeyVout `json:"scriptPubKey"`
}

type ScriptPubKeyVout struct {
  Address string `json:"address"`
}

여기서 Vout에 트랜잭션을 통해 송금을 받을 사람의 내역이 배열로 들어가있고

  • 송금받을 금액 : Vout[n].Value
  • 송금받을 지갑주소 : Vout[n].ScriptPubKey.Address

로 확인할 수 있습니다.

[]Vout을 순회하며 address를 조회하다가 일치하는 지갑주소를 발견하면 그 때 Vin을 확인합니다.
Vin에는 지갑주소가 별도로 명시되어있지 않고 Txid항목과 Vout항목이 있습니다. 각각을 이용하면UTXO가 발행된 트랜잭션의 해시값해당 트랜잭션의 Vout배열 중 몇번째에 해당하는 UTXO인지(Vout 내의 n)를 알 수 있습니다.

이를 이용해 getrawtransaction api로 해당 트랜잭션을 조회합니다.

이 과정을 거치면 그 UTXO의 금액과 소유한 지갑주소를 확인할 수 있습니다.

이 때

일반적인 지갑→지갑 트랜잭션은

이런 1 In - 1~2 Out의 형태를 띄기 때문에 Vout에서 지갑주소를 발견하면 Vin의 0번 인덱스의 해시를 조회함으로써 from 주소를 확인하여 가지고 올 수 있습니다. (현재 스캐너의 로직도 Vin[0].Txid만 조회해서 from 주소로 사용하는 방식)

4. 트러블슈팅

비트코인에 대한 트랜잭션 크롤링 작업을 처리할때는 특유의 UTXO 구조 때문에 여러가지 문제를 마주하였고 이에 대해 정리를 했습니다.

1. N In - N Out 형태의 트랜잭션 처리

비트코인은 구조상 여러개의 In과 여러개의 Out을 가질 수 있으므로
만약 이러한 형태의 트랜잭션에서 Vout에 저희가 가지고 있는 지갑주소가 검출된 경우, from 주소를 어떤 것으로 특정해야하는지를 모르겠습니다.

만약 이러한 형태의 트랜잭션이 일반적인 거래소나 지갑을 이용한 입출금 시에는 이용되지 않는 방식이라면 이러한 케이스는 무시하고 기존의 로직(Vin에는 하나의 지갑주소만 있다고 가정) 방식을 유지해도 될 지, 아니면 이러한 경우를 고려하여 코드를 수정해야 하는지 궁금합니다.

이후 해당 로직을 유지한 채로 개발을 진행

2. 모든 트랜잭션의 From address 확인이 필요한 경우

현재는 스캐너에서 입금 기록만을 확인하고 있기때문에 To에 해당하는 내역만 조회한다면 블록데이터만 가지고 가능하지만, From에 해당하는 주소값도 만약 확인해야한다면
일반적으로 블록 하나당 2000개의 트랜잭션과 6-7000개의 Vin이 있다고 가정했을 때 모든 트랜잭션의 Vin마다 getrawtransaction api를 이용해 매번 주소를 확인해야하고 너무 많은 시간과 리소스를 필요로 하는 작업이 됩니다.

현재는 from은 확인을 따로 하고 있지 않기때문에 이러한 과정이 필요하지는 않는데.. 그럼 이 부분은 생각하지 않고 넘어가도 괜찮을지, 아니면 이렇게 from 주소를 확인해야할 경우도 미리 생각해둬야할지 궁금합니다.

getrawtransaction API를 이용하지 않고 직접 Vin의 scriptSig 등을 이용해 추가 rpc 호출 없이도 from 주소를 파싱할 수 있는 방법이 없는지 확인 : 비트코인의 스크립트 구조에 대해 스터디를 진행했으나 오히려 이러한 방식이 더 비효율적인 듯하여 기존 로직을 유지

0개의 댓글