aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
authoraaronbuchwald <[email protected]>2020-11-09 16:51:30 -0500
committerGitHub <[email protected]>2020-11-09 16:51:30 -0500
commitae4541f42a666fb5ae1c36d6f7423c3d9eb2c875 (patch)
tree449b53fd8522acc9adb54e93fdc50815f36000d9 /plugin
parentc613e21ea107e8369926f4b53b7830a9d5dabf18 (diff)
parent419cd2d882b93bd98f54957a15c25716fca09123 (diff)
Merge pull request #51 from ava-labs/devv0.3.14
Dev
Diffstat (limited to 'plugin')
-rw-r--r--plugin/evm/block.go4
-rw-r--r--plugin/evm/config.go1
-rw-r--r--plugin/evm/database.go4
-rw-r--r--plugin/evm/export_tx.go24
-rw-r--r--plugin/evm/export_tx_test.go2
-rw-r--r--plugin/evm/factory.go6
-rw-r--r--plugin/evm/import_tx.go26
-rw-r--r--plugin/evm/import_tx_test.go22
-rw-r--r--plugin/evm/service.go178
-rw-r--r--plugin/evm/tx.go10
-rw-r--r--plugin/evm/user.go7
-rw-r--r--plugin/evm/vm.go106
-rw-r--r--plugin/evm/vm_test.go24
-rw-r--r--plugin/params.go1
14 files changed, 295 insertions, 120 deletions
diff --git a/plugin/evm/block.go b/plugin/evm/block.go
index 77a85b3..7c23c17 100644
--- a/plugin/evm/block.go
+++ b/plugin/evm/block.go
@@ -34,7 +34,7 @@ func (b *Block) Accept() error {
log.Trace(fmt.Sprintf("Block %s is accepted", b.ID()))
vm.updateStatus(b.id, choices.Accepted)
- if err := vm.acceptedDB.Put(b.ethBlock.Number().Bytes(), b.id.Bytes()); err != nil {
+ if err := vm.acceptedDB.Put(b.ethBlock.Number().Bytes(), b.id[:]); err != nil {
return err
}
@@ -68,7 +68,7 @@ func (b *Block) Status() choices.Status {
// Parent implements the snowman.Block interface
func (b *Block) Parent() snowman.Block {
- parentID := ids.NewID(b.ethBlock.ParentHash())
+ parentID := ids.ID(b.ethBlock.ParentHash())
if block := b.vm.getBlock(parentID); block != nil {
return block
}
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/database.go b/plugin/evm/database.go
index 18890fa..59a78a9 100644
--- a/plugin/evm/database.go
+++ b/plugin/evm/database.go
@@ -48,12 +48,12 @@ func (db Database) NewBatch() ethdb.Batch { return Batch{db.Database.NewBatch()}
// NewIterator implements ethdb.Database
func (db Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
- return db.NewIteratorWithStartAndPrefix(start, prefix)
+ return db.Database.NewIteratorWithStartAndPrefix(start, prefix)
}
// NewIteratorWithStart implements ethdb.Database
func (db Database) NewIteratorWithStart(start []byte) ethdb.Iterator {
- return db.NewIteratorWithStart(start)
+ return db.Database.NewIteratorWithStart(start)
}
// Batch implements ethdb.Batch
diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go
index d099eb2..ed069d4 100644
--- a/plugin/evm/export_tx.go
+++ b/plugin/evm/export_tx.go
@@ -17,6 +17,7 @@ import (
safemath "github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
@@ -52,15 +53,13 @@ func (tx *UnsignedExportTx) Verify(
return errNilTx
case tx.syntacticallyVerified: // already passed syntactic verification
return nil
- case tx.DestinationChain.IsZero():
- return errWrongChainID
- case !tx.DestinationChain.Equals(avmID):
+ case tx.DestinationChain != avmID:
return errWrongChainID
case len(tx.ExportedOutputs) == 0:
return errNoExportOutputs
case tx.NetworkID != ctx.NetworkID:
return errWrongNetworkID
- case !ctx.ChainID.Equals(tx.BlockchainID):
+ case ctx.ChainID != tx.BlockchainID:
return errWrongBlockchainID
}
@@ -154,9 +153,9 @@ func (tx *UnsignedExportTx) Accept(ctx *snow.Context, _ database.Batch) error {
if err != nil {
return err
}
-
+ utxoID := utxo.InputID()
elem := &atomic.Element{
- Key: utxo.InputID().Bytes(),
+ Key: utxoID[:],
Value: utxoBytes,
}
if out, ok := utxo.Out.(avax.Addressable); ok {
@@ -177,13 +176,13 @@ func (vm *VM) newExportTx(
to ids.ShortID, // Address of chain recipient
keys []*crypto.PrivateKeySECP256K1R, // Pay the fee and provide the tokens
) (*Tx, error) {
- if !vm.ctx.XChainID.Equals(chainID) {
+ if vm.ctx.XChainID != chainID {
return nil, errWrongChainID
}
var toBurn uint64
var err error
- if assetID.Equals(vm.ctx.AVAXAssetID) {
+ if assetID == vm.ctx.AVAXAssetID {
toBurn, err = safemath.Add64(amount, vm.txFee)
if err != nil {
return nil, errOverflowExport
@@ -198,7 +197,7 @@ func (vm *VM) newExportTx(
}
// burn non-AVAX
- if !assetID.Equals(vm.ctx.AVAXAssetID) {
+ if assetID != vm.ctx.AVAXAssetID {
ins2, signers2, err := vm.GetSpendableFunds(keys, assetID, amount)
if err != nil {
return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err)
@@ -242,7 +241,7 @@ func (tx *UnsignedExportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error
addrs := map[[20]byte]uint64{}
for _, from := range tx.Ins {
log.Info("crosschain C->X", "addr", from.Address, "amount", from.Amount)
- if from.AssetID.Equals(vm.ctx.AVAXAssetID) {
+ if from.AssetID == vm.ctx.AVAXAssetID {
amount := new(big.Int).Mul(
new(big.Int).SetUint64(from.Amount), x2cRate)
if state.GetBalance(from.Address).Cmp(amount) < 0 {
@@ -251,11 +250,10 @@ func (tx *UnsignedExportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error
state.SubBalance(from.Address, amount)
} else {
amount := new(big.Int).SetUint64(from.Amount)
- assetID := from.AssetID.Key()
- if state.GetBalanceMultiCoin(from.Address, assetID).Cmp(amount) < 0 {
+ if state.GetBalanceMultiCoin(from.Address, common.Hash(from.AssetID)).Cmp(amount) < 0 {
return errInsufficientFunds
}
- state.SubBalanceMultiCoin(from.Address, assetID, amount)
+ state.SubBalanceMultiCoin(from.Address, common.Hash(from.AssetID), amount)
}
if state.GetNonce(from.Address) != from.Nonce {
return errInvalidNonce
diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go
index 319c6dd..6fdf3a2 100644
--- a/plugin/evm/export_tx_test.go
+++ b/plugin/evm/export_tx_test.go
@@ -76,7 +76,7 @@ func TestExportTxVerify(t *testing.T) {
// Test Valid Export Tx
if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil {
- t.Fatalf("Failed to verify valid ExportTx: %w", err)
+ t.Fatalf("Failed to verify valid ExportTx: %s", err)
}
exportTx.syntacticallyVerified = false
diff --git a/plugin/evm/factory.go b/plugin/evm/factory.go
index 593d85b..daedf39 100644
--- a/plugin/evm/factory.go
+++ b/plugin/evm/factory.go
@@ -3,13 +3,11 @@
package evm
-import (
- "github.com/ava-labs/avalanchego/ids"
-)
+import "github.com/ava-labs/avalanchego/ids"
// ID this VM should be referenced by
var (
- ID = ids.NewID([32]byte{'e', 'v', 'm'})
+ ID = ids.ID{'e', 'v', 'm'}
)
// Factory ...
diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go
index ae6b540..23dbc5f 100644
--- a/plugin/evm/import_tx.go
+++ b/plugin/evm/import_tx.go
@@ -58,15 +58,13 @@ func (tx *UnsignedImportTx) Verify(
return errNilTx
case tx.syntacticallyVerified: // already passed syntactic verification
return nil
- case tx.SourceChain.IsZero():
- return errWrongChainID
- case !tx.SourceChain.Equals(avmID):
+ case tx.SourceChain != avmID:
return errWrongChainID
case len(tx.ImportedInputs) == 0:
return errNoImportInputs
case tx.NetworkID != ctx.NetworkID:
return errWrongNetworkID
- case !ctx.ChainID.Equals(tx.BlockchainID):
+ case ctx.ChainID != tx.BlockchainID:
return errWrongBlockchainID
}
@@ -124,7 +122,8 @@ func (tx *UnsignedImportTx) SemanticVerify(
utxoIDs := make([][]byte, len(tx.ImportedInputs))
for i, in := range tx.ImportedInputs {
- utxoIDs[i] = in.UTXOID.InputID().Bytes()
+ inputID := in.UTXOID.InputID()
+ utxoIDs[i] = inputID[:]
}
// allUTXOBytes is guaranteed to be the same length as utxoIDs
allUTXOBytes, err := vm.ctx.SharedMemory.Get(tx.SourceChain, utxoIDs)
@@ -144,7 +143,7 @@ func (tx *UnsignedImportTx) SemanticVerify(
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
- if !utxoAssetID.Equals(inAssetID) {
+ if utxoAssetID != inAssetID {
return permError{errAssetIDMismatch}
}
@@ -164,7 +163,8 @@ func (tx *UnsignedImportTx) Accept(ctx *snow.Context, _ database.Batch) error {
// TODO: Is any batch passed in here?
utxoIDs := make([][]byte, len(tx.ImportedInputs))
for i, in := range tx.ImportedInputs {
- utxoIDs[i] = in.InputID().Bytes()
+ inputID := in.InputID()
+ utxoIDs[i] = inputID[:]
}
return ctx.SharedMemory.Remove(tx.SourceChain, utxoIDs)
}
@@ -175,7 +175,7 @@ func (vm *VM) newImportTx(
to common.Address, // Address of recipient
keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds
) (*Tx, error) {
- if !vm.ctx.XChainID.Equals(chainID) {
+ if vm.ctx.XChainID != chainID {
return nil, errWrongChainID
}
@@ -204,8 +204,7 @@ func (vm *VM) newImportTx(
continue
}
aid := utxo.AssetID()
- aidKey := aid.Key()
- importedAmount[aidKey], err = math.Add64(importedAmount[aidKey], input.Amount())
+ importedAmount[aid], err = math.Add64(importedAmount[aid], input.Amount())
if err != nil {
return nil, err
}
@@ -238,8 +237,7 @@ func (vm *VM) newImportTx(
// This will create unique outputs (in the context of sorting)
// since each output will have a unique assetID
- for assetKey, amount := range importedAmount {
- assetID := ids.NewID(assetKey)
+ for assetID, amount := range importedAmount {
//if assetID.Equals(vm.ctx.AVAXAssetID) || amount == 0 {
if amount == 0 {
continue
@@ -273,13 +271,13 @@ func (vm *VM) newImportTx(
func (tx *UnsignedImportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error {
for _, to := range tx.Outs {
log.Info("crosschain X->C", "addr", to.Address, "amount", to.Amount)
- if to.AssetID.Equals(vm.ctx.AVAXAssetID) {
+ if to.AssetID == vm.ctx.AVAXAssetID {
amount := new(big.Int).Mul(
new(big.Int).SetUint64(to.Amount), x2cRate)
state.AddBalance(to.Address, amount)
} else {
amount := new(big.Int).SetUint64(to.Amount)
- state.AddBalanceMultiCoin(to.Address, to.AssetID.Key(), amount)
+ state.AddBalanceMultiCoin(to.Address, common.Hash(to.AssetID), amount)
}
}
return nil
diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go
index fcd18ac..53b9494 100644
--- a/plugin/evm/import_tx_test.go
+++ b/plugin/evm/import_tx_test.go
@@ -22,7 +22,7 @@ func TestImportTxVerifyNil(t *testing.T) {
func TestImportTxVerify(t *testing.T) {
var importAmount uint64 = 10000000
- txID := ids.NewID([32]byte{0xff})
+ txID := ids.ID{0xff}
importTx := &UnsignedImportTx{
NetworkID: testNetworkID,
BlockchainID: testCChainID,
@@ -77,7 +77,7 @@ func TestImportTxVerify(t *testing.T) {
// Test Valid ImportTx
if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil {
- t.Fatalf("Failed to verify ImportTx: %w", err)
+ t.Fatalf("Failed to verify ImportTx: %s", err)
}
importTx.syntacticallyVerified = false
@@ -134,12 +134,12 @@ func TestImportTxSemanticVerify(t *testing.T) {
importAmount := uint64(1000000)
utxoID := avax.UTXOID{
- TxID: ids.NewID([32]byte{
+ TxID: ids.ID{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
- }),
+ },
}
utxo := &avax.UTXO{
@@ -203,9 +203,9 @@ func TestImportTxSemanticVerify(t *testing.T) {
if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil {
t.Fatal("Should have failed to import non-existent UTXO")
}
-
+ inputID := utxo.InputID()
if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{
- Key: utxo.InputID().Bytes(),
+ Key: inputID[:],
Value: utxoBytes,
Traits: [][]byte{
testKeys[0].PublicKey().Address().Bytes(),
@@ -268,7 +268,7 @@ func TestImportTxSemanticVerify(t *testing.T) {
}
if err := unsignedImportTx.Accept(vm.ctx, nil); err != nil {
- t.Fatalf("Accept failed due to: %w", err)
+ t.Fatalf("Accept failed due to: %s", err)
}
if err := unsignedImportTx.EVMStateTransfer(vm, state); err != nil {
@@ -293,12 +293,12 @@ func TestNewImportTx(t *testing.T) {
importAmount := uint64(1000000)
utxoID := avax.UTXOID{
- TxID: ids.NewID([32]byte{
+ TxID: ids.ID{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
- }),
+ },
}
utxo := &avax.UTXO{
@@ -318,9 +318,9 @@ func TestNewImportTx(t *testing.T) {
}
xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID)
-
+ inputID := utxo.InputID()
if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{
- Key: utxo.InputID().Bytes(),
+ Key: inputID[:],
Value: utxoBytes,
Traits: [][]byte{
testKeys[0].PublicKey().Address().Bytes(),
diff --git a/plugin/evm/service.go b/plugin/evm/service.go
index a934941..65ef3a2 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-v0.3.14"
)
// 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 {
@@ -244,7 +262,7 @@ type ExportArgs struct {
// It must be imported on the X-Chain to complete the transfer
func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.JSONTxID) error {
log.Info("EVM: Export called")
- if args.AssetID.IsZero() {
+ if args.AssetID == ids.Empty {
return fmt.Errorf("assetID is required")
}
@@ -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 7c2ebf1..82d4c10 100644
--- a/plugin/evm/tx.go
+++ b/plugin/evm/tx.go
@@ -23,10 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common"
)
-// Max size of memo field
-// Don't change without also changing avm.maxMemoSize
-const maxMemoSize = 256
-
var (
errWrongBlockchainID = errors.New("wrong blockchain ID provided")
errWrongNetworkID = errors.New("tx was issued with a different network ID")
@@ -117,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
@@ -134,7 +130,7 @@ func (ins *innerSortInputsAndSigners) Less(i, j int) bool {
if addrComp != 0 {
return addrComp < 0
}
- return bytes.Compare(ins.inputs[i].AssetID.Bytes(), ins.inputs[j].AssetID.Bytes()) < 0
+ return bytes.Compare(ins.inputs[i].AssetID[:], ins.inputs[j].AssetID[:]) < 0
}
func (ins *innerSortInputsAndSigners) Len() int { return len(ins.inputs) }
@@ -165,7 +161,7 @@ func (outs *innerSortEVMOutputs) Less(i, j int) bool {
if addrComp != 0 {
return addrComp < 0
}
- return bytes.Compare(outs.outputs[i].AssetID.Bytes(), outs.outputs[j].AssetID.Bytes()) < 0
+ return bytes.Compare(outs.outputs[i].AssetID[:], outs.outputs[j].AssetID[:]) < 0
}
func (outs *innerSortEVMOutputs) Len() int { return len(outs.outputs) }
diff --git a/plugin/evm/user.go b/plugin/evm/user.go
index b751634..0ab1863 100644
--- a/plugin/evm/user.go
+++ b/plugin/evm/user.go
@@ -15,12 +15,11 @@ import (
// Key in the database whose corresponding value is the list of
// addresses this user controls
-var addressesKey = ids.Empty.Bytes()
+var addressesKey = ids.Empty[:]
var (
- errDBNil = errors.New("db uninitialized")
- errKeyNil = errors.New("key uninitialized")
- errEmptyAddress = errors.New("address is empty")
+ errDBNil = errors.New("db uninitialized")
+ errKeyNil = errors.New("key uninitialized")
)
type user struct {
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index c902f85..284a772 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -4,7 +4,6 @@
package evm
import (
- "bytes"
"crypto/rand"
"encoding/json"
"errors"
@@ -57,10 +56,6 @@ import (
)
var (
- zeroAddr = common.Address{
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- }
x2cRate = big.NewInt(1000000000)
)
@@ -83,10 +78,6 @@ const (
bdTimerStateLong
)
-const (
- addressSep = "-"
-)
-
var (
txFee = units.MilliAvax
@@ -99,21 +90,14 @@ var (
errInvalidAddr = errors.New("invalid hex address")
errTooManyAtomicTx = errors.New("too many pending atomic txs")
errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo")
- errWrongNumberOfCredentials = errors.New("should have the same number of credentials as inputs")
- errNoInputs = errors.New("tx has no inputs")
errNoImportInputs = errors.New("tx has no imported inputs")
errInputsNotSortedUnique = errors.New("inputs not sorted and unique")
errPublicKeySignatureMismatch = errors.New("signature doesn't match public key")
errSignatureInputsMismatch = errors.New("number of inputs does not match number of signatures")
- errUnknownAsset = errors.New("unknown asset ID")
- errNoFunds = errors.New("no spendable funds were found")
errWrongChainID = errors.New("tx has wrong chain ID")
errInsufficientFunds = errors.New("insufficient funds")
errNoExportOutputs = errors.New("tx has no export outputs")
errOutputsNotSorted = errors.New("tx outputs not sorted")
- errNoImportOutputs = errors.New("tx has no outputs to import")
- errNoExportInputs = errors.New("tx has no inputs to export")
- errInputsNotSortedAndUnique = errors.New("inputs not sorted and unique")
errOverflowExport = errors.New("overflow when computing export amount + txFee")
errInvalidNonce = errors.New("invalid nonce")
)
@@ -155,7 +139,8 @@ func init() {
type VM struct {
ctx *snow.Context
- CLIConfig CommandLineConfig
+ CLIConfig CommandLineConfig
+ encodingManager formatting.EncodingManager
chainID *big.Int
networkID uint64
@@ -236,6 +221,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
}
@@ -243,8 +234,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
}
@@ -309,7 +299,7 @@ func (vm *VM) Initialize(
log.Trace("EVM sealed a block")
blk := &Block{
- id: ids.NewID(block.Hash()),
+ id: ids.ID(block.Hash()),
ethBlock: block,
vm: vm,
}
@@ -318,7 +308,7 @@ func (vm *VM) Initialize(
return errInvalidBlock
}
vm.newBlockChan <- blk
- vm.updateStatus(ids.NewID(block.Hash()), choices.Processing)
+ vm.updateStatus(ids.ID(block.Hash()), choices.Processing)
vm.txPoolStabilizedLock.Lock()
vm.txPoolStabilizedHead = block.Hash()
vm.txPoolStabilizedLock.Unlock()
@@ -385,7 +375,7 @@ func (vm *VM) Initialize(
lastAccepted = chain.GetGenesisBlock()
}
vm.lastAccepted = &Block{
- id: ids.NewID(lastAccepted.Hash()),
+ id: ids.ID(lastAccepted.Hash()),
ethBlock: lastAccepted,
vm: vm,
}
@@ -437,7 +427,7 @@ func (vm *VM) BuildBlock() (snowman.Block, error) {
vm.blockDelayTimer.SetTimeoutIn(minBlockTime)
vm.bdlock.Unlock()
- log.Debug(fmt.Sprintf("built block 0x%x", block.ID().Bytes()))
+ log.Debug(fmt.Sprintf("built block %s", block.ID()))
// make sure Tx Pool is updated
<-vm.txPoolStabilizedOk
return block, nil
@@ -457,12 +447,11 @@ func (vm *VM) ParseBlock(b []byte) (snowman.Block, error) {
}
blockHash := ethBlock.Hash()
// Coinbase must be zero on C-Chain
- if bytes.Compare(blockHash.Bytes(), vm.genesisHash.Bytes()) != 0 &&
- bytes.Compare(ethBlock.Coinbase().Bytes(), coreth.BlackholeAddr.Bytes()) != 0 {
+ if blockHash != vm.genesisHash && ethBlock.Coinbase() != coreth.BlackholeAddr {
return nil, errInvalidBlock
}
block := &Block{
- id: ids.NewID(blockHash),
+ id: ids.ID(blockHash),
ethBlock: ethBlock,
vm: vm,
}
@@ -484,7 +473,7 @@ func (vm *VM) GetBlock(id ids.ID) (snowman.Block, error) {
// SetPreference sets what the current tail of the chain is
func (vm *VM) SetPreference(blkID ids.ID) {
- err := vm.chain.SetTail(blkID.Key())
+ err := vm.chain.SetTail(common.Hash(blkID))
vm.ctx.Log.AssertNoError(err)
}
@@ -534,6 +523,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, ", ")))
@@ -634,7 +627,7 @@ func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
if acceptedID, err := ids.ToID(acceptedIDBytes); err != nil {
log.Error(fmt.Sprintf("snowman-eth: acceptedID bytes didn't match expected value: %s", err))
} else {
- if acceptedID.Equals(blockID) {
+ if acceptedID == blockID {
vm.blockStatusCache.Put(blockID, choices.Accepted)
return choices.Accepted
}
@@ -645,14 +638,14 @@ func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
status := vm.getUncachedStatus(blk)
if status == choices.Accepted {
- err := vm.acceptedDB.Put(heightKey, blockID.Bytes())
+ err := vm.acceptedDB.Put(heightKey, blockID[:])
if err != nil {
log.Error(fmt.Sprintf("snowman-eth: failed to write back acceptedID bytes: %s", err))
}
tempBlock := wrappedBlk
for tempBlock.ethBlock != nil {
- parentID := ids.NewID(tempBlock.ethBlock.ParentHash())
+ parentID := ids.ID(tempBlock.ethBlock.ParentHash())
tempBlock = vm.getBlock(parentID)
if tempBlock == nil || tempBlock.ethBlock == nil {
break
@@ -664,7 +657,7 @@ func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
break
}
- if err := vm.acceptedDB.Put(heightKey, parentID.Bytes()); err != nil {
+ if err := vm.acceptedDB.Put(heightKey, parentID[:]); err != nil {
log.Error(fmt.Sprintf("snowman-eth: failed to write back acceptedID bytes: %s", err))
}
}
@@ -685,7 +678,7 @@ func (vm *VM) getUncachedStatus(blk *types.Block) choices.Status {
highBlock, lowBlock = lowBlock, highBlock
}
for highBlock.Number().Cmp(lowBlock.Number()) > 0 {
- parentBlock := vm.getBlock(ids.NewID(highBlock.ParentHash()))
+ parentBlock := vm.getBlock(ids.ID(highBlock.ParentHash()))
if parentBlock == nil {
return choices.Processing
}
@@ -706,12 +699,12 @@ func (vm *VM) getBlock(id ids.ID) *Block {
if blockIntf, ok := vm.blockCache.Get(id); ok {
return blockIntf.(*Block)
}
- ethBlock := vm.chain.GetBlockByHash(id.Key())
+ ethBlock := vm.chain.GetBlockByHash(common.Hash(id))
if ethBlock == nil {
return nil
}
block := &Block{
- id: ids.NewID(ethBlock.Hash()),
+ id: ids.ID(ethBlock.Hash()),
ethBlock: ethBlock,
vm: vm,
}
@@ -719,16 +712,6 @@ func (vm *VM) getBlock(id ids.ID) *Block {
return block
}
-func (vm *VM) issueRemoteTxs(txs []*types.Transaction) error {
- errs := vm.chain.AddRemoteTxs(txs)
- for _, err := range errs {
- if err != nil {
- return err
- }
- }
- return vm.tryBlockGen()
-}
-
func (vm *VM) writeBackMetadata() {
vm.metalock.Lock()
defer vm.metalock.Unlock()
@@ -855,7 +838,7 @@ func (vm *VM) GetAtomicUTXOs(
chainID,
addrsList,
startAddr.Bytes(),
- startUTXOID.Bytes(),
+ startUTXOID[:],
limit,
)
if err != nil {
@@ -902,10 +885,10 @@ func (vm *VM) GetSpendableFunds(keys []*crypto.PrivateKeySECP256K1R, assetID ids
}
addr := GetEthAddress(key)
var balance uint64
- if assetID.Equals(vm.ctx.AVAXAssetID) {
+ if assetID == vm.ctx.AVAXAssetID {
balance = new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64()
} else {
- balance = state.GetBalanceMultiCoin(addr, assetID.Key()).Uint64()
+ balance = state.GetBalanceMultiCoin(addr, common.Hash(assetID)).Uint64()
}
if balance == 0 {
continue
@@ -943,6 +926,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 != 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/evm/vm_test.go b/plugin/evm/vm_test.go
index 8ce7825..020b663 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -23,18 +23,16 @@ import (
var (
testNetworkID uint32 = 10
- testCChainID = ids.NewID([32]byte{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'})
- testXChainID = ids.NewID([32]byte{'t', 'e', 's', 't', 'x'})
- nonExistentID = ids.NewID([32]byte{'F'})
+ testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'}
+ testXChainID = ids.ID{'t', 'e', 's', 't', 'x'}
+ nonExistentID = ids.ID{'F'}
testTxFee = uint64(1000)
- startBalance = uint64(50000)
testKeys []*crypto.PrivateKeySECP256K1R
testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i]
testShortIDAddrs []ids.ShortID
- testAvaxAssetID = ids.NewID([32]byte{1, 2, 3})
- username = "Johns"
- password = "CjasdjhiPeirbSenfeI13" // #nosec G101
- ethChainID uint32 = 43112
+ testAvaxAssetID = ids.ID{1, 2, 3}
+ username = "Johns"
+ password = "CjasdjhiPeirbSenfeI13" // #nosec G101
)
func init() {
@@ -63,7 +61,7 @@ func BuildGenesisTest(t *testing.T) []byte {
genesis := &core.Genesis{}
if err := json.Unmarshal([]byte(genesisJSON), genesis); err != nil {
- t.Fatalf("Problem unmarshaling genesis JSON: %w", err)
+ t.Fatalf("Problem unmarshaling genesis JSON: %s", err)
}
genesisReply, err := ss.BuildGenesis(nil, genesis)
if err != nil {
@@ -79,10 +77,10 @@ func NewContext() *snow.Context {
ctx.AVAXAssetID = testAvaxAssetID
ctx.XChainID = ids.Empty.Prefix(0)
aliaser := ctx.BCLookup.(*ids.Aliaser)
- aliaser.Alias(testCChainID, "C")
- aliaser.Alias(testCChainID, testCChainID.String())
- aliaser.Alias(testXChainID, "X")
- aliaser.Alias(testXChainID, testXChainID.String())
+ _ = aliaser.Alias(testCChainID, "C")
+ _ = aliaser.Alias(testCChainID, testCChainID.String())
+ _ = aliaser.Alias(testXChainID, "X")
+ _ = aliaser.Alias(testXChainID, testXChainID.String())
// SNLookup might be required here???
return ctx
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 {