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 }