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