github.com/reapchain/go-reapchain@v0.2.15-0.20210609012950-9735c110c705/cmd/chainadapter/main.go (about)

     1  // Copyright 2014 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // geth is the official command-line client for Ethereum.
    18  package main
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"github.com/ethereum/go-ethereum/core/types"
    24  	"github.com/ethereum/go-ethereum/ethclient"
    25  	"github.com/ethereum/go-ethereum/reapapis"
    26  	"github.com/ethereum/go-ethereum/rpc"
    27  	"github.com/segmentio/kafka-go"
    28  	"golang.org/x/net/context"
    29  	"math/big"
    30  	"os"
    31  	"time"
    32  
    33  	"github.com/ethereum/go-ethereum/cmd/utils"
    34  	"github.com/ethereum/go-ethereum/common"
    35  	"github.com/ethereum/go-ethereum/console"
    36  	"github.com/ethereum/go-ethereum/internal/debug"
    37  	"github.com/ethereum/go-ethereum/log"
    38  	"gopkg.in/urfave/cli.v1"
    39  )
    40  
    41  const (
    42  	clientIdentifier = "chainadapter" // Client identifier to advertise over the network
    43  )
    44  
    45  var (
    46  	// Git SHA1 commit hash of the release (set via linker flags)
    47  	gitCommit = ""
    48  	gitDate   = ""
    49  	// The app that holds all commands and flags.
    50  	app = utils.NewApp(gitCommit, "Agent from H/L to Reapchain")
    51  	// flags that configure the node
    52  )
    53  
    54  func init() {
    55  	// Initialize the CLI app and start Geth
    56  	app.Action = chainadapter
    57  	app.HideVersion = true // we have a command to print the version
    58  	app.Copyright = "Copyright 2013-2020 The go-ethereum Authors"
    59  	app.Commands = []cli.Command{
    60  	}
    61  	app.Flags = append(app.Flags, debug.Flags...)
    62  
    63  	app.Before = func(ctx *cli.Context) error {
    64  		return debug.Setup(ctx)
    65  	}
    66  	app.After = func(ctx *cli.Context) error {
    67  		debug.Exit()
    68  		console.Stdin.Close() // Resets terminal mode.
    69  		return nil
    70  	}
    71  }
    72  
    73  func main() {
    74  	if err := app.Run(os.Args); err != nil {
    75  		fmt.Fprintln(os.Stderr, err)
    76  		os.Exit(1)
    77  	}
    78  }
    79  
    80  // chainadapter is the main entry point into the system if no special subcommand is ran.
    81  // It creates a default node based on the command line arguments and runs it in
    82  // blocking mode, waiting for it to be shut down.
    83  func chainadapter(ctx *cli.Context) error {
    84  	if args := ctx.Args(); len(args) > 0 {
    85  		return fmt.Errorf("invalid command: %q", args[0])
    86  	}
    87  
    88  	proxyEnvInfos := getProxyEnvInfos()
    89  
    90  	// Governance key import
    91  	superAccount := reapapis.NewImportAccount()
    92  	if err := superAccount.Import(proxyEnvInfos.Governance); err != nil {
    93  		log.Error("failed to import super account key", "error", err)
    94  		os.Exit(-1)
    95  	}
    96  
    97  	// Reapchain 특정 Node 연결하기
    98  	client, err := rpc.Dial(proxyEnvInfos.Node.RpcAddress)
    99  	if err != nil {
   100  		log.Error("failed to connect with RPC ", "rpc", proxyEnvInfos.Node.RpcAddress, "error", err)
   101  		os.Exit(-1)
   102  	}
   103  
   104  	channelInput := make(chan kafka.Message)
   105  	channelOutput := make(chan bool)
   106  
   107  	// kafka에 데이터가 들어 왔을 경우, 대신 처리할 콜백 함수
   108  	// result값이 true이면, kafka의 Topic index에 commit 처리함
   109  	callback := func(message kafka.Message) bool {
   110  		log.Debug("receive kafka message", "kafka", string(message.Value))
   111  		channelInput <- message
   112  		result := <-channelOutput
   113  		return result
   114  	}
   115  
   116  	// kafka에 연결하기
   117  	myKafka := reapapis.NewKafkaClient([]string{proxyEnvInfos.Kafka.Address}, proxyEnvInfos.Kafka.Topic, callback)
   118  
   119  	// kafka 메시지 수신 백그라운드 동작시작
   120  	myKafka.ReadBackground(context.Background(), reapapis.ToEnd)
   121  
   122  	for {
   123  		// 데이터가 들어올때 까지 대기
   124  		inputData := <-channelInput
   125  		var dataForProtocol reapapis.ProxyCall
   126  		if err := reapapis.Deserialize(string(inputData.Value), &dataForProtocol); err != nil {
   127  			log.Error("json unmarshalling error, but commit for next Kafka's TX", "err", err)
   128  			channelOutput <- true
   129  		}
   130  
   131  		switch dataForProtocol.Call {
   132  		case "burn":
   133  			log.Debug("burn", "protocol", dataForProtocol.Account)
   134  			if exchange(client, superAccount, dataForProtocol.Account, big.NewInt(int64(dataForProtocol.Value)), proxyEnvInfos.Ratio) != nil {
   135  				channelOutput <- false
   136  			}
   137  		default:
   138  			log.Error("unknown Call", "checking", dataForProtocol.Call)
   139  			channelOutput <- false
   140  			continue
   141  		}
   142  		channelOutput <- true
   143  	}
   144  	return nil
   145  }
   146  
   147  //REAPCHAIN_ENVFILE, REAPCHAIN_ENVIRON 환경변수로 값 가져오기.
   148  //실패시 프로세스 다운됨
   149  func getProxyEnvInfos() reapapis.ProxyInfo {
   150  	configfile := os.Getenv("REAPCHAIN_ENVFILE")
   151  	if len(configfile) == 0 {
   152  		log.Error("getenv REAPCHAIN_ENVFILE", "empty")
   153  		os.Exit(-1)
   154  	}
   155  	configEnvVar := os.Getenv("REAPCHAIN_ENVIRON")
   156  	if len(configEnvVar) == 0 {
   157  		log.Error("getenv REAPCHAIN_ENVIRON", "empty")
   158  		os.Exit(-1)
   159  	}
   160  	config, err := reapapis.LoadConfigFile(configfile)
   161  	if err != nil {
   162  		log.Error("Get config for reapchain", "error", err)
   163  		os.Exit(-1)
   164  	}
   165  
   166  	switch configEnvVar {
   167  	case "local":
   168  		return config.Proxy.Local
   169  	case "test":
   170  		return config.Proxy.Test
   171  	case "production":
   172  		return config.Proxy.Production
   173  	default:
   174  		log.Error("unknown SETUP_INFO", "SETUP_INFO( local|test|production ) must be set", configEnvVar)
   175  	}
   176  	return reapapis.ProxyInfo{}
   177  }
   178  
   179  // Reapchain에게 환전요청하는 enpoint API, 내부에서 채번, signing, 환전Tx 송신, Tx의 응답확인함.
   180  // todo gaslimit/gasprice 값은 genesis 로부터 가져올것, eth_gasPrice, eth_estimateGas 함수 통해서 가져올것.
   181  func transfer(client *rpc.Client, account *reapapis.Account, to string, reap *big.Int, ratio float32) error {
   182  	// nonce 채번하기
   183  	var nonce uint64
   184  	var err error
   185  	nonce, err = reapapis.GetNonceTransaction(client, account.Account().Address, nil)
   186  	if err != nil {
   187  		log.Error("failed to get Nonce address : ", account.Account().Address)
   188  		return err
   189  	}
   190  
   191  	log.Debug("Nonce value ", "Account", account.Account().Address, "nonce", nonce)
   192  
   193  	tx := types.NewTransaction(nonce, account.Account().Address, reap, big.NewInt(3000000), big.NewInt(1), common.FromHex(to))
   194  	tx, err = account.SignTxWithPassphrase(tx, "reapchain", big.NewInt(2017))
   195  	if err != nil {
   196  		log.Error("SignTx error : ", "exchange", err)
   197  		return err
   198  	}
   199  
   200  	if err = reapapis.PublishTransaction(client, tx); err != nil {
   201  		log.Error("publishTransaction error : ", "exchange", err)
   202  		return err
   203  	}
   204  	log.Debug("publishTransaction", "Transaction hash", tx.Hash().String())
   205  
   206  	var retryCnt uint = 0
   207  	for {
   208  		retryCnt++
   209  		if retryCnt > 10 {
   210  			return errors.New("too many retry")
   211  		}
   212  
   213  		time.Sleep(1 * time.Second)
   214  
   215  		receipt, err := reapapis.TransactionReceipt(client, tx.Hash())
   216  		if err != nil {
   217  			log.Debug("TransactionReceipt", "retry", retryCnt)
   218  			continue
   219  		} else {
   220  			log.Debug("TransactionReceipt", "Receipt Hash", receipt.String())
   221  			break
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  //환전함수
   228  //TODO ratio 환전비율 변수 먹히지 않음
   229  //TODO GasEstimate 30000000 하드코딩되어 있음.
   230  //TODO reapapis.Account --> Wallet으로
   231  //TODO SignTxWithPassphrase, passwd, chainID 2017 하드코딩되어 있음
   232  func exchange(rpc *rpc.Client, account *reapapis.Account, to string, reap *big.Int, ratio float32) error {
   233  	var nonce uint64
   234  	var err error
   235  	nonce, err = getBigestNonce(rpc, account.Account().Address)
   236  	if err != nil {
   237  		log.Error("failed to get Nonce address : ", "Address", account.Account().Address)
   238  		return err
   239  	}
   240  	log.Debug("Nonce value ", "Account", account.Account().Address, "nonce", nonce)
   241  
   242  	tx := types.NewTransaction(nonce, account.Account().Address, reap, big.NewInt(3000000), big.NewInt(1), common.FromHex(to))
   243  	tx, err = account.SignTxWithPassphrase(tx, "reapchain", big.NewInt(2017))
   244  	if err != nil {
   245  		log.Error("SignTx error : ", "transfer", err)
   246  		return err
   247  	}
   248  
   249  	if err = publishTransaction(rpc, tx); err != nil {
   250  		log.Error("publishTransaction error : ", "transfer", err)
   251  		return err
   252  	}
   253  	log.Debug("publishTransaction", "transfer", tx.Hash().String())
   254  
   255  	var retryCnt uint = 0
   256  	for {
   257  		retryCnt++
   258  		if retryCnt > 20 {
   259  			return errors.New("too many retry")
   260  		}
   261  
   262  		time.Sleep(1 * time.Second)
   263  
   264  		receipt, err := getTransactionRecipt(rpc, tx.Hash())
   265  		if err != nil {
   266  			log.Debug("TransactionReceipt", "retry", retryCnt)
   267  			continue
   268  		} else {
   269  			log.Debug("TransactionReceipt", "Receipt Hash", receipt.TxHash)
   270  			break
   271  		}
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  //pending값과, blockchain값중에 큰 nonce값을 가져온다.
   278  func getBigestNonce(rpc *rpc.Client, account common.Address) (uint64, error) {
   279  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   280  	defer cancel()
   281  
   282  	client := ethclient.NewClient(rpc)
   283  	//blockNumber가 nil이면, 최신 블록중에 nonce 값
   284  	nonceFromLastBlock, err := client.NonceAt(ctx, account, nil)
   285  	if err != nil {
   286  		log.Error("get NonceAt", "error", err)
   287  		return 0, err
   288  	}
   289  	nonceFromPending, err := client.PendingNonceAt(ctx, account)
   290  	if err != nil {
   291  		log.Error("Pending Nonce", "error", err)
   292  		return 0, err
   293  	}
   294  	if nonceFromPending > nonceFromLastBlock {
   295  		return nonceFromPending, nil
   296  	}
   297  	return nonceFromLastBlock, nil
   298  }
   299  
   300  func publishTransaction(rpc *rpc.Client, tx *types.Transaction) error {
   301  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   302  	defer cancel()
   303  
   304  	client := ethclient.NewClient(rpc)
   305  	err := client.SendTransaction(ctx, tx)
   306  	if err != nil {
   307  		log.Error("SendTransaction", "error", err)
   308  		return err
   309  	}
   310  	return nil
   311  }
   312  
   313  func getTransactionRecipt(rpc *rpc.Client, txHash common.Hash) (*types.Receipt, error) {
   314  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   315  	defer cancel()
   316  
   317  	var r *types.Receipt
   318  	client := ethclient.NewClient(rpc)
   319  
   320  	r, err := client.TransactionReceipt(ctx, txHash)
   321  	if err != nil {
   322  		log.Error("TransactionReceipt", "error", err)
   323  		return nil, err
   324  	}
   325  	return r, nil
   326  }