gitlab.com/flarenetwork/coreth@v0.1.1/plugin/evm/service.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "math/big" 11 "net/http" 12 "strings" 13 14 "github.com/ava-labs/avalanchego/api" 15 "github.com/ava-labs/avalanchego/ids" 16 "github.com/ava-labs/avalanchego/utils/constants" 17 "github.com/ava-labs/avalanchego/utils/crypto" 18 "github.com/ava-labs/avalanchego/utils/formatting" 19 "github.com/ava-labs/avalanchego/utils/json" 20 "github.com/ethereum/go-ethereum/common" 21 "github.com/ethereum/go-ethereum/common/hexutil" 22 ethcrypto "github.com/ethereum/go-ethereum/crypto" 23 "github.com/ethereum/go-ethereum/log" 24 "gitlab.com/flarenetwork/coreth/params" 25 ) 26 27 // test constants 28 const ( 29 GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85" 30 GenesisTestKey = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1" 31 32 // Max number of addresses that can be passed in as argument to GetUTXOs 33 maxGetUTXOsAddrs = 1024 34 ) 35 36 var ( 37 errNoAddresses = errors.New("no addresses provided") 38 errNoSourceChain = errors.New("no source chain provided") 39 errNilTxID = errors.New("nil transaction ID") 40 41 initialBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) 42 ) 43 44 // SnowmanAPI introduces snowman specific functionality to the evm 45 type SnowmanAPI struct{ vm *VM } 46 47 // AvaxAPI offers Avalanche network related API methods 48 type AvaxAPI struct{ vm *VM } 49 50 // NetAPI offers network related API methods 51 type NetAPI struct{ vm *VM } 52 53 // Listening returns an indication if the node is listening for network connections. 54 func (s *NetAPI) Listening() bool { return true } // always listening 55 56 // PeerCount returns the number of connected peers 57 func (s *NetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(0) } 58 59 // Version returns the current ethereum protocol version. 60 func (s *NetAPI) Version() string { return fmt.Sprintf("%d", s.vm.networkID) } 61 62 // Web3API offers helper API methods 63 type Web3API struct{} 64 65 // ClientVersion returns the version of the vm running 66 func (s *Web3API) ClientVersion() string { return Version } 67 68 // Sha3 returns the bytes returned by hashing [input] with Keccak256 69 func (s *Web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return ethcrypto.Keccak256(input) } 70 71 // GetAcceptedFrontReply defines the reply that will be sent from the 72 // GetAcceptedFront API call 73 type GetAcceptedFrontReply struct { 74 Hash common.Hash `json:"hash"` 75 Number *big.Int `json:"number"` 76 } 77 78 // GetAcceptedFront returns the last accepted block's hash and height 79 func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) { 80 blk := api.vm.chain.LastAcceptedBlock() 81 return &GetAcceptedFrontReply{ 82 Hash: blk.Hash(), 83 Number: blk.Number(), 84 }, nil 85 } 86 87 // IssueBlock to the chain 88 func (api *SnowmanAPI) IssueBlock(ctx context.Context) error { 89 log.Info("Issuing a new block") 90 91 api.vm.signalTxsReady() 92 return nil 93 } 94 95 // parseAssetID parses an assetID string into an ID 96 func (service *AvaxAPI) parseAssetID(assetID string) (ids.ID, error) { 97 if assetID == "" { 98 return ids.ID{}, fmt.Errorf("assetID is required") 99 } else if assetID == "AVAX" { 100 return service.vm.ctx.AVAXAssetID, nil 101 } else { 102 return ids.FromString(assetID) 103 } 104 } 105 106 type VersionReply struct { 107 Version string `json:"version"` 108 } 109 110 // ClientVersion returns the version of the VM running 111 func (service *AvaxAPI) Version(r *http.Request, args *struct{}, reply *VersionReply) error { 112 reply.Version = Version 113 return nil 114 } 115 116 // ExportKeyArgs are arguments for ExportKey 117 type ExportKeyArgs struct { 118 api.UserPass 119 Address string `json:"address"` 120 } 121 122 // ExportKeyReply is the response for ExportKey 123 type ExportKeyReply struct { 124 // The decrypted PrivateKey for the Address provided in the arguments 125 PrivateKey string `json:"privateKey"` 126 PrivateKeyHex string `json:"privateKeyHex"` 127 } 128 129 // ExportKey returns a private key from the provided user 130 func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error { 131 log.Info("EVM: ExportKey called") 132 133 address, err := ParseEthAddress(args.Address) 134 if err != nil { 135 return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) 136 } 137 138 db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) 139 if err != nil { 140 return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) 141 } 142 defer db.Close() 143 144 user := user{ 145 secpFactory: &service.vm.secpFactory, 146 db: db, 147 } 148 sk, err := user.getKey(address) 149 if err != nil { 150 return fmt.Errorf("problem retrieving private key: %w", err) 151 } 152 encodedKey, err := formatting.EncodeWithChecksum(formatting.CB58, sk.Bytes()) 153 if err != nil { 154 return fmt.Errorf("problem encoding bytes as cb58: %w", err) 155 } 156 reply.PrivateKey = constants.SecretKeyPrefix + encodedKey 157 reply.PrivateKeyHex = hexutil.Encode(sk.Bytes()) 158 return nil 159 } 160 161 // ImportKeyArgs are arguments for ImportKey 162 type ImportKeyArgs struct { 163 api.UserPass 164 PrivateKey string `json:"privateKey"` 165 } 166 167 // ImportKey adds a private key to the provided user 168 func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *api.JSONAddress) error { 169 log.Info("EVM: ImportKey called", "username", args.Username) 170 171 if !strings.HasPrefix(args.PrivateKey, constants.SecretKeyPrefix) { 172 return fmt.Errorf("private key missing %s prefix", constants.SecretKeyPrefix) 173 } 174 175 trimmedPrivateKey := strings.TrimPrefix(args.PrivateKey, constants.SecretKeyPrefix) 176 pkBytes, err := formatting.Decode(formatting.CB58, trimmedPrivateKey) 177 if err != nil { 178 return fmt.Errorf("problem parsing private key: %w", err) 179 } 180 181 skIntf, err := service.vm.secpFactory.ToPrivateKey(pkBytes) 182 if err != nil { 183 return fmt.Errorf("problem parsing private key: %w", err) 184 } 185 sk, ok := skIntf.(*crypto.PrivateKeySECP256K1R) 186 if !ok { 187 return fmt.Errorf("expected *crypto.PrivateKeySECP256K1R but got %T", skIntf) 188 } 189 190 // TODO: return eth address here 191 reply.Address = FormatEthAddress(GetEthAddress(sk)) 192 193 db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) 194 if err != nil { 195 return fmt.Errorf("problem retrieving data: %w", err) 196 } 197 defer db.Close() 198 199 user := user{ 200 secpFactory: &service.vm.secpFactory, 201 db: db, 202 } 203 if err := user.putAddress(sk); err != nil { 204 return fmt.Errorf("problem saving key %w", err) 205 } 206 return nil 207 } 208 209 // ImportArgs are arguments for passing into Import requests 210 type ImportArgs struct { 211 api.UserPass 212 213 // Fee that should be used when creating the tx 214 BaseFee *hexutil.Big `json:"baseFee"` 215 216 // Chain the funds are coming from 217 SourceChain string `json:"sourceChain"` 218 219 // The address that will receive the imported funds 220 To string `json:"to"` 221 } 222 223 // ImportAVAX is a deprecated name for Import. 224 func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { 225 return service.Import(nil, args, response) 226 } 227 228 // Import issues a transaction to import AVAX from the X-chain. The AVAX 229 // must have already been exported from the X-Chain. 230 func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { 231 log.Info("EVM: ImportAVAX called") 232 233 chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) 234 if err != nil { 235 return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) 236 } 237 238 to, err := ParseEthAddress(args.To) 239 if err != nil { // Parse address 240 return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err) 241 } 242 243 // Get the user's info 244 db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) 245 if err != nil { 246 return fmt.Errorf("couldn't get user '%s': %w", args.Username, err) 247 } 248 defer db.Close() 249 250 user := user{ 251 secpFactory: &service.vm.secpFactory, 252 db: db, 253 } 254 privKeys, err := user.getKeys() 255 if err != nil { // Get keys 256 return fmt.Errorf("couldn't get keys controlled by the user: %w", err) 257 } 258 259 var baseFee *big.Int 260 if args.BaseFee == nil { 261 // Get the base fee to use 262 baseFee, err = service.vm.estimateBaseFee(context.Background()) 263 if err != nil { 264 return err 265 } 266 } else { 267 baseFee = args.BaseFee.ToInt() 268 } 269 270 tx, err := service.vm.newImportTx(chainID, to, baseFee, privKeys) 271 if err != nil { 272 return err 273 } 274 275 response.TxID = tx.ID() 276 return service.vm.issueTx(tx) 277 } 278 279 // ExportAVAXArgs are the arguments to ExportAVAX 280 type ExportAVAXArgs struct { 281 api.UserPass 282 283 // Fee that should be used when creating the tx 284 BaseFee *hexutil.Big `json:"baseFee"` 285 286 // Amount of asset to send 287 Amount json.Uint64 `json:"amount"` 288 289 // ID of the address that will receive the AVAX. This address includes the 290 // chainID, which is used to determine what the destination chain is. 291 To string `json:"to"` 292 } 293 294 // ExportAVAX exports AVAX from the C-Chain to the X-Chain 295 // It must be imported on the X-Chain to complete the transfer 296 func (service *AvaxAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JSONTxID) error { 297 return service.Export(nil, &ExportArgs{ 298 ExportAVAXArgs: *args, 299 AssetID: service.vm.ctx.AVAXAssetID.String(), 300 }, response) 301 } 302 303 // ExportArgs are the arguments to Export 304 type ExportArgs struct { 305 ExportAVAXArgs 306 // AssetID of the tokens 307 AssetID string `json:"assetID"` 308 } 309 310 // Export exports an asset from the C-Chain to the X-Chain 311 // It must be imported on the X-Chain to complete the transfer 312 func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.JSONTxID) error { 313 log.Info("EVM: Export called") 314 315 assetID, err := service.parseAssetID(args.AssetID) 316 if err != nil { 317 return err 318 } 319 320 if args.Amount == 0 { 321 return errors.New("argument 'amount' must be > 0") 322 } 323 324 chainID, to, err := service.vm.ParseAddress(args.To) 325 if err != nil { 326 return err 327 } 328 329 // Get this user's data 330 db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) 331 if err != nil { 332 return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) 333 } 334 defer db.Close() 335 336 user := user{ 337 secpFactory: &service.vm.secpFactory, 338 db: db, 339 } 340 privKeys, err := user.getKeys() 341 if err != nil { 342 return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) 343 } 344 345 var baseFee *big.Int 346 if args.BaseFee == nil { 347 // Get the base fee to use 348 baseFee, err = service.vm.estimateBaseFee(context.Background()) 349 if err != nil { 350 return err 351 } 352 } else { 353 baseFee = args.BaseFee.ToInt() 354 } 355 356 // Create the transaction 357 tx, err := service.vm.newExportTx( 358 assetID, // AssetID 359 uint64(args.Amount), // Amount 360 chainID, // ID of the chain to send the funds to 361 to, // Address 362 baseFee, 363 privKeys, // Private keys 364 ) 365 if err != nil { 366 return fmt.Errorf("couldn't create tx: %w", err) 367 } 368 369 response.TxID = tx.ID() 370 return service.vm.issueTx(tx) 371 } 372 373 // GetUTXOs gets all utxos for passed in addresses 374 func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply *api.GetUTXOsReply) error { 375 service.vm.ctx.Log.Info("EVM: GetUTXOs called for with %s", args.Addresses) 376 377 if len(args.Addresses) == 0 { 378 return errNoAddresses 379 } 380 if len(args.Addresses) > maxGetUTXOsAddrs { 381 return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs) 382 } 383 384 if args.SourceChain == "" { 385 return errNoSourceChain 386 } 387 388 chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) 389 if err != nil { 390 return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err) 391 } 392 sourceChain := chainID 393 394 addrSet := ids.ShortSet{} 395 for _, addrStr := range args.Addresses { 396 addr, err := service.vm.ParseLocalAddress(addrStr) 397 if err != nil { 398 return fmt.Errorf("couldn't parse address %q: %w", addrStr, err) 399 } 400 addrSet.Add(addr) 401 } 402 403 startAddr := ids.ShortEmpty 404 startUTXO := ids.Empty 405 if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" { 406 startAddr, err = service.vm.ParseLocalAddress(args.StartIndex.Address) 407 if err != nil { 408 return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err) 409 } 410 startUTXO, err = ids.FromString(args.StartIndex.UTXO) 411 if err != nil { 412 return fmt.Errorf("couldn't parse start index utxo: %w", err) 413 } 414 } 415 416 utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs( 417 sourceChain, 418 addrSet, 419 startAddr, 420 startUTXO, 421 int(args.Limit), 422 ) 423 if err != nil { 424 return fmt.Errorf("problem retrieving UTXOs: %w", err) 425 } 426 427 reply.UTXOs = make([]string, len(utxos)) 428 for i, utxo := range utxos { 429 b, err := service.vm.codec.Marshal(codecVersion, utxo) 430 if err != nil { 431 return fmt.Errorf("problem marshalling UTXO: %w", err) 432 } 433 str, err := formatting.EncodeWithChecksum(args.Encoding, b) 434 if err != nil { 435 return fmt.Errorf("problem encoding utxo: %w", err) 436 } 437 reply.UTXOs[i] = str 438 } 439 440 endAddress, err := service.vm.FormatLocalAddress(endAddr) 441 if err != nil { 442 return fmt.Errorf("problem formatting address: %w", err) 443 } 444 445 reply.EndIndex.Address = endAddress 446 reply.EndIndex.UTXO = endUTXOID.String() 447 reply.NumFetched = json.Uint64(len(utxos)) 448 reply.Encoding = args.Encoding 449 return nil 450 } 451 452 // IssueTx ... 453 func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error { 454 log.Info("EVM: IssueTx called") 455 456 txBytes, err := formatting.Decode(args.Encoding, args.Tx) 457 if err != nil { 458 return fmt.Errorf("problem decoding transaction: %w", err) 459 } 460 461 tx := &Tx{} 462 if _, err := service.vm.codec.Unmarshal(txBytes, tx); err != nil { 463 return fmt.Errorf("problem parsing transaction: %w", err) 464 } 465 if err := tx.Sign(service.vm.codec, nil); err != nil { 466 return fmt.Errorf("problem initializing transaction: %w", err) 467 } 468 469 response.TxID = tx.ID() 470 return service.vm.issueTx(tx) 471 } 472 473 // GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API 474 type GetAtomicTxStatusReply struct { 475 Status Status `json:"status"` 476 BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` 477 } 478 479 // GetAtomicTxStatus returns the status of the specified transaction 480 func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, reply *GetAtomicTxStatusReply) error { 481 log.Info("EVM: GetAtomicTxStatus called", "txID", args.TxID) 482 483 if args.TxID == ids.Empty { 484 return errNilTxID 485 } 486 487 _, status, height, _ := service.vm.getAtomicTx(args.TxID) 488 489 reply.Status = status 490 if status == Accepted { 491 jsonHeight := json.Uint64(height) 492 reply.BlockHeight = &jsonHeight 493 } 494 return nil 495 } 496 497 type FormattedTx struct { 498 api.FormattedTx 499 BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` 500 } 501 502 // GetAtomicTx returns the specified transaction 503 func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply *FormattedTx) error { 504 log.Info("EVM: GetAtomicTx called", "txID", args.TxID) 505 506 if args.TxID == ids.Empty { 507 return errNilTxID 508 } 509 510 tx, status, height, err := service.vm.getAtomicTx(args.TxID) 511 if err != nil { 512 return err 513 } 514 515 if status == Unknown { 516 return fmt.Errorf("could not find tx %s", args.TxID) 517 } 518 519 txBytes, err := formatting.EncodeWithChecksum(args.Encoding, tx.Bytes()) 520 if err != nil { 521 return err 522 } 523 reply.Tx = txBytes 524 reply.Encoding = args.Encoding 525 if status == Accepted { 526 jsonHeight := json.Uint64(height) 527 reply.BlockHeight = &jsonHeight 528 } 529 return nil 530 }