블록을 참조하여 실제로 블록에 기록된 트랜잭션을 확인해야 합니다. 이러한 작업을 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 자동으로 해주는 프로그램을 블록체인 Daemon(데몬)이라고 합니다.
블록체인 Daemon으로는 전체 모든 트랜잭션과 블록을 수집할 수도(ex. 블록 익스플로러), 특정한 스마트 컨트랙트 주소(서비스에서 사용하는) 또는 사용자의 지갑 주소를 포함하는 트랜잭션과 블록만을 수집할 수도 있습니다.
가나슈(Ganache)는 가상의 이더리움 네트워크를 생성해서 송금, 스마트 컨트랙트 배포 및 실행 등을 가능하게, 로컬 테스트 환경을 구성해주는 프로그램입니다. 가나슈 등을 이용해 만든 가상 환경을 TestRPC라고도 합니다.
client, err := ethclient.Dial(cf.Network.URL)
if err != nil {
log.Fatal(err)
}
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex())
block, err := client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Hash().Hex())
fmt.Println(block.Number().Uint64())
fmt.Println(block.Time())
fmt.Println(block.Nonce())
fmt.Println(len(block.Transactions()))
// TODO: 블록 구조체 생성
b := model.Block{
BlockHash: block.Hash(),
BlockNumber: block.Number(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
Time: block.Time(),
Nonce: block.Nonce(),
Transactions: make([]model.Transaction, 0),
}
// TODO: 트랜잭션 추출
txs := block.Transactions()
if len(txs) > 0 {
for _, tx := range txs {
t := model.Transaction{}
t.TxHash = tx.Hash()
msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), big.NewInt(1))
if err != nil {
log.Fatal(err)
}
t.From = msg.From()
t.To = tx.To()
t.Nonce = tx.Nonce()
t.GasPrice = tx.GasPrice()
t.GasLimit = tx.Gas()
t.Amount = tx.Value()
t.BlockHash = block.Hash()
t.BlockNumber = block.Number().Uint64()
if tx.To() != nil {
t.To = tx.To()
}
b.Transactions = append(b.Transactions, t)
}
}
// TODO: 트랜잭션 구조체 생성
// DB 저장
err = md.SaveBlock(&b)
if err != nil {
log.Fatal(err)
}
}
}
client, err := ethclient.Dial(cf.Network.URL)
if err != nil {
log.Fatal(err)
}
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex())
block, err := client.BlockByNumber(context.Background(), header.Number)
if err != nil {
log.Fatal(err)
}
// 블록 구조체 생성
b := model.Block{
BlockHash: block.Hash().Hex(),
BlockNumber: block.Number().Uint64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
Time: block.Time(),
Nonce: block.Nonce(),
Transactions: make([]model.Transaction, 0),
}
// 트랜잭션 추출
txs := block.Transactions()
if len(txs) > 0 {
for _, tx := range txs {
msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), block.BaseFee())
if err != nil {
log.Fatal(err)
}
// 트랜잭션 구조체 생성
t := model.Transaction{
TxHash: tx.Hash().Hex(),
To: "", // 디폴트 값 처리
From: msg.From().Hex(),
Nonce: tx.Nonce(),
GasPrice: tx.GasPrice().Uint64(),
GasLimit: tx.Gas(),
Amount: tx.Value().Uint64(),
BlockHash: block.Hash().Hex(),
BlockNumber: block.Number().Uint64(),
}
if tx.To() != nil {
t.To = tx.To().Hex()
}
b.Transactions = append(b.Transactions, t)
}
}
// DB insert
err = md.SaveBlock(&b)
if err != nil {
log.Fatal(err)
}
}
}
// input Data에 데이터가 있으면 함수를 호출할 가능성이 있다.
if len(tx.Data()) != 0 {
// 실제 ERC20 토큰은 input datad안에 들어있다.
to, value := erc20.ERC20Transaction(hex.EncodeToString(tx.Data()))
if to != "" {
symbol, name, decimal := erc20.GetContractInfo(client, tx.To())
fmt.Println("ERC20 Contract Address: ", tx.To().Hex())
fmt.Println("ERC20 Contract to Name: ", name)
fmt.Println("ERC20 Contract to Symbol: ", symbol)
fmt.Println("ERC20 Contract to Decimals: ", decimal)
fmt.Println("ERC20 Transfer to Address: ", to)
if tokenValue := utils.GetRealValue(value, decimal); tokenValue != "" {
fmt.Println("ERC20 value :", tokenValue)
}
}
func GetContractInfo(client *ethclient.Client, to *common.Address) (string, string, uint8) {
instance, err := contracts.NewContracts(*to, client)
if err != nil {
log.Fatal(err)
}
name, err := instance.Name(&bind.CallOpts{})
if err != nil {
log.Fatal(err)
}
symbol, err := instance.Symbol(&bind.CallOpts{})
if err != nil {
log.Fatal(err)
}
decimals, err := instance.Decimals(&bind.CallOpts{})
if err != nil {
log.Fatal(err)
}
return name, symbol, decimals
}
func ERC20Transaction(data string) (string, string) {
// ERC20 토큰은 136개의 글자수로 이루어져 있다.
// a9059cbb0000000000000000000000004ebbd4881a45b836bac17ea52f1bcef72b787b0e00000000000000000000000000000000000000000000010f0cf064dd59200000
if len(data) != 136 {
return "", "0"
} else {
// 앞 8자리는 methodID
methodID := data[:8]
// 32~72는 to Address
to := data[32:72]
// 72~136은 토큰 양
value := data[72:136]
if methodID != "a9059cbb" {
return "", "0"
}
i := new(big.Int)
// 앞에 0 모두 제거
valueStr := strings.TrimLeft(value, "0")
i.SetString(valueStr, 16)
return to, i.String()
}
}
To Address
이다.해당 주소에서 transfer(address,uint256)
를 입력하면 “a9059cbb”
가 출력되는 것을 확인할 수 있다.
Amount
func GetRealValue(value string, decimal uint8) string {
n := new(big.Int)
pw := int(math.Pow(float64(10), float64(decimal)))
i := new(big.Int).SetUint64(uint64(pw))
n, ok := n.SetString(value, 10)
if !ok {
return ""
} else {
result := big.NewInt(0)
result.Div(n, i)
return result.String()
}
}
if contractAddress, err := utils.GetContractAddress(client, tx.Hash()); err != nil {
log.Fatal(err)
} else {
fmt.Println("GetContractAddress : ", contractAddress)
}
func GetContractAddress(client *ethclient.Client, txid common.Hash) (string, error) {
receipt_tx, err := client.TransactionReceipt(context.Background(), txid)
if err != nil {
return "", err
}
return receipt_tx.ContractAddress.Hex(), nil
}