aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugin')
-rw-r--r--plugin/evm/export_tx.go1
-rw-r--r--plugin/evm/export_tx_test.go15
-rw-r--r--plugin/evm/import_tx.go5
-rw-r--r--plugin/evm/import_tx_test.go311
-rw-r--r--plugin/evm/service.go2
-rw-r--r--plugin/evm/tx.go4
-rw-r--r--plugin/evm/vm.go2
-rw-r--r--plugin/evm/vm_test.go14
8 files changed, 296 insertions, 58 deletions
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 transactions
type EVMOutput struct {
Address common.Address `serialize:"true" json:"address"`
Amount uint64 `serialize:"true" json:"amount"`
AssetID ids.ID `serialize:"true" json:"assetID"`
}
+// EVMInput defines an input for the EVM State to be used in import transactions
type EVMInput struct {
Address common.Address `serialize:"true" json:"address"`
Amount uint64 `serialize:"true" json:"amount"`
@@ -46,10 +48,12 @@ type EVMInput struct {
Nonce uint64 `serialize:"true" json:"nonce"`
}
+// Verify ...
func (out *EVMOutput) Verify() error {
return nil
}
+// Verify ...
func (in *EVMInput) Verify() error {
return nil
}
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index 3ae3b12..4fbfe02 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -103,6 +103,7 @@ var (
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")
@@ -916,6 +917,7 @@ func (vm *VM) GetSpendableFunds(keys []*crypto.PrivateKeySECP256K1R, assetID ids
return inputs, signers, nil
}
+// GetAcceptedNonce returns the nonce associated with the address at the last accepted block
func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) {
state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock)
if err != nil {
diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go
index 2002c93..8ce7825 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -90,7 +90,7 @@ func NewContext() *snow.Context {
// 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) {
+func GenesisVM(t *testing.T, finishBootstrapping bool) (chan engCommon.Message, *VM, []byte, *atomic.Memory) {
genesisBytes := BuildGenesisTest(t)
ctx := NewContext()
@@ -125,17 +125,9 @@ func GenesisVM(t *testing.T) (chan engCommon.Message, *VM, *atomic.Memory) {
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
+ return issuer, vm, genesisBytes, m
}
func TestVMGenesis(t *testing.T) {
- _, _, _ = GenesisVM(t)
+ _, _, _, _ = GenesisVM(t, true)
}