diff options
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/evm/config.go | 1 | ||||
-rw-r--r-- | plugin/evm/service.go | 176 | ||||
-rw-r--r-- | plugin/evm/tx.go | 2 | ||||
-rw-r--r-- | plugin/evm/vm.go | 45 | ||||
-rw-r--r-- | plugin/params.go | 1 |
5 files changed, 220 insertions, 5 deletions
diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 4d6650a..21b3aaf 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -16,6 +16,7 @@ type CommandLineConfig struct { PersonalAPIEnabled bool `json:"personal-api-enabled"` TxPoolAPIEnabled bool `json:"tx-pool-api-enabled"` DebugAPIEnabled bool `json:"debug-api-enabled"` + Web3APIEnabled bool `json:"web3-api-enabled"` ParsingError error } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 0ec7620..c0106ab 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -19,17 +19,26 @@ import ( "github.com/ava-labs/avalanchego/utils/json" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" ) const ( - version = "coreth-v0.3.7" + version = "coreth" ) // test constants const ( GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85" GenesisTestKey = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1" + + // Max number of addresses that can be passed in as argument to GetUTXOs + maxGetUTXOsAddrs = 1024 +) + +var ( + errNoAddresses = errors.New("no addresses provided") + errNoSourceChain = errors.New("no source chain provided") ) // SnowmanAPI introduces snowman specific functionality to the evm @@ -50,6 +59,15 @@ func (s *NetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(0) } // TODO: re // Version returns the current ethereum protocol version. func (s *NetAPI) Version() string { return fmt.Sprintf("%d", s.vm.networkID) } +// Web3API offers helper API methods +type Web3API struct{} + +// ClientVersion returns the version of the vm running +func (s *Web3API) ClientVersion() string { return version } + +// Sha3 returns the bytes returned by hashing [input] with Keccak256 +func (s *Web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return ethcrypto.Keccak256(input) } + // GetAcceptedFrontReply defines the reply that will be sent from the // GetAcceptedFront API call type GetAcceptedFrontReply struct { @@ -285,3 +303,159 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api. response.TxID = tx.ID() return service.vm.issueTx(tx) } + +// Index is an address and an associated UTXO. +// Marks a starting or stopping point when fetching UTXOs. Used for pagination. +type Index struct { + Address string `json:"address"` // The address as a string + UTXO string `json:"utxo"` // The UTXO ID as a string +} + +// GetUTXOsArgs are arguments for passing into GetUTXOs. +// Gets the UTXOs that reference at least one address in [Addresses]. +// Returns at most [limit] addresses. +// If specified, [SourceChain] is the chain where the atomic UTXOs were exported from. If not specified, +// then GetUTXOs returns an error since the C Chain only has atomic UTXOs. +// If [limit] == 0 or > [maxUTXOsToFetch], fetches up to [maxUTXOsToFetch]. +// [StartIndex] defines where to start fetching UTXOs (for pagination.) +// UTXOs fetched are from addresses equal to or greater than [StartIndex.Address] +// For address [StartIndex.Address], only UTXOs with IDs greater than [StartIndex.UTXO] will be returned. +// If [StartIndex] is omitted, gets all UTXOs. +// If GetUTXOs is called multiple times, with our without [StartIndex], it is not guaranteed +// that returned UTXOs are unique. That is, the same UTXO may appear in the response of multiple calls. +type GetUTXOsArgs struct { + Addresses []string `json:"addresses"` + SourceChain string `json:"sourceChain"` + Limit json.Uint32 `json:"limit"` + StartIndex Index `json:"startIndex"` + Encoding string `json:"encoding"` +} + +// GetUTXOsReply defines the GetUTXOs replies returned from the API +type GetUTXOsReply struct { + // Number of UTXOs returned + NumFetched json.Uint64 `json:"numFetched"` + // The UTXOs + UTXOs []string `json:"utxos"` + // The last UTXO that was returned, and the address it corresponds to. + // Used for pagination. To get the rest of the UTXOs, call GetUTXOs + // again and set [StartIndex] to this value. + EndIndex Index `json:"endIndex"` + // Encoding specifies the encoding format the UTXOs are returned in + Encoding string `json:"encoding"` +} + +// GetUTXOs gets all utxos for passed in addresses +func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *GetUTXOsReply) error { + service.vm.ctx.Log.Info("EVM: GetUTXOs called for with %s", args.Addresses) + + if len(args.Addresses) == 0 { + return errNoAddresses + } + if len(args.Addresses) > maxGetUTXOsAddrs { + return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs) + } + + encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding) + if err != nil { + return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err) + } + + sourceChain := ids.ID{} + if args.SourceChain == "" { + return errNoSourceChain + } + + chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) + if err != nil { + return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err) + } + sourceChain = chainID + + addrSet := ids.ShortSet{} + for _, addrStr := range args.Addresses { + addr, err := service.vm.ParseLocalAddress(addrStr) + if err != nil { + return fmt.Errorf("couldn't parse address %q: %w", addrStr, err) + } + addrSet.Add(addr) + } + + startAddr := ids.ShortEmpty + startUTXO := ids.Empty + if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" { + startAddr, err = service.vm.ParseLocalAddress(args.StartIndex.Address) + if err != nil { + return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err) + } + startUTXO, err = ids.FromString(args.StartIndex.UTXO) + if err != nil { + return fmt.Errorf("couldn't parse start index utxo: %w", err) + } + } + + utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs( + sourceChain, + addrSet, + startAddr, + startUTXO, + int(args.Limit), + ) + if err != nil { + return fmt.Errorf("problem retrieving UTXOs: %w", err) + } + + reply.UTXOs = make([]string, len(utxos)) + for i, utxo := range utxos { + b, err := service.vm.codec.Marshal(utxo) + if err != nil { + return fmt.Errorf("problem marshalling UTXO: %w", err) + } + reply.UTXOs[i] = encoding.ConvertBytes(b) + } + + endAddress, err := service.vm.FormatLocalAddress(endAddr) + if err != nil { + return fmt.Errorf("problem formatting address: %w", err) + } + + reply.EndIndex.Address = endAddress + reply.EndIndex.UTXO = endUTXOID.String() + reply.NumFetched = json.Uint64(len(utxos)) + reply.Encoding = encoding.Encoding() + return nil +} + +// IssueTx ... +func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error { + log.Info("EVM: IssueTx called") + + encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding) + if err != nil { + return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err) + } + txBytes, err := encoding.ConvertString(args.Tx) + if err != nil { + return fmt.Errorf("problem decoding transaction: %w", err) + } + + tx := &Tx{} + if err := service.vm.codec.Unmarshal(txBytes, tx); err != nil { + return fmt.Errorf("problem parsing transaction: %w", err) + } + if err := tx.Sign(service.vm.codec, nil); err != nil { + return fmt.Errorf("problem initializing transaction: %w", err) + } + + utx, ok := tx.UnsignedTx.(UnsignedAtomicTx) + if !ok { + return errors.New("cannot issue non-atomic transaction through IssueTx API") + } + + if err := utx.SemanticVerify(service.vm, tx); err != nil { + return err + } + + response.TxID = tx.ID() + return service.vm.issueTx(tx) +} diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index e44eefe..82d4c10 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -113,7 +113,7 @@ func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) erro signedBytes, err := c.Marshal(tx) if err != nil { - return fmt.Errorf("couldn't marshal ProposalTx: %w", err) + return fmt.Errorf("couldn't marshal Tx: %w", err) } tx.Initialize(unsignedBytes, signedBytes) return nil diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 53dc349..c7c7ca0 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -140,7 +140,8 @@ func init() { type VM struct { ctx *snow.Context - CLIConfig CommandLineConfig + CLIConfig CommandLineConfig + encodingManager formatting.EncodingManager chainID *big.Int networkID uint64 @@ -221,6 +222,12 @@ func (vm *VM) Initialize( return vm.CLIConfig.ParsingError } + encodingManager, err := formatting.NewEncodingManager(formatting.CB58Encoding) + if err != nil { + return fmt.Errorf("problem creating encoding manager: %w", err) + } + vm.encodingManager = encodingManager + if len(fxs) > 0 { return errUnsupportedFXs } @@ -228,8 +235,7 @@ func (vm *VM) Initialize( vm.ctx = ctx vm.chaindb = Database{db} g := new(core.Genesis) - err := json.Unmarshal(b, g) - if err != nil { + if err := json.Unmarshal(b, g); err != nil { return err } @@ -519,6 +525,10 @@ func (vm *VM) CreateHandlers() map[string]*commonEng.HTTPHandler { handler.RegisterName("net", &NetAPI{vm}) enabledAPIs = append(enabledAPIs, "net") } + if vm.CLIConfig.Web3APIEnabled { + handler.RegisterName("web3", &Web3API{}) + enabledAPIs = append(enabledAPIs, "web3") + } log.Info(fmt.Sprintf("Enabled APIs: %s", strings.Join(enabledAPIs, ", "))) @@ -918,6 +928,35 @@ func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) { return state.GetNonce(address), nil } +// ParseLocalAddress takes in an address for this chain and produces the ID +func (vm *VM) ParseLocalAddress(addrStr string) (ids.ShortID, error) { + chainID, addr, err := vm.ParseAddress(addrStr) + if err != nil { + return ids.ShortID{}, err + } + if !chainID.Equals(vm.ctx.ChainID) { + return ids.ShortID{}, fmt.Errorf("expected chainID to be %q but was %q", + vm.ctx.ChainID, chainID) + } + return addr, nil +} + +// FormatLocalAddress takes in a raw address and produces the formatted address +func (vm *VM) FormatLocalAddress(addr ids.ShortID) (string, error) { + return vm.FormatAddress(vm.ctx.ChainID, addr) +} + +// FormatAddress takes in a chainID and a raw address and produces the formatted +// address +func (vm *VM) FormatAddress(chainID ids.ID, addr ids.ShortID) (string, error) { + chainIDAlias, err := vm.ctx.BCLookup.PrimaryAlias(chainID) + if err != nil { + return "", err + } + hrp := constants.GetHRP(vm.ctx.NetworkID) + return formatting.FormatAddress(chainIDAlias, hrp, addr.Bytes()) +} + // ParseEthAddress parses [addrStr] and returns an Ethereum address func ParseEthAddress(addrStr string) (common.Address, error) { if !common.IsHexAddress(addrStr) { diff --git a/plugin/params.go b/plugin/params.go index 6615798..a130d52 100644 --- a/plugin/params.go +++ b/plugin/params.go @@ -27,6 +27,7 @@ func init() { cliConfig.PersonalAPIEnabled = true cliConfig.TxPoolAPIEnabled = true cliConfig.NetAPIEnabled = true + cliConfig.Web3APIEnabled = true cliConfig.RPCGasCap = 2500000000 // 25000000 x 100 cliConfig.RPCTxFeeCap = 100 // 100 AVAX } else { |