From a725a6a16c53f508681484e36729ebe65a5b33b1 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 22 Sep 2020 16:45:12 -0400 Subject: Add test for ExportTx Verify --- go.sum | 1 + plugin/evm/export_tx.go | 14 ++++- plugin/evm/export_tx_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++ plugin/evm/service.go | 9 +-- plugin/evm/tx.go | 31 ++++++++++ plugin/evm/vm.go | 54 +++++++++------- plugin/evm/vm_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 30 deletions(-) create mode 100644 plugin/evm/export_tx_test.go create mode 100644 plugin/evm/vm_test.go diff --git a/go.sum b/go.sum index a9a34f5..407e8b3 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,7 @@ github.com/ava-labs/avalanchego v0.8.3 h1:ioc7RtSAzIv40qxHDikhqZGVxF3y+8cXeIrLpG github.com/ava-labs/avalanchego v0.8.3/go.mod h1:6zPzQv7m6vSvdKAwH+lLTga0IMd/0+HLMT5OULrpFcU= github.com/ava-labs/coreth v0.2.14-rc.1/go.mod h1:Zhb60GFIB7G5AnUCks0Jo4Rezx/EovL8o+z51aBF1A8= github.com/ava-labs/coreth v0.2.15-rc.4/go.mod h1:+sK2XGKCNA48uzeHWe4iBzmiOBYmYvnnzLtOkQeQfkk= +github.com/ava-labs/gecko v0.6.1-rc.1 h1:BhWmoDGA0Obs5ZbEdpNqw/3rx9ZMPmjcZu1oD+yySLY= github.com/ava-labs/gecko v0.6.1-rc.1/go.mod h1:TT6uA1BETZpVMR0xiFtE8I5Mv4DULlS+lAL3xuYKnpA= github.com/ava-labs/go-ethereum v1.9.3 h1:GmnMZ/dlvVAPFmWBzEpRJX49pUAymPfoASLNRJqR0AY= github.com/ava-labs/go-ethereum v1.9.3/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8= diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index f210352..b76e312 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -56,6 +56,8 @@ func (tx *UnsignedExportTx) Verify( return errWrongChainID case !tx.DestinationChain.Equals(avmID): return errWrongChainID + case len(tx.Ins) == 0: + return errNoExportInputs case len(tx.ExportedOutputs) == 0: return errNoExportOutputs case tx.NetworkID != ctx.NetworkID: @@ -69,6 +71,9 @@ func (tx *UnsignedExportTx) Verify( return err } } + if !IsSortedAndUniqueEVMInputs(tx.Ins) { + return errInputsNotSortedAndUnique + } for _, out := range tx.ExportedOutputs { if err := out.Verify(); err != nil { @@ -160,7 +165,7 @@ func (tx *UnsignedExportTx) Accept(ctx *snow.Context, _ database.Batch) error { return ctx.SharedMemory.Put(tx.DestinationChain, elems) } -// Create a new transaction +// newExportTx returns a new ExportTx func (vm *VM) newExportTx( assetID ids.ID, // AssetID of the tokens to export amount uint64, // Amount of tokens to export @@ -183,14 +188,14 @@ func (vm *VM) newExportTx( toBurn = vm.txFee } // burn AVAX - ins, signers, err := vm.GetSpendableCanonical(keys, vm.ctx.AVAXAssetID, toBurn) + ins, signers, err := vm.GetSpendableFunds(keys, vm.ctx.AVAXAssetID, toBurn) if err != nil { return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) } // burn non-AVAX if !assetID.Equals(vm.ctx.AVAXAssetID) { - ins2, signers2, err := vm.GetSpendableCanonical(keys, assetID, amount) + ins2, signers2, err := vm.GetSpendableFunds(keys, assetID, amount) if err != nil { return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) } @@ -210,6 +215,9 @@ func (vm *VM) newExportTx( }, }} + avax.SortTransferableOutputs(exportOuts, vm.codec) + SortEVMInputsAndSigners(ins, signers) + // Create the transaction utx := &UnsignedExportTx{ NetworkID: vm.ctx.NetworkID, diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go new file mode 100644 index 0000000..2af123f --- /dev/null +++ b/plugin/evm/export_tx_test.go @@ -0,0 +1,144 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +func TestExportTxVerifyNil(t *testing.T) { + var exportTx UnsignedExportTx + if err := exportTx.Verify(testXChainID, NewContext(), testTxFee, testAvaxAssetID); err == nil { + t.Fatal("Verify should have failed due to nil transaction") + } +} + +func TestExportTxVerify(t *testing.T) { + var exportAmount uint64 = 10000000 + exportTx := &UnsignedExportTx{ + NetworkID: testNetworkID, + BlockchainID: testCChainID, + DestinationChain: testXChainID, + Ins: []EVMInput{ + { + Address: testEthAddrs[0], + Amount: exportAmount, + AssetID: testAvaxAssetID, + Nonce: 0, + }, + { + Address: testEthAddrs[2], + Amount: exportAmount, + AssetID: testAvaxAssetID, + Nonce: 0, + }, + }, + ExportedOutputs: []*avax.TransferableOutput{ + { + Asset: avax.Asset{ID: testAvaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: exportAmount - testTxFee, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{testShortIDAddrs[0]}, + }, + }, + }, + { + Asset: avax.Asset{ID: testAvaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: exportAmount, // only subtract fee from one output + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{testShortIDAddrs[1]}, + }, + }, + }, + }, + } + + // Sort the inputs and outputs to ensure the transaction is canonical + avax.SortTransferableOutputs(exportTx.ExportedOutputs, Codec) + // Pass in a list of signers here with the appropriate length + // to avoid causing a nil-pointer error in the helper method + emptySigners := make([][]*crypto.PrivateKeySECP256K1R, 2) + SortEVMInputsAndSigners(exportTx.Ins, emptySigners) + + ctx := NewContext() + + // Test Valid Export Tx + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { + t.Fatalf("Failed to verify valid ExportTx: %w", err) + } + + exportTx.syntacticallyVerified = false + exportTx.NetworkID = testNetworkID + 1 + + // Test Incorrect Network ID Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to incorrect network ID") + } + + exportTx.syntacticallyVerified = false + exportTx.NetworkID = testNetworkID + exportTx.BlockchainID = nonExistentID + // Test Incorrect Blockchain ID Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to incorrect blockchain ID") + } + + exportTx.syntacticallyVerified = false + exportTx.BlockchainID = testCChainID + exportTx.DestinationChain = nonExistentID + // Test Incorrect Destination Chain ID Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to incorrect destination chain") + } + + exportTx.syntacticallyVerified = false + exportTx.DestinationChain = testXChainID + exportedOuts := exportTx.ExportedOutputs + exportTx.ExportedOutputs = nil + // Test No Exported Outputs Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to no exported outputs") + } + + exportTx.syntacticallyVerified = false + exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[1], exportedOuts[0]} + // Test Unsorted outputs Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to no exported outputs") + } + + exportTx.syntacticallyVerified = false + exportTx.ExportedOutputs = exportedOuts + inputs := exportTx.Ins + exportTx.Ins = nil + // Test No Exported Outputs Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to no inputs") + } + + exportTx.syntacticallyVerified = false + exportTx.Ins = []EVMInput{inputs[1], inputs[0]} + // Test unsorted EVM Inputs Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to unsorted inputs") + } + + exportTx.syntacticallyVerified = false + exportTx.Ins = []EVMInput{inputs[0], inputs[0]} + // Test non-unique EVM Inputs Errors + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to non-unique inputs") + } +} diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 8ede1b0..6c56631 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -151,7 +151,7 @@ type ExportKeyReply struct { func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error { log.Info("EVM: ExportKey called") - address, err := service.vm.ParseEthAddress(args.Address) + address, err := ParseEthAddress(args.Address) if err != nil { return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } @@ -200,10 +200,7 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *a sk := skIntf.(*crypto.PrivateKeySECP256K1R) // TODO: return eth address here - reply.Address, err = service.vm.FormatEthAddress(GetEthAddress(sk)) - if err != nil { - return fmt.Errorf("problem formatting address: %w", err) - } + reply.Address = FormatEthAddress(GetEthAddress(sk)) db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) if err != nil { @@ -244,7 +241,7 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api. return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) } - to, err := service.vm.ParseEthAddress(args.To) + to, err := ParseEthAddress(args.To) if err != nil { // Parse address return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err) } diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 9580bc0..fd52222 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -4,14 +4,17 @@ package evm import ( + "bytes" "errors" "fmt" + "sort" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/codec" "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/utils/hashing" @@ -115,3 +118,31 @@ func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) erro tx.Initialize(unsignedBytes, signedBytes) return nil } + +// innerSortInputs implements sort.Interface for EVMInput +type innerSortInputs struct { + inputs []EVMInput + signers [][]*crypto.PrivateKeySECP256K1R +} + +func (ins *innerSortInputs) Less(i, j int) bool { + return bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes()) < 0 +} + +func (ins *innerSortInputs) Len() int { return len(ins.inputs) } + +func (ins *innerSortInputs) Swap(i, j int) { + ins.inputs[j], ins.inputs[i] = ins.inputs[i], ins.inputs[j] + ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j] +} + +// SortEVMInputsAndSigners sorts the list of EVMInputs based solely on the address of the input +func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySECP256K1R) { + sort.Sort(&innerSortInputs{inputs: inputs, signers: signers}) +} + +// IsSortedAndUniqueEVMInputs returns true if the EVM Inputs are sorted and unique +// based on the account addresses +func IsSortedAndUniqueEVMInputs(inputs []EVMInput) bool { + return utils.IsSortedAndUnique(&innerSortInputs{inputs: inputs}) +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 9ad7411..bf43492 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -109,6 +109,8 @@ var ( errInsufficientFunds = errors.New("insufficient funds") errNoExportOutputs = errors.New("no export outputs") errOutputsNotSorted = errors.New("outputs not sorted") + errNoExportInputs = errors.New("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") ) @@ -772,17 +774,6 @@ func (vm *VM) getLastAccepted() *Block { return vm.lastAccepted } -func (vm *VM) ParseEthAddress(addrStr string) (common.Address, error) { - if !common.IsHexAddress(addrStr) { - return common.Address{}, errInvalidAddr - } - return common.HexToAddress(addrStr), nil -} - -func (vm *VM) FormatEthAddress(addr common.Address) (string, error) { - return addr.Hex(), nil -} - // ParseAddress takes in an address and produces the ID of the chain it's for // the ID of the address func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) { @@ -871,16 +862,11 @@ func (vm *VM) GetAtomicUTXOs( return utxos, lastAddrID, lastUTXOID, nil } -func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address { - return PublicKeyToEthAddress(privKey.PublicKey()) -} - -func PublicKeyToEthAddress(pubKey crypto.PublicKey) common.Address { - return ethcrypto.PubkeyToAddress( - (*pubKey.(*crypto.PublicKeySECP256K1R).ToECDSA())) -} - -func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, assetID ids.ID, amount uint64) ([]EVMInput, [][]*crypto.PrivateKeySECP256K1R, error) { +// GetSpendableFunds returns a list of EVMInputs and keys (in corresponding order) +// to total [amount] of [assetID] owned by [keys] +// TODO switch to returning a list of private keys +// since there are no multisig inputs in Ethereum +func (vm *VM) GetSpendableFunds(keys []*crypto.PrivateKeySECP256K1R, assetID ids.ID, amount uint64) ([]EVMInput, [][]*crypto.PrivateKeySECP256K1R, error) { // NOTE: should we use HEAD block or lastAccepted? state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock) if err != nil { @@ -920,9 +906,11 @@ func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, assetID signers = append(signers, []*crypto.PrivateKeySECP256K1R{key}) amount -= balance } + if amount > 0 { return nil, nil, errInsufficientFunds } + return inputs, signers, nil } @@ -933,3 +921,27 @@ func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) { } return state.GetNonce(address), nil } + +// ParseEthAddress parses [addrStr] and returns an Ethereum address +func ParseEthAddress(addrStr string) (common.Address, error) { + if !common.IsHexAddress(addrStr) { + return common.Address{}, errInvalidAddr + } + return common.HexToAddress(addrStr), nil +} + +// FormatEthAddress formats [addr] into a string +func FormatEthAddress(addr common.Address) string { + return addr.Hex() +} + +// GetEthAddress returns the ethereum address derived from [privKey] +func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address { + return PublicKeyToEthAddress(privKey.PublicKey()) +} + +// PublicKeyToEthAddress returns the ethereum address derived from [pubKey] +func PublicKeyToEthAddress(pubKey crypto.PublicKey) common.Address { + return ethcrypto.PubkeyToAddress( + (*pubKey.(*crypto.PublicKeySECP256K1R).ToECDSA())) +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go new file mode 100644 index 0000000..2002c93 --- /dev/null +++ b/plugin/evm/vm_test.go @@ -0,0 +1,141 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "encoding/json" + "testing" + + "github.com/ava-labs/avalanchego/api/keystore" + "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/prefixdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + engCommon "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils/crypto" + "github.com/ava-labs/avalanchego/utils/formatting" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/coreth/core" + "github.com/ethereum/go-ethereum/common" +) + +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'}) + 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 +) + +func init() { + cb58 := formatting.CB58{} + factory := crypto.FactorySECP256K1R{} + + for _, key := range []string{ + "24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5", + "2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY", + "cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35", + } { + _ = cb58.FromString(key) + pk, _ := factory.ToPrivateKey(cb58.Bytes) + secpKey := pk.(*crypto.PrivateKeySECP256K1R) + testKeys = append(testKeys, secpKey) + testEthAddrs = append(testEthAddrs, GetEthAddress(secpKey)) + testShortIDAddrs = append(testShortIDAddrs, pk.PublicKey().Address()) + } +} + +// BuildGenesisTest returns the genesis bytes for Coreth VM to be used in testing +func BuildGenesisTest(t *testing.T) []byte { + ss := StaticService{} + + genesisJSON := "{\"config\":{\"chainId\":43112,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + + genesis := &core.Genesis{} + if err := json.Unmarshal([]byte(genesisJSON), genesis); err != nil { + t.Fatalf("Problem unmarshaling genesis JSON: %w", err) + } + genesisReply, err := ss.BuildGenesis(nil, genesis) + if err != nil { + t.Fatalf("Failed to create test genesis") + } + return genesisReply.Bytes +} + +func NewContext() *snow.Context { + ctx := snow.DefaultContextTest() + ctx.NetworkID = testNetworkID + ctx.ChainID = testCChainID + 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()) + + // SNLookup might be required here??? + return ctx +} + +// GenesisVM creates a VM instance with the genesis test bytes and returns +// the channel use to send messages to the engine, the vm, and atomic memory +func GenesisVM(t *testing.T) (chan engCommon.Message, *VM, *atomic.Memory) { + genesisBytes := BuildGenesisTest(t) + ctx := NewContext() + + baseDB := memdb.New() + + m := &atomic.Memory{} + m.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB)) + ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) + + // NB: this lock is intentionally left locked when this function returns. + // The caller of this function is responsible for unlocking. + ctx.Lock.Lock() + + userKeystore := keystore.CreateTestKeystore() + if err := userKeystore.AddUser(username, password); err != nil { + t.Fatal(err) + } + ctx.Keystore = userKeystore.NewBlockchainKeyStore(ctx.ChainID) + + issuer := make(chan engCommon.Message, 1) + vm := &VM{ + txFee: testTxFee, + } + err := vm.Initialize( + ctx, + prefixdb.New([]byte{1}, baseDB), + genesisBytes, + issuer, + []*engCommon.Fx{}, + ) + if err != nil { + t.Fatal(err) + } + + if err := vm.Bootstrapping(); err != nil { + t.Fatal(err) + } + + if err := vm.Bootstrapped(); err != nil { + t.Fatal(err) + } + + return issuer, vm, m +} + +func TestVMGenesis(t *testing.T) { + _, _, _ = GenesisVM(t) +} -- cgit v1.2.3 From 332dc311e9a21f21f141cfb262c7c23f5ea61ecb Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Wed, 23 Sep 2020 00:38:51 -0400 Subject: Change name of EVMInputs inner sort struct --- plugin/evm/tx.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index fd52222..db49980 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -119,30 +119,30 @@ func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) erro return nil } -// innerSortInputs implements sort.Interface for EVMInput -type innerSortInputs struct { +// innerSortInputsAndSigners implements sort.Interface for EVMInput +type innerSortInputsAndSigners struct { inputs []EVMInput signers [][]*crypto.PrivateKeySECP256K1R } -func (ins *innerSortInputs) Less(i, j int) bool { +func (ins *innerSortInputsAndSigners) Less(i, j int) bool { return bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes()) < 0 } -func (ins *innerSortInputs) Len() int { return len(ins.inputs) } +func (ins *innerSortInputsAndSigners) Len() int { return len(ins.inputs) } -func (ins *innerSortInputs) Swap(i, j int) { +func (ins *innerSortInputsAndSigners) Swap(i, j int) { ins.inputs[j], ins.inputs[i] = ins.inputs[i], ins.inputs[j] ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j] } // SortEVMInputsAndSigners sorts the list of EVMInputs based solely on the address of the input func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySECP256K1R) { - sort.Sort(&innerSortInputs{inputs: inputs, signers: signers}) + sort.Sort(&innerSortInputsAndSigners{inputs: inputs, signers: signers}) } // IsSortedAndUniqueEVMInputs returns true if the EVM Inputs are sorted and unique // based on the account addresses func IsSortedAndUniqueEVMInputs(inputs []EVMInput) bool { - return utils.IsSortedAndUnique(&innerSortInputs{inputs: inputs}) + return utils.IsSortedAndUnique(&innerSortInputsAndSigners{inputs: inputs}) } -- cgit v1.2.3 From 6cc9d3c61ee803f37cbc6e65799797c0dc51e9e5 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Wed, 23 Sep 2020 01:35:28 -0400 Subject: Sort evm outputs of import tx --- go.mod | 2 +- go.sum | 5 +++-- plugin/evm/import_tx.go | 29 ++++++++++++++--------------- plugin/evm/tx.go | 39 +++++++++++++++++++++++++++++++++++++-- plugin/evm/vm.go | 49 +++++++++++++++++++++++++------------------------ 5 files changed, 80 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index adf981a..5d6dfe9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/ava-labs/avalanchego v0.8.3 + github.com/ava-labs/avalanchego v1.0.0 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.7.1 github.com/edsrzf/mmap-go v1.0.0 diff --git a/go.sum b/go.sum index 407e8b3..ed1316b 100644 --- a/go.sum +++ b/go.sum @@ -39,11 +39,12 @@ github.com/aristanetworks/goarista v0.0.0-20200812190859-4cb0e71f3c0e h1:tkEt0le github.com/aristanetworks/goarista v0.0.0-20200812190859-4cb0e71f3c0e/go.mod h1:QZe5Yh80Hp1b6JxQdpfSEEe8X7hTyTEZSosSrFf/oJE= github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= github.com/ava-labs/avalanche-go v0.8.0-beta/go.mod h1:quYojL1hu0ue2glUT1ng28kADs9R94zGdEvfW0/HRg8= -github.com/ava-labs/avalanchego v0.8.3 h1:ioc7RtSAzIv40qxHDikhqZGVxF3y+8cXeIrLpGz9jtc= github.com/ava-labs/avalanchego v0.8.3/go.mod h1:6zPzQv7m6vSvdKAwH+lLTga0IMd/0+HLMT5OULrpFcU= +github.com/ava-labs/avalanchego v1.0.0 h1:xpqiPJS4Gftm6iKrBQJX7tpU8y0QvnyAcznlqOOsriM= +github.com/ava-labs/avalanchego v1.0.0/go.mod h1:0N34atGvJCfJaIjFkHBDG5QzqlSJlsC07/d9irZfA7Y= github.com/ava-labs/coreth v0.2.14-rc.1/go.mod h1:Zhb60GFIB7G5AnUCks0Jo4Rezx/EovL8o+z51aBF1A8= github.com/ava-labs/coreth v0.2.15-rc.4/go.mod h1:+sK2XGKCNA48uzeHWe4iBzmiOBYmYvnnzLtOkQeQfkk= -github.com/ava-labs/gecko v0.6.1-rc.1 h1:BhWmoDGA0Obs5ZbEdpNqw/3rx9ZMPmjcZu1oD+yySLY= +github.com/ava-labs/coreth v0.3.4/go.mod h1:d5h8SoFyMOoFqg2gHzYgftZlKgZ1MHKKydjyq2hoABk= github.com/ava-labs/gecko v0.6.1-rc.1/go.mod h1:TT6uA1BETZpVMR0xiFtE8I5Mv4DULlS+lAL3xuYKnpA= github.com/ava-labs/go-ethereum v1.9.3 h1:GmnMZ/dlvVAPFmWBzEpRJX49pUAymPfoASLNRJqR0AY= github.com/ava-labs/go-ethereum v1.9.3/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8= diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 0b0d348..4d7f219 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -75,6 +75,9 @@ func (tx *UnsignedImportTx) Verify( return err } } + if !IsSortedAndUniqueEVMOutputs(tx.Outs) { + return errExportOutputsNotSortedAndUnique + } for _, in := range tx.ImportedInputs { if err := in.Verify(); err != nil { @@ -127,15 +130,6 @@ func (tx *UnsignedImportTx) SemanticVerify( return tempError{err} } - utxos := make([]*avax.UTXO, len(tx.ImportedInputs)) - for i, utxoBytes := range allUTXOBytes { - utxo := &avax.UTXO{} - if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil { - return tempError{err} - } - utxos[i] = utxo - } - for i, in := range tx.ImportedInputs { utxoBytes := allUTXOBytes[i] @@ -173,7 +167,7 @@ func (tx *UnsignedImportTx) Accept(ctx *snow.Context, _ database.Batch) error { return ctx.SharedMemory.Remove(tx.SourceChain, utxoIDs) } -// Create a new transaction +// newImportTx returns a new ImportTx func (vm *VM) newImportTx( chainID ids.ID, // chain to import from to common.Address, // Address of recipient @@ -240,20 +234,23 @@ func (vm *VM) newImportTx( // }) //} - // non-AVAX asset outputs - for aidKey, amount := range importedAmount { - aid := ids.NewID(aidKey) - //if aid.Equals(vm.ctx.AVAXAssetID) || amount == 0 { + // 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) + //if assetID.Equals(vm.ctx.AVAXAssetID) || amount == 0 { if amount == 0 { continue } outs = append(outs, EVMOutput{ Address: to, Amount: amount, - AssetID: aid, + AssetID: assetID, }) } + SortEVMOutputs(outs) + // Create the transaction utx := &UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, @@ -269,6 +266,8 @@ func (vm *VM) newImportTx( return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) } +// EVMStateTransfer performs the state transfer to increase the balances of +// accounts accordingly with the imported EVMOutputs 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) diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index db49980..7dcebc8 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -126,7 +126,11 @@ type innerSortInputsAndSigners struct { } func (ins *innerSortInputsAndSigners) Less(i, j int) bool { - return bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes()) < 0 + addrComp := bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes()) + if addrComp != 0 { + return addrComp < 0 + } + return bytes.Compare(ins.inputs[i].AssetID.Bytes(), ins.inputs[j].AssetID.Bytes()) < 0 } func (ins *innerSortInputsAndSigners) Len() int { return len(ins.inputs) } @@ -136,7 +140,7 @@ func (ins *innerSortInputsAndSigners) Swap(i, j int) { ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j] } -// SortEVMInputsAndSigners sorts the list of EVMInputs based solely on the address of the input +// SortEVMInputsAndSigners sorts the list of EVMInputs based on the addresses and assetIDs func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySECP256K1R) { sort.Sort(&innerSortInputsAndSigners{inputs: inputs, signers: signers}) } @@ -146,3 +150,34 @@ func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySE func IsSortedAndUniqueEVMInputs(inputs []EVMInput) bool { return utils.IsSortedAndUnique(&innerSortInputsAndSigners{inputs: inputs}) } + +// innerSortEVMOutputs implements sort.Interface for EVMOutput +type innerSortEVMOutputs struct { + outputs []EVMOutput +} + +func (outs *innerSortEVMOutputs) Less(i, j int) bool { + addrComp := bytes.Compare(outs.outputs[i].Address.Bytes(), outs.outputs[j].Address.Bytes()) + if addrComp != 0 { + return addrComp < 0 + } + return bytes.Compare(outs.outputs[i].AssetID.Bytes(), outs.outputs[j].AssetID.Bytes()) < 0 +} + +func (outs *innerSortEVMOutputs) Len() int { return len(outs.outputs) } + +func (outs *innerSortEVMOutputs) Swap(i, j int) { + outs.outputs[j], outs.outputs[i] = outs.outputs[i], outs.outputs[j] +} + +// SortEVMOutputs sorts the list of EVMOutputs based on the addresses and assetIDs +// of the outputs +func SortEVMOutputs(outputs []EVMOutput) { + sort.Sort(&innerSortEVMOutputs{outputs: outputs}) +} + +// IsSortedAndUniqueEVMOutputs returns true if the EVMOutputs are sorted and unique +// based on the account addresses and assetIDs +func IsSortedAndUniqueEVMOutputs(outputs []EVMOutput) bool { + return utils.IsSortedAndUnique(&innerSortEVMOutputs{outputs: outputs}) +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index bf43492..64ae42e 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -89,30 +89,31 @@ const ( var ( txFee = units.MilliAvax - errEmptyBlock = errors.New("empty block") - errCreateBlock = errors.New("couldn't create block") - errUnknownBlock = errors.New("unknown block") - errBlockFrequency = errors.New("too frequent block issuance") - errUnsupportedFXs = errors.New("unsupported feature extensions") - errInvalidBlock = errors.New("invalid block") - 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") - 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("no export outputs") - errOutputsNotSorted = errors.New("outputs not sorted") - errNoExportInputs = errors.New("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") + errEmptyBlock = errors.New("empty block") + errCreateBlock = errors.New("couldn't create block") + errUnknownBlock = errors.New("unknown block") + errBlockFrequency = errors.New("too frequent block issuance") + errUnsupportedFXs = errors.New("unsupported feature extensions") + errInvalidBlock = errors.New("invalid block") + 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") + 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("no export outputs") + errExportOutputsNotSortedAndUnique = errors.New("export outputs are not sorted and unique") + errOutputsNotSorted = errors.New("outputs not sorted") + errNoExportInputs = errors.New("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") ) func maxDuration(x, y time.Duration) time.Duration { -- cgit v1.2.3 From febbf65d34101980d159a1f47b65848183082db8 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Wed, 23 Sep 2020 02:03:43 -0400 Subject: Improve error messages --- plugin/evm/import_tx.go | 2 ++ plugin/evm/vm.go | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 4d7f219..2b8882f 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -64,6 +64,8 @@ func (tx *UnsignedImportTx) Verify( return errWrongChainID case len(tx.ImportedInputs) == 0: return errNoImportInputs + case len(tx.Outs) == 0: + return errNoImportOutputs case tx.NetworkID != ctx.NetworkID: return errWrongNetworkID case !ctx.ChainID.Equals(tx.BlockchainID): diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 64ae42e..3ae3b12 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -107,10 +107,11 @@ var ( errNoFunds = errors.New("no spendable funds were found") errWrongChainID = errors.New("tx has wrong chain ID") errInsufficientFunds = errors.New("insufficient funds") - errNoExportOutputs = errors.New("no export outputs") - errExportOutputsNotSortedAndUnique = errors.New("export outputs are not sorted and unique") - errOutputsNotSorted = errors.New("outputs not sorted") - errNoExportInputs = errors.New("no inputs to export") + errNoExportOutputs = errors.New("tx has no export outputs") + errExportOutputsNotSortedAndUnique = errors.New("tx export outputs are not sorted and unique") + 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") -- cgit v1.2.3 From affb9c79b05ce6610c4b397fc6998df23cf599bd Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Wed, 23 Sep 2020 15:41:05 -0400 Subject: remove requirement for evm inputs and outputs to be sorted and unique --- plugin/evm/export_tx.go | 3 --- plugin/evm/export_tx_test.go | 15 --------------- plugin/evm/import_tx.go | 3 --- 3 files changed, 21 deletions(-) diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index b76e312..5a8494a 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -71,9 +71,6 @@ func (tx *UnsignedExportTx) Verify( return err } } - if !IsSortedAndUniqueEVMInputs(tx.Ins) { - return errInputsNotSortedAndUnique - } for _, out := range tx.ExportedOutputs { if err := out.Verify(); err != nil { diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 2af123f..b17f719 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -121,24 +121,9 @@ func TestExportTxVerify(t *testing.T) { exportTx.syntacticallyVerified = false exportTx.ExportedOutputs = exportedOuts - inputs := exportTx.Ins exportTx.Ins = nil // Test No Exported Outputs Errors if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { t.Fatal("ExportTx should have failed verification due to no inputs") } - - exportTx.syntacticallyVerified = false - exportTx.Ins = []EVMInput{inputs[1], inputs[0]} - // Test unsorted EVM Inputs Errors - if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - t.Fatal("ExportTx should have failed verification due to unsorted inputs") - } - - exportTx.syntacticallyVerified = false - exportTx.Ins = []EVMInput{inputs[0], inputs[0]} - // Test non-unique EVM Inputs Errors - if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - t.Fatal("ExportTx should have failed verification due to non-unique inputs") - } } diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 2b8882f..00e133b 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -77,9 +77,6 @@ func (tx *UnsignedImportTx) Verify( return err } } - if !IsSortedAndUniqueEVMOutputs(tx.Outs) { - return errExportOutputsNotSortedAndUnique - } for _, in := range tx.ImportedInputs { if err := in.Verify(); err != nil { -- cgit v1.2.3 From 100f358d61d60d1196e3a68517c25147b45d965b Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Wed, 23 Sep 2020 15:43:35 -0400 Subject: remove requirement that evm inputs/outputs are non-empty list --- plugin/evm/export_tx.go | 2 -- plugin/evm/export_tx_test.go | 8 -------- plugin/evm/import_tx.go | 2 -- 3 files changed, 12 deletions(-) diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 5a8494a..037f875 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -56,8 +56,6 @@ func (tx *UnsignedExportTx) Verify( return errWrongChainID case !tx.DestinationChain.Equals(avmID): return errWrongChainID - case len(tx.Ins) == 0: - return errNoExportInputs case len(tx.ExportedOutputs) == 0: return errNoExportOutputs case tx.NetworkID != ctx.NetworkID: diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index b17f719..40670ff 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -118,12 +118,4 @@ func TestExportTxVerify(t *testing.T) { if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { t.Fatal("ExportTx should have failed verification due to no exported outputs") } - - exportTx.syntacticallyVerified = false - exportTx.ExportedOutputs = exportedOuts - exportTx.Ins = nil - // Test No Exported Outputs Errors - if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - t.Fatal("ExportTx should have failed verification due to no inputs") - } } diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 00e133b..b848e61 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -64,8 +64,6 @@ func (tx *UnsignedImportTx) Verify( return errWrongChainID case len(tx.ImportedInputs) == 0: return errNoImportInputs - case len(tx.Outs) == 0: - return errNoImportOutputs case tx.NetworkID != ctx.NetworkID: return errWrongNetworkID case !ctx.ChainID.Equals(tx.BlockchainID): -- cgit v1.2.3 From d833a96a315eaf3385316c6cfc89a2c7b5f32c73 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Mon, 5 Oct 2020 16:00:34 -0400 Subject: import tx test wip --- plugin/evm/import_tx_test.go | 126 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 plugin/evm/import_tx_test.go diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go new file mode 100644 index 0000000..b497794 --- /dev/null +++ b/plugin/evm/import_tx_test.go @@ -0,0 +1,126 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +func TestImportTxVerifyNil(t *testing.T) { + var importTx UnsignedImportTx + if err := importTx.Verify(testXChainID, NewContext(), testTxFee, testAvaxAssetID); err == nil { + t.Fatal("Verify should have failed due to nil transaction") + } +} + +func TestImportTxVerify(t *testing.T) { + var importAmount uint64 = 10000000 + txID := ids.NewID([32]byte{0xff}) + importTx := &UnsignedImportTx{ + NetworkID: testNetworkID, + BlockchainID: testCChainID, + SourceChain: testXChainID, + ImportedInputs: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(0), + }, + Asset: avax.Asset{ID: testAvaxAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(1), + }, + Asset: avax.Asset{ID: testAvaxAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + }, + Outs: []EVMOutput{ + { + Address: testEthAddrs[0], + Amount: importAmount, + AssetID: testAvaxAssetID, + }, + { + Address: testEthAddrs[1], + Amount: importAmount, + AssetID: testAvaxAssetID, + }, + }, + } + + ctx := NewContext() + + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { + t.Fatalf("Failed to verify ImportTx: %w", err) + } + // // Sort the inputs and outputs to ensure the transaction is canonical + // avax.SortTransferableOutputs(exportTx.ExportedOutputs, Codec) + // // Pass in a list of signers here with the appropriate length + // // to avoid causing a nil-pointer error in the helper method + // emptySigners := make([][]*crypto.PrivateKeySECP256K1R, 2) + // SortEVMInputsAndSigners(exportTx.Ins, emptySigners) + + // // Test Valid Export Tx + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { + // t.Fatalf("Failed to verify valid ExportTx: %w", err) + // } + + // exportTx.syntacticallyVerified = false + // exportTx.NetworkID = testNetworkID + 1 + + // // Test Incorrect Network ID Errors + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + // t.Fatal("ExportTx should have failed verification due to incorrect network ID") + // } + + // exportTx.syntacticallyVerified = false + // exportTx.NetworkID = testNetworkID + // exportTx.BlockchainID = nonExistentID + // // Test Incorrect Blockchain ID Errors + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + // t.Fatal("ExportTx should have failed verification due to incorrect blockchain ID") + // } + + // exportTx.syntacticallyVerified = false + // exportTx.BlockchainID = testCChainID + // exportTx.DestinationChain = nonExistentID + // // Test Incorrect Destination Chain ID Errors + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + // t.Fatal("ExportTx should have failed verification due to incorrect destination chain") + // } + + // exportTx.syntacticallyVerified = false + // exportTx.DestinationChain = testXChainID + // exportedOuts := exportTx.ExportedOutputs + // exportTx.ExportedOutputs = nil + // // Test No Exported Outputs Errors + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + // t.Fatal("ExportTx should have failed verification due to no exported outputs") + // } + + // exportTx.syntacticallyVerified = false + // exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[1], exportedOuts[0]} + // // Test Unsorted outputs Errors + // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + // t.Fatal("ExportTx should have failed verification due to no exported outputs") + // } +} -- cgit v1.2.3 From b77f95a85f32dab8a111ac4849e3487551e3da12 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Tue, 6 Oct 2020 13:42:19 -0400 Subject: updated avalanchego version to support responseWriter headers --- go.mod | 2 +- go.sum | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index adf981a..df0881b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/ava-labs/avalanchego v0.8.3 + github.com/ava-labs/avalanchego v1.0.2-rc.1 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.7.1 github.com/edsrzf/mmap-go v1.0.0 diff --git a/go.sum b/go.sum index a9a34f5..5f7f212 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,11 @@ github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/ github.com/ava-labs/avalanche-go v0.8.0-beta/go.mod h1:quYojL1hu0ue2glUT1ng28kADs9R94zGdEvfW0/HRg8= github.com/ava-labs/avalanchego v0.8.3 h1:ioc7RtSAzIv40qxHDikhqZGVxF3y+8cXeIrLpGz9jtc= github.com/ava-labs/avalanchego v0.8.3/go.mod h1:6zPzQv7m6vSvdKAwH+lLTga0IMd/0+HLMT5OULrpFcU= +github.com/ava-labs/avalanchego v1.0.2-rc.1 h1:Io4Ok8jXuafnt5MYonWXw/sv9XYw2MzuwXFnJyawfWk= +github.com/ava-labs/avalanchego v1.0.2-rc.1/go.mod h1:JAkAaaj0AlCDSnfFHZwfcEPA2Sl+fOwyLJ6veWMjQWE= github.com/ava-labs/coreth v0.2.14-rc.1/go.mod h1:Zhb60GFIB7G5AnUCks0Jo4Rezx/EovL8o+z51aBF1A8= github.com/ava-labs/coreth v0.2.15-rc.4/go.mod h1:+sK2XGKCNA48uzeHWe4iBzmiOBYmYvnnzLtOkQeQfkk= +github.com/ava-labs/coreth v0.3.4/go.mod h1:d5h8SoFyMOoFqg2gHzYgftZlKgZ1MHKKydjyq2hoABk= github.com/ava-labs/gecko v0.6.1-rc.1/go.mod h1:TT6uA1BETZpVMR0xiFtE8I5Mv4DULlS+lAL3xuYKnpA= github.com/ava-labs/go-ethereum v1.9.3 h1:GmnMZ/dlvVAPFmWBzEpRJX49pUAymPfoASLNRJqR0AY= github.com/ava-labs/go-ethereum v1.9.3/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8= -- cgit v1.2.3 From a5b80ffc01821becffbe4f36d937f778c911af5f Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 6 Oct 2020 16:12:56 -0400 Subject: Add semantic verification tests for import tx --- go.sum | 1 + plugin/evm/export_tx.go | 1 + plugin/evm/export_tx_test.go | 15 ++- plugin/evm/import_tx.go | 5 + plugin/evm/import_tx_test.go | 311 +++++++++++++++++++++++++++++++++++++------ plugin/evm/service.go | 2 +- plugin/evm/tx.go | 4 + plugin/evm/vm.go | 2 + plugin/evm/vm_test.go | 14 +- 9 files changed, 297 insertions(+), 58 deletions(-) diff --git a/go.sum b/go.sum index ed1316b..81c7c0a 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,7 @@ github.com/ava-labs/avalanche-go v0.8.0-beta/go.mod h1:quYojL1hu0ue2glUT1ng28kAD github.com/ava-labs/avalanchego v0.8.3/go.mod h1:6zPzQv7m6vSvdKAwH+lLTga0IMd/0+HLMT5OULrpFcU= github.com/ava-labs/avalanchego v1.0.0 h1:xpqiPJS4Gftm6iKrBQJX7tpU8y0QvnyAcznlqOOsriM= github.com/ava-labs/avalanchego v1.0.0/go.mod h1:0N34atGvJCfJaIjFkHBDG5QzqlSJlsC07/d9irZfA7Y= +github.com/ava-labs/avalanchego v1.0.1 h1:zCJzU+HhmLWcK8uTa91S3MY8Fll2S7bmN2xvLgEhwlA= github.com/ava-labs/coreth v0.2.14-rc.1/go.mod h1:Zhb60GFIB7G5AnUCks0Jo4Rezx/EovL8o+z51aBF1A8= github.com/ava-labs/coreth v0.2.15-rc.4/go.mod h1:+sK2XGKCNA48uzeHWe4iBzmiOBYmYvnnzLtOkQeQfkk= github.com/ava-labs/coreth v0.3.4/go.mod h1:d5h8SoFyMOoFqg2gHzYgftZlKgZ1MHKKydjyq2hoABk= diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 037f875..067252a 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -228,6 +228,7 @@ func (vm *VM) newExportTx( return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) } +// EVMStateTransfer executes the state update from the atomic export transaction func (tx *UnsignedExportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error { addrs := map[[20]byte]uint64{} for _, from := range tx.Ins { diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 40670ff..319c6dd 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -13,7 +13,7 @@ import ( ) func TestExportTxVerifyNil(t *testing.T) { - var exportTx UnsignedExportTx + var exportTx *UnsignedExportTx if err := exportTx.Verify(testXChainID, NewContext(), testTxFee, testAvaxAssetID); err == nil { t.Fatal("Verify should have failed due to nil transaction") } @@ -116,6 +116,17 @@ func TestExportTxVerify(t *testing.T) { exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[1], exportedOuts[0]} // Test Unsorted outputs Errors if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - t.Fatal("ExportTx should have failed verification due to no exported outputs") + t.Fatal("ExportTx should have failed verification due to no unsorted exported outputs") + } + + exportTx.syntacticallyVerified = false + exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[0], nil} + // Test invalid exported output + if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ExportTx should have failed verification due to invalid output") } } + +func TestExportTxSemanticVerify(t *testing.T) { + +} diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index b848e61..ae6b540 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -98,6 +98,10 @@ func (tx *UnsignedImportTx) SemanticVerify( return permError{err} } + if len(stx.Creds) != len(tx.ImportedInputs) { + return permError{errSignatureInputsMismatch} + } + // do flow-checking fc := avax.NewFlowChecker() //fc.Produce(vm.ctx.AVAXAssetID, vm.txFee) @@ -122,6 +126,7 @@ func (tx *UnsignedImportTx) SemanticVerify( for i, in := range tx.ImportedInputs { utxoIDs[i] = in.UTXOID.InputID().Bytes() } + // allUTXOBytes is guaranteed to be the same length as utxoIDs allUTXOBytes, err := vm.ctx.SharedMemory.Get(tx.SourceChain, utxoIDs) if err != nil { return tempError{err} diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go index b497794..fcd18ac 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -6,13 +6,15 @@ package evm import ( "testing" + "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) func TestImportTxVerifyNil(t *testing.T) { - var importTx UnsignedImportTx + var importTx *UnsignedImportTx if err := importTx.Verify(testXChainID, NewContext(), testTxFee, testAvaxAssetID); err == nil { t.Fatal("Verify should have failed due to nil transaction") } @@ -69,58 +71,279 @@ func TestImportTxVerify(t *testing.T) { ctx := NewContext() + // // Sort the inputs and outputs to ensure the transaction is canonical + avax.SortTransferableInputs(importTx.ImportedInputs) + SortEVMOutputs(importTx.Outs) + + // Test Valid ImportTx if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { t.Fatalf("Failed to verify ImportTx: %w", err) } - // // Sort the inputs and outputs to ensure the transaction is canonical - // avax.SortTransferableOutputs(exportTx.ExportedOutputs, Codec) - // // Pass in a list of signers here with the appropriate length - // // to avoid causing a nil-pointer error in the helper method - // emptySigners := make([][]*crypto.PrivateKeySECP256K1R, 2) - // SortEVMInputsAndSigners(exportTx.Ins, emptySigners) - // // Test Valid Export Tx - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { - // t.Fatalf("Failed to verify valid ExportTx: %w", err) - // } - - // exportTx.syntacticallyVerified = false - // exportTx.NetworkID = testNetworkID + 1 + importTx.syntacticallyVerified = false + importTx.NetworkID = testNetworkID + 1 // // Test Incorrect Network ID Errors - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - // t.Fatal("ExportTx should have failed verification due to incorrect network ID") - // } + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect network ID") + } - // exportTx.syntacticallyVerified = false - // exportTx.NetworkID = testNetworkID - // exportTx.BlockchainID = nonExistentID + importTx.syntacticallyVerified = false + importTx.NetworkID = testNetworkID + importTx.BlockchainID = nonExistentID // // Test Incorrect Blockchain ID Errors - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - // t.Fatal("ExportTx should have failed verification due to incorrect blockchain ID") - // } + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect blockchain ID") + } - // exportTx.syntacticallyVerified = false - // exportTx.BlockchainID = testCChainID - // exportTx.DestinationChain = nonExistentID + importTx.syntacticallyVerified = false + importTx.BlockchainID = testCChainID + importTx.SourceChain = nonExistentID // // Test Incorrect Destination Chain ID Errors - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - // t.Fatal("ExportTx should have failed verification due to incorrect destination chain") - // } - - // exportTx.syntacticallyVerified = false - // exportTx.DestinationChain = testXChainID - // exportedOuts := exportTx.ExportedOutputs - // exportTx.ExportedOutputs = nil + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect source chain") + } + + importTx.syntacticallyVerified = false + importTx.SourceChain = testXChainID + importedIns := importTx.ImportedInputs + importTx.ImportedInputs = nil // // Test No Exported Outputs Errors - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - // t.Fatal("ExportTx should have failed verification due to no exported outputs") - // } - - // exportTx.syntacticallyVerified = false - // exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[1], exportedOuts[0]} - // // Test Unsorted outputs Errors - // if err := exportTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { - // t.Fatal("ExportTx should have failed verification due to no exported outputs") - // } + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to no imported inputs") + } + + importTx.syntacticallyVerified = false + importTx.ImportedInputs = []*avax.TransferableInput{importedIns[1], importedIns[0]} + // // Test Unsorted Imported Inputs Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to unsorted import inputs") + } + + importTx.syntacticallyVerified = false + importTx.ImportedInputs = []*avax.TransferableInput{importedIns[0], nil} + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to invalid input") + } +} + +func TestImportTxSemanticVerify(t *testing.T) { + _, vm, _, sharedMemory := GenesisVM(t, false) + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + + importAmount := uint64(1000000) + utxoID := avax.UTXOID{ + TxID: ids.NewID([32]byte{ + 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{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(utxo) + if err != nil { + t.Fatal(err) + } + + evmOutput := EVMOutput{ + Address: testEthAddrs[0], + Amount: importAmount, + AssetID: vm.ctx.AVAXAssetID, + } + unsignedImportTx := &UnsignedImportTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + SourceChain: vm.ctx.XChainID, + ImportedInputs: []*avax.TransferableInput{{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{SigIndices: []uint32{0}}, + }, + }}, + Outs: []EVMOutput{evmOutput}, + } + + state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock) + if err != nil { + t.Fatalf("Failed to get last accepted stateDB due to: %s", err) + } + + if empty := state.Empty(testEthAddrs[0]); !empty { + t.Fatalf("Expected ethereum address to have empty starting balance.") + } + + if err := unsignedImportTx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil { + t.Fatal(err) + } + unsignedImportTx.syntacticallyVerified = false + + tx := &Tx{UnsignedTx: unsignedImportTx} + + // Sign with the correct key + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes without the UTXO being present during bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatal("Should have failed to import non-existent UTXO") + } + + if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{ + Key: utxo.InputID().Bytes(), + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes when the UTXO is present during bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("Semantic verification should have passed during bootstrapping when the UTXO was present") + } + + // Check that SemanticVerify does not pass if an additional output is added in + unsignedImportTx.Outs = append(unsignedImportTx.Outs, EVMOutput{ + Address: testEthAddrs[1], + Amount: importAmount, + AssetID: vm.ctx.AVAXAssetID, + }) + + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatal("Semantic verification should have failed due to insufficient funds") + } + + unsignedImportTx.Outs = []EVMOutput{evmOutput} + + if err := vm.Bootstrapping(); err != nil { + t.Fatal(err) + } + + if err := vm.Bootstrapped(); err != nil { + t.Fatal(err) + } + + vm.ctx.Bootstrapped() + + // Remove the signature + tx.Creds = nil + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("SemanticVerify should have failed due to no signatures") + } + + // Sign with the incorrect key + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[1]}}); err != nil { + t.Fatal(err) + } + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("SemanticVerify should have failed due to an invalid signature") + } + + // Re-sign with the correct key + tx.Creds = nil + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes when the UTXO is present after bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("Semantic verification should have passed after bootstrapping with the UTXO present") + } + + if err := unsignedImportTx.Accept(vm.ctx, nil); err != nil { + t.Fatalf("Accept failed due to: %w", err) + } + + if err := unsignedImportTx.EVMStateTransfer(vm, state); err != nil { + t.Fatalf("EVM State Transfer failed due to: %s", err) + } + + balance := state.GetBalance(testEthAddrs[0]) + if balance == nil { + t.Fatal("Found nil balance for address receiving imported funds") + } else if balance.Uint64() != importAmount*x2cRate.Uint64() { + t.Fatalf("Balance was %d, but expected balance of: %d", balance.Uint64(), importAmount*x2cRate.Uint64()) + } + + // Check that SemanticVerify fails when the UTXO is not present after bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("Semantic verification should have failed after the UTXO removed from shared memory") + } +} + +func TestNewImportTx(t *testing.T) { + _, vm, _, sharedMemory := GenesisVM(t, true) + + importAmount := uint64(1000000) + utxoID := avax.UTXOID{ + TxID: ids.NewID([32]byte{ + 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{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(utxo) + if err != nil { + t.Fatal(err) + } + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + + if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{ + Key: utxo.InputID().Bytes(), + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}); err != nil { + t.Fatal(err) + } + + tx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], []*crypto.PrivateKeySECP256K1R{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + importTx, ok := tx.UnsignedTx.(UnsignedAtomicTx) + if !ok { + t.Fatal("newImportTx did not return an atomic transaction") + } + + if err := importTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("newImportTx created an invalid transaction") + } + + if err := importTx.Accept(vm.ctx, nil); err != nil { + t.Fatalf("Failed to accept import transaction due to: %s", err) + } } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 6c56631..a844f10 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -231,7 +231,7 @@ func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *ImportArgs, response * return service.Import(nil, args, response) } -// ImportAVAX issues a transaction to import AVAX from the X-chain. The AVAX +// Import issues a transaction to import AVAX from the X-chain. The AVAX // must have already been exported from the X-Chain. func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.JsonTxID) error { log.Info("EVM: ImportAVAX called") diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 7dcebc8..7c2ebf1 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -33,12 +33,14 @@ var ( errNilTx = errors.New("tx is nil") ) +// EVMOutput defines an output from EVM State created from export tr