TIL 44 - 블록체인과 백엔드 활용 심화(Daemon)

프동프동·2023년 2월 11일
0

TIL

목록 보기
44/46
post-thumbnail

블록체인 Daemon의 역할에 대해 이해할 수 있다.

블록을 참조하여 실제로 블록에 기록된 트랜잭션을 확인해야 합니다. 이러한 작업을 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 자동으로 해주는 프로그램을 블록체인 Daemon(데몬)이라고 합니다.

블록체인 Daemon으로는 전체 모든 트랜잭션과 블록을 수집할 수도(ex. 블록 익스플로러), 특정한 스마트 컨트랙트 주소(서비스에서 사용하는) 또는 사용자의 지갑 주소를 포함하는 트랜잭션과 블록만을 수집할 수도 있습니다.

Ganache 활용에 대해 간단하게 이해할 수 있다.

가나슈(Ganache)는 가상의 이더리움 네트워크를 생성해서 송금, 스마트 컨트랙트 배포 및 실행 등을 가능하게, 로컬 테스트 환경을 구성해주는 프로그램입니다. 가나슈 등을 이용해 만든 가상 환경을 TestRPC라고도 합니다.

Go를 활용해 이더리움 네트워크의 Daemon을 개발할 수 있다.

ethclient 초기화

client, err := ethclient.Dial(cf.Network.URL)
	if err != nil {
		log.Fatal(err)
	}

subscribe

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)
			}

		}
	}

Go를 활용해 WEMIX 네트워크의 Daemon을 개발할 수 있다.

ethclient 초기화

client, err := ethclient.Dial(cf.Network.URL)
	if err != nil {
		log.Fatal(err)
	}

subscribe

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)
			}
		}
	}

특정한 조건의 트랜잭션만 추출하기

ERC20 Transfer Transaction이 발생했을 때 긁어오기

  • ERC20 토큰 보내기

  • 실행 결과

  • main.go
    // 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)
    		}
    	}
    • 트랜잭션에 input data가 포함되어 있으면 함수를 호출할 가능성이 있음을 확인하고
    • input data를 파싱한다.
  • erc20/erc20.go
    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
    
    }
  • erc20/erc20.go
    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()
    	}
    }
    • data의 길이가 136개의 글자수를 가지고 있으면 ERC20의 토큰일 수 있다.
    • 8자리가 “a9059cbb”로 시작하면 ERC20이다.
      • 32~72자리에 있는 데이터는 To Address 이다.
        • 해당 주소에서 transfer(address,uint256) 를 입력하면 “a9059cbb” 가 출력되는 것을 확인할 수 있다.

          Keccak-256

      • 72~136 자리는 토큰의Amount
        • 앞에 0을 모두 제거한다.
    • value는 계산해서 처리해줘야한다.
  • utils/utils.go
    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()
    	}
    }

Contract Address Transaction 긁어오기

  • main.go
    if contractAddress, err := utils.GetContractAddress(client, tx.Hash()); err != nil {
    	log.Fatal(err)
    } else {
    	fmt.Println("GetContractAddress : ", contractAddress)
    }
    • GetContractAddress
      • 트랜잭션 해시값을 받는다.
  • utils/utils.go
    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
    }
    • Receipt에 있는 Contract Address를 가져온다.
  • 실행 결과
    • 배포 진행
    • 결과
profile
좋은 개발자가 되고싶은

0개의 댓글