From f3eb89cbe4611c9372ece05eaf5cb7a609954b8e Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 23:34:33 -0400 Subject: ... --- plugin/evm/export_tx.go | 155 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 plugin/evm/export_tx.go (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go new file mode 100644 index 0000000..3a544a6 --- /dev/null +++ b/plugin/evm/export_tx.go @@ -0,0 +1,155 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "errors" + "fmt" + + "github.com/ava-labs/gecko/database" + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/snow" + "github.com/ava-labs/gecko/utils/crypto" + "github.com/ava-labs/gecko/vms/components/avax" + "github.com/ava-labs/gecko/vms/secp256k1fx" + + safemath "github.com/ava-labs/gecko/utils/math" +) + +var ( + errNoExportOutputs = errors.New("no export outputs") + errOutputsNotSorted = errors.New("outputs not sorted") + errOverflowExport = errors.New("overflow when computing export amount + txFee") + + _ UnsignedAtomicTx = &UnsignedExportTx{} +) + +// UnsignedExportTx is an unsigned ExportTx +type UnsignedExportTx struct { + avax.Metadata + // true iff this transaction has already passed syntactic verification + syntacticallyVerified bool + // ID of the network on which this tx was issued + NetworkID uint32 `serialize:"true" json:"networkID"` + // ID of this blockchain. + BlockchainID ids.ID `serialize:"true" json:"blockchainID"` + // Which chain to send the funds to + DestinationChain ids.ID `serialize:"true" json:"destinationChain"` + // Inputs + Ins []EVMInput `serialize:"true" json:"inputs"` + // Outputs that are exported to the chain + ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"` +} + +// InputUTXOs returns an empty set +func (tx *UnsignedExportTx) InputUTXOs() ids.Set { return ids.Set{} } + +// Verify this transaction is well-formed +func (tx *UnsignedExportTx) Verify( + avmID ids.ID, + ctx *snow.Context, + feeAmount uint64, + feeAssetID ids.ID, +) error { + switch { + case tx == nil: + return errNilTx + case tx.syntacticallyVerified: // already passed syntactic verification + return nil + case tx.DestinationChain.IsZero(): + return errWrongChainID + case !tx.DestinationChain.Equals(avmID): + return errWrongChainID + case len(tx.ExportedOutputs) == 0: + return errNoExportOutputs + case tx.NetworkID != ctx.NetworkID: + return errWrongNetworkID + case !ctx.ChainID.Equals(tx.BlockchainID): + return errWrongBlockchainID + } + + for _, in := range tx.Ins { + if err := in.Verify(); err != nil { + return err + } + } + + for _, out := range tx.ExportedOutputs { + if err := out.Verify(); err != nil { + return err + } + } + if !avax.IsSortedTransferableOutputs(tx.ExportedOutputs, Codec) { + return errOutputsNotSorted + } + + tx.syntacticallyVerified = true + return nil +} + +// SemanticVerify this transaction is valid. +func (tx *UnsignedExportTx) SemanticVerify( + vm *VM, + stx *Tx, +) TxError { + if err := tx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil { + return permError{err} + } + + // TODO: credential check + // TODO: flow check + // TODO: verify UTXO outputs via gRPC + return nil +} + +// Accept this transaction. +func (tx *UnsignedExportTx) Accept(ctx *snow.Context, batch database.Batch) error { + // TODO: finish this function via gRPC + return nil +} + +// Create a new transaction +func (vm *VM) newExportTx( + amount uint64, // Amount of tokens to export + chainID ids.ID, // Chain to send the UTXOs to + 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) { + return nil, errWrongChainID + } + + toBurn, err := safemath.Add64(amount, vm.txFee) + if err != nil { + return nil, errOverflowExport + } + ins, signers, err := vm.GetSpendableCanonical(keys, toBurn) + if err != nil { + return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) + } + + // Create the transaction + utx := &UnsignedExportTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + DestinationChain: chainID, + Ins: ins, + ExportedOutputs: []*avax.TransferableOutput{{ // Exported to X-Chain + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: amount, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{to}, + }, + }, + }}, + } + tx := &Tx{UnsignedTx: utx} + if err := tx.Sign(vm.codec, signers); err != nil { + return nil, err + } + return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) +} -- cgit v1.2.3-70-g09d2 From c6fbdb0dc9453bf4dbf43490d7d83b7a4de2f182 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 23:42:43 -0400 Subject: clean up code --- plugin/evm/export_tx.go | 2 -- plugin/evm/import_tx.go | 14 +++++++++++++- plugin/evm/tx.go | 4 ++++ plugin/evm/vm.go | 14 ++------------ 4 files changed, 19 insertions(+), 15 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 3a544a6..0314f0c 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -21,8 +21,6 @@ var ( errNoExportOutputs = errors.New("no export outputs") errOutputsNotSorted = errors.New("outputs not sorted") errOverflowExport = errors.New("overflow when computing export amount + txFee") - - _ UnsignedAtomicTx = &UnsignedExportTx{} ) // UnsignedExportTx is an unsigned ExportTx diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 68f34b0..d6e03dc 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -6,6 +6,9 @@ package evm import ( "errors" "fmt" + "math/big" + + "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" @@ -134,7 +137,7 @@ func (tx *UnsignedImportTx) SemanticVerify( // we don't want to remove an imported UTXO in semanticVerify // only to have the transaction not be Accepted. This would be inconsistent. // Recall that imported UTXOs are not kept in a versionDB. -func (tx *UnsignedImportTx) Accept(batch database.Batch) error { +func (tx *UnsignedImportTx) Accept(ctx *snow.Context, batch database.Batch) error { // TODO: finish this function via gRPC return nil } @@ -218,3 +221,12 @@ func (vm *VM) newImportTx( } return tx, utx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID) } + +func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) { + for _, to := range tx.Outs { + amount := new(big.Int).SetUint64(to.Amount) + state.AddBalance(to.Address, new(big.Int).Mul(amount, x2cRate)) + nonce := state.GetNonce(to.Address) + state.SetNonce(to.Address, nonce+1) + } +} diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 90cd232..dfd49f0 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -7,6 +7,8 @@ import ( "errors" "fmt" + "github.com/ava-labs/coreth/core/state" + "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" @@ -62,6 +64,8 @@ type UnsignedAtomicTx interface { // Accept this transaction with the additionally provided state transitions. Accept(ctx *snow.Context, batch database.Batch) error + + EVMStateTransfer(state *state.StateDB) } // Tx is a signed transaction diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 5bb8ac0..292e383 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -163,15 +163,6 @@ func (vm *VM) getAtomicTx(block *types.Block) *Tx { return atx } -func importTxStateTransfer(tx *UnsignedImportTx, state *state.StateDB) { - for _, to := range tx.Outs { - amount := new(big.Int).SetUint64(to.Amount) - state.AddBalance(to.Address, new(big.Int).Mul(amount, x2cRate)) - nonce := state.GetNonce(to.Address) - state.SetNonce(to.Address, nonce+1) - } -} - /* ****************************************************************************** ********************************* Snowman API ******************************** @@ -225,7 +216,7 @@ func (vm *VM) Initialize( chain.SetOnFinalizeAndAssemble(func(state *state.StateDB, txs []*types.Transaction) ([]byte, error) { select { case atx := <-vm.pendingAtomicTxs: - importTxStateTransfer(atx.UnsignedTx.(*UnsignedImportTx), state) + atx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) raw, _ := vm.codec.Marshal(atx) return raw, nil default: @@ -259,8 +250,7 @@ func (vm *VM) Initialize( return vm.getLastAccepted().ethBlock }) chain.SetOnExtraStateChange(func(block *types.Block, state *state.StateDB) error { - atx := vm.getAtomicTx(block).UnsignedTx.(*UnsignedImportTx) - importTxStateTransfer(atx, state) + vm.getAtomicTx(block).UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) return nil }) vm.blockCache = cache.LRU{Size: 2048} -- cgit v1.2.3-70-g09d2 From 2d409cb9790e55fd014546222f448786bbefa46b Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 23:48:47 -0400 Subject: ... --- plugin/evm/export_tx.go | 7 ------- plugin/evm/factory.go | 8 ++------ plugin/evm/import_tx.go | 26 ++++++-------------------- plugin/evm/vm.go | 33 +++++++++++++++++++++------------ 4 files changed, 29 insertions(+), 45 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 0314f0c..249b7cf 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -4,7 +4,6 @@ package evm import ( - "errors" "fmt" "github.com/ava-labs/gecko/database" @@ -17,12 +16,6 @@ import ( safemath "github.com/ava-labs/gecko/utils/math" ) -var ( - errNoExportOutputs = errors.New("no export outputs") - errOutputsNotSorted = errors.New("outputs not sorted") - errOverflowExport = errors.New("overflow when computing export amount + txFee") -) - // UnsignedExportTx is an unsigned ExportTx type UnsignedExportTx struct { avax.Metadata diff --git a/plugin/evm/factory.go b/plugin/evm/factory.go index ae2ea27..6ae62ae 100644 --- a/plugin/evm/factory.go +++ b/plugin/evm/factory.go @@ -14,16 +14,12 @@ var ( // Factory ... type Factory struct { - AVAX ids.ID - AVM ids.ID - Fee uint64 + Fee uint64 } // New ... func (f *Factory) New() interface{} { return &VM{ - avaxAssetID: f.AVAX, - avm: f.AVM, - txFee: f.Fee, + txFee: f.Fee, } } diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index d6e03dc..995e488 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -4,7 +4,6 @@ package evm import ( - "errors" "fmt" "math/big" @@ -13,26 +12,13 @@ import ( "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" - crypto "github.com/ava-labs/gecko/utils/crypto" + "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/vms/components/avax" "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/go-ethereum/common" ) -var ( - 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") -) - // UnsignedImportTx is an unsigned ImportTx type UnsignedImportTx struct { avax.Metadata @@ -109,16 +95,16 @@ func (tx *UnsignedImportTx) SemanticVerify( vm *VM, stx *Tx, ) TxError { - if err := tx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID); err != nil { + if err := tx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil { return permError{err} } // do flow-checking fc := avax.NewFlowChecker() - fc.Produce(vm.avaxAssetID, vm.txFee) + fc.Produce(vm.ctx.AVAXAssetID, vm.txFee) for _, out := range tx.Outs { - fc.Produce(vm.avaxAssetID, out.Amount) + fc.Produce(vm.ctx.AVAXAssetID, out.Amount) } for _, in := range tx.ImportedInputs { @@ -168,7 +154,7 @@ func (vm *VM) newImportTx( importedAmount := uint64(0) now := vm.clock.Unix() for _, utxo := range atomicUTXOs { - if !utxo.AssetID().Equals(vm.avaxAssetID) { + if !utxo.AssetID().Equals(vm.ctx.AVAXAssetID) { continue } inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now) @@ -219,7 +205,7 @@ func (vm *VM) newImportTx( if err := tx.Sign(vm.codec, signers); err != nil { return nil, err } - return tx, utx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID) + return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) } func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 292e383..53810ee 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -74,14 +74,27 @@ const ( ) var ( - 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 atomix txs") + 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 atomix 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") + errOverflowExport = errors.New("overflow when computing export amount + txFee") ) func maxDuration(x, y time.Duration) time.Duration { @@ -146,8 +159,6 @@ type VM struct { atomicTxSubmitChan chan struct{} codec codec.Codec clock timer.Clock - avaxAssetID ids.ID - avm ids.ID txFee uint64 pendingAtomicTxs chan *Tx blockAtomicInputCache cache.LRU @@ -182,8 +193,6 @@ func (vm *VM) Initialize( } vm.ctx = ctx - vm.avaxAssetID = ctx.AVAXAssetID - vm.avm = ctx.XChainID vm.chaindb = Database{db} g := new(core.Genesis) err := json.Unmarshal(b, g) -- cgit v1.2.3-70-g09d2 From 0c417aaa02c5cc8bb6a9629ac2502e0c4d200071 Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 20 Aug 2020 00:03:12 -0400 Subject: impl state transfer for C-to-X Tx --- plugin/evm/export_tx.go | 24 ++++++++++++++++++++++-- plugin/evm/import_tx.go | 23 ++++++++++++++++------- plugin/evm/tx.go | 2 +- plugin/evm/vm.go | 9 +++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 249b7cf..9af11e0 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -5,15 +5,17 @@ package evm import ( "fmt" + "math/big" + + "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/utils/crypto" + safemath "github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/vms/components/avax" "github.com/ava-labs/gecko/vms/secp256k1fx" - - safemath "github.com/ava-labs/gecko/utils/math" ) // UnsignedExportTx is an unsigned ExportTx @@ -31,6 +33,8 @@ type UnsignedExportTx struct { Ins []EVMInput `serialize:"true" json:"inputs"` // Outputs that are exported to the chain ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"` + // EVM nonce + nonce uint64 } // InputUTXOs returns an empty set @@ -144,3 +148,19 @@ func (vm *VM) newExportTx( } return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) } + +func (tx *UnsignedExportTx) EVMStateTransfer(state *state.StateDB) error { + for _, from := range tx.Ins { + amount := new(big.Int).Mul( + new(big.Int).SetUint64(from.Amount), x2cRate) + if state.GetBalance(from.Address).Cmp(amount) < 0 { + return errInsufficientFunds + } + state.SubBalance(from.Address, amount) + if state.GetNonce(from.Address) != tx.nonce { + return errInvalidNonce + } + state.SetNonce(from.Address, tx.nonce+1) + } + return nil +} diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 995e488..68e5fed 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -34,8 +34,8 @@ type UnsignedImportTx struct { ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"` // Outputs Outs []EVMOutput `serialize:"true" json:"outputs"` - // Memo field contains arbitrary bytes, up to maxMemoSize - Memo []byte `serialize:"true" json:"memo"` + // EVM nonce + nonce uint64 } // InputUTXOs returns the UTXOIDs of the imported funds @@ -193,6 +193,10 @@ func (vm *VM) newImportTx( }) } + nonce, err := vm.GetAcceptedNonce(to) + if err != nil { + return nil, err + } // Create the transaction utx := &UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, @@ -200,6 +204,7 @@ func (vm *VM) newImportTx( Outs: outs, ImportedInputs: importedInputs, SourceChain: chainID, + nonce: nonce, } tx := &Tx{UnsignedTx: utx} if err := tx.Sign(vm.codec, signers); err != nil { @@ -208,11 +213,15 @@ func (vm *VM) newImportTx( return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID) } -func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) { +func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) error { for _, to := range tx.Outs { - amount := new(big.Int).SetUint64(to.Amount) - state.AddBalance(to.Address, new(big.Int).Mul(amount, x2cRate)) - nonce := state.GetNonce(to.Address) - state.SetNonce(to.Address, nonce+1) + state.AddBalance(to.Address, + new(big.Int).Mul( + new(big.Int).SetUint64(to.Amount), x2cRate)) + if state.GetNonce(to.Address) != tx.nonce { + return errInvalidNonce + } + state.SetNonce(to.Address, tx.nonce+1) } + return nil } diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index dfd49f0..7e60c79 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -65,7 +65,7 @@ type UnsignedAtomicTx interface { // Accept this transaction with the additionally provided state transitions. Accept(ctx *snow.Context, batch database.Batch) error - EVMStateTransfer(state *state.StateDB) + EVMStateTransfer(state *state.StateDB) error } // Tx is a signed transaction diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 53810ee..6155728 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -95,6 +95,7 @@ var ( errNoExportOutputs = errors.New("no export outputs") errOutputsNotSorted = errors.New("outputs not sorted") errOverflowExport = errors.New("overflow when computing export amount + txFee") + errInvalidNonce = errors.New("invalid nonce") ) func maxDuration(x, y time.Duration) time.Duration { @@ -721,3 +722,11 @@ func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount } return inputs, signers, nil } + +func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) { + state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock) + if err != nil { + return 0, err + } + return state.GetNonce(address), nil +} -- cgit v1.2.3-70-g09d2 From eeb62be039927d461bcd5bebc456e3ab1a31307c Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 20 Aug 2020 00:27:32 -0400 Subject: move nonce to EVMOutput/Input --- plugin/evm/export_tx.go | 6 ++---- plugin/evm/import_tx.go | 17 ++++++++--------- plugin/evm/tx.go | 1 + plugin/evm/vm.go | 5 +++++ 4 files changed, 16 insertions(+), 13 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 9af11e0..423c754 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -33,8 +33,6 @@ type UnsignedExportTx struct { Ins []EVMInput `serialize:"true" json:"inputs"` // Outputs that are exported to the chain ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"` - // EVM nonce - nonce uint64 } // InputUTXOs returns an empty set @@ -157,10 +155,10 @@ func (tx *UnsignedExportTx) EVMStateTransfer(state *state.StateDB) error { return errInsufficientFunds } state.SubBalance(from.Address, amount) - if state.GetNonce(from.Address) != tx.nonce { + if state.GetNonce(from.Address) != from.Nonce { return errInvalidNonce } - state.SetNonce(from.Address, tx.nonce+1) + state.SetNonce(from.Address, from.Nonce+1) } return nil } diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 68e5fed..2b4f995 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -34,8 +34,6 @@ type UnsignedImportTx struct { ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"` // Outputs Outs []EVMOutput `serialize:"true" json:"outputs"` - // EVM nonce - nonce uint64 } // InputUTXOs returns the UTXOIDs of the imported funds @@ -182,6 +180,11 @@ func (vm *VM) newImportTx( return nil, errNoFunds // No imported UTXOs were spendable } + nonce, err := vm.GetAcceptedNonce(to) + if err != nil { + return nil, err + } + outs := []EVMOutput{} if importedAmount < vm.txFee { // imported amount goes toward paying tx fee // TODO: spend EVM balance to compensate vm.txFee-importedAmount @@ -190,13 +193,10 @@ func (vm *VM) newImportTx( outs = append(outs, EVMOutput{ Address: to, Amount: importedAmount - vm.txFee, + Nonce: nonce, }) } - nonce, err := vm.GetAcceptedNonce(to) - if err != nil { - return nil, err - } // Create the transaction utx := &UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, @@ -204,7 +204,6 @@ func (vm *VM) newImportTx( Outs: outs, ImportedInputs: importedInputs, SourceChain: chainID, - nonce: nonce, } tx := &Tx{UnsignedTx: utx} if err := tx.Sign(vm.codec, signers); err != nil { @@ -218,10 +217,10 @@ func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) error { state.AddBalance(to.Address, new(big.Int).Mul( new(big.Int).SetUint64(to.Amount), x2cRate)) - if state.GetNonce(to.Address) != tx.nonce { + if state.GetNonce(to.Address) != to.Nonce { return errInvalidNonce } - state.SetNonce(to.Address, tx.nonce+1) + state.SetNonce(to.Address, to.Nonce+1) } return nil } diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 7e60c79..789ce56 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -33,6 +33,7 @@ var ( type EVMOutput struct { Address common.Address `serialize:"true" json:"address"` Amount uint64 `serialize:"true" json:"amount"` + Nonce uint64 `serialize:"true" json:"nonce"` } func (out *EVMOutput) Verify() error { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index f62bc7b..38646c9 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -737,9 +737,14 @@ func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount if amount < balance { balance = amount } + nonce, err := vm.GetAcceptedNonce(addr) + if err != nil { + return nil, nil, err + } inputs = append(inputs, EVMInput{ Address: addr, Amount: balance, + Nonce: nonce, }) signers = append(signers, []*crypto.PrivateKeySECP256K1R{key}) amount -= balance -- cgit v1.2.3-70-g09d2 From a054d19f05a31a0421d6fe1bc534da46921481d5 Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 20 Aug 2020 01:03:31 -0400 Subject: fix minor bugs --- plugin/evm/export_tx.go | 2 ++ plugin/evm/vm.go | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 423c754..4f47a16 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -8,6 +8,7 @@ import ( "math/big" "github.com/ava-labs/coreth/core/state" + "github.com/ava-labs/go-ethereum/log" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" @@ -149,6 +150,7 @@ func (vm *VM) newExportTx( func (tx *UnsignedExportTx) EVMStateTransfer(state *state.StateDB) error { for _, from := range tx.Ins { + log.Info("consume", "in", from.Address, "amount", from.Amount, "nonce", from.Nonce, "nonce0", state.GetNonce(from.Address)) amount := new(big.Int).Mul( new(big.Int).SetUint64(from.Amount), x2cRate) if state.GetBalance(from.Address).Cmp(amount) < 0 { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index fffc0d5..8823380 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -229,7 +229,10 @@ func (vm *VM) Initialize( chain.SetOnFinalizeAndAssemble(func(state *state.StateDB, txs []*types.Transaction) ([]byte, error) { select { case atx := <-vm.pendingAtomicTxs: - atx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) + if err := atx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state); err != nil { + vm.newBlockChan <- nil + return nil, err + } raw, _ := vm.codec.Marshal(atx) return raw, nil default: @@ -263,8 +266,7 @@ func (vm *VM) Initialize( return vm.getLastAccepted().ethBlock }) chain.SetOnExtraStateChange(func(block *types.Block, state *state.StateDB) error { - vm.getAtomicTx(block).UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) - return nil + return vm.getAtomicTx(block).UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) }) vm.blockCache = cache.LRU{Size: 2048} vm.blockStatusCache = cache.LRU{Size: 1024} @@ -735,6 +737,9 @@ func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount } addr := GetEthAddress(key) balance := new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64() + if balance == 0 { + continue + } if amount < balance { balance = amount } -- cgit v1.2.3-70-g09d2 From b3fb1dc6ed3c5610c03abdd6f2ff7bf164fb83ca Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 20 Aug 2020 02:21:57 -0400 Subject: add flow check for ExportTx --- plugin/evm/export_tx.go | 32 ++++++++++++++++++++++++++++++-- plugin/evm/vm.go | 7 ++++++- 2 files changed, 36 insertions(+), 3 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 4f47a16..8b3c4f8 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -91,8 +91,36 @@ func (tx *UnsignedExportTx) SemanticVerify( return permError{err} } - // TODO: credential check - // TODO: flow check + f := crypto.FactorySECP256K1R{} + for i, cred := range stx.Creds { + if err := cred.Verify(); err != nil { + return permError{err} + } + pubKey, err := f.RecoverPublicKey(tx.UnsignedBytes(), cred.(*secp256k1fx.Credential).Sigs[0][:]) + if err != nil { + return permError{err} + } + if tx.Ins[i].Address != PublicKeyToEthAddress(pubKey) { + return permError{errPublicKeySignatureMismatch} + } + } + + // do flow-checking + fc := avax.NewFlowChecker() + fc.Produce(vm.ctx.AVAXAssetID, vm.txFee) + + for _, out := range tx.ExportedOutputs { + fc.Produce(out.AssetID(), out.Output().Amount()) + } + + for _, in := range tx.Ins { + fc.Consume(vm.ctx.AVAXAssetID, in.Amount) + } + + if err := fc.Verify(); err != nil { + return permError{err} + } + // TODO: verify UTXO outputs via gRPC return nil } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 8823380..98ecb6f 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -175,6 +175,7 @@ func (vm *VM) getAtomicTx(block *types.Block) *Tx { panic(err) } } + atx.Sign(vm.codec, nil) return atx } @@ -719,8 +720,12 @@ func (vm *VM) GetAtomicUTXOs( } func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address { + return PublicKeyToEthAddress(privKey.PublicKey()) +} + +func PublicKeyToEthAddress(pubKey crypto.PublicKey) common.Address { return ethcrypto.PubkeyToAddress( - (*privKey.PublicKey().(*crypto.PublicKeySECP256K1R).ToECDSA())) + (*pubKey.(*crypto.PublicKeySECP256K1R).ToECDSA())) } func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount uint64) ([]EVMInput, [][]*crypto.PrivateKeySECP256K1R, error) { -- cgit v1.2.3-70-g09d2 From 52c6ccd2f063a6cccc35f5b4380b22fb9dd0f102 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 20 Aug 2020 09:42:06 -0400 Subject: implemented exportTx accept --- plugin/evm/block.go | 78 ++++++++++++++++++++++++++----------------------- plugin/evm/export_tx.go | 35 ++++++++++++++++++++-- plugin/evm/vm.go | 13 +++++---- 3 files changed, 82 insertions(+), 44 deletions(-) (limited to 'plugin/evm/export_tx.go') diff --git a/plugin/evm/block.go b/plugin/evm/block.go index b3412a0..8e13c67 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -33,9 +33,12 @@ func (b *Block) Accept() error { vm.updateStatus(b.ID(), choices.Accepted) tx := vm.getAtomicTx(b.ethBlock) + if tx == nil { + return nil + } utx, ok := tx.UnsignedTx.(UnsignedAtomicTx) if !ok { - return errors.New("unknown atomic tx type") + return errors.New("unknown tx type") } return utx.Accept(vm.ctx, nil) @@ -73,47 +76,50 @@ func (b *Block) Parent() snowman.Block { func (b *Block) Verify() error { vm := b.vm tx := vm.getAtomicTx(b.ethBlock) - switch atx := tx.UnsignedTx.(type) { - case *UnsignedImportTx: - if b.ethBlock.Hash() == vm.genesisHash { - return nil - } - p := b.Parent() - path := []*Block{} - inputs := new(ids.Set) - for { - if p.Status() == choices.Accepted || p.(*Block).ethBlock.Hash() == vm.genesisHash { - break + if tx != nil { + switch atx := tx.UnsignedTx.(type) { + case *UnsignedImportTx: + if b.ethBlock.Hash() == vm.genesisHash { + return nil } - if ret, hit := vm.blockAtomicInputCache.Get(p.ID()); hit { - inputs = ret.(*ids.Set) - break + p := b.Parent() + path := []*Block{} + inputs := new(ids.Set) + for { + if p.Status() == choices.Accepted || p.(*Block).ethBlock.Hash() == vm.genesisHash { + break + } + if ret, hit := vm.blockAtomicInputCache.Get(p.ID()); hit { + inputs = ret.(*ids.Set) + break + } + path = append(path, p.(*Block)) + p = p.Parent().(*Block) } - path = append(path, p.(*Block)) - p = p.Parent().(*Block) - } - for i := len(path) - 1; i >= 0; i-- { - inputsCopy := new(ids.Set) - p := path[i] - atx := vm.getAtomicTx(p.ethBlock) - inputs.Union(atx.UnsignedTx.(UnsignedAtomicTx).InputUTXOs()) - inputsCopy.Union(*inputs) - vm.blockAtomicInputCache.Put(p.ID(), inputsCopy) - } - for _, in := range atx.InputUTXOs().List() { - if inputs.Contains(in) { - return errInvalidBlock + for i := len(path) - 1; i >= 0; i-- { + inputsCopy := new(ids.Set) + p := path[i] + atx := vm.getAtomicTx(p.ethBlock) + if atx != nil { + inputs.Union(atx.UnsignedTx.(UnsignedAtomicTx).InputUTXOs()) + inputsCopy.Union(*inputs) + } + vm.blockAtomicInputCache.Put(p.ID(), inputsCopy) } + for _, in := range atx.InputUTXOs().List() { + if inputs.Contains(in) { + return errInvalidBlock + } + } + case *UnsignedExportTx: + default: + return errors.New("unknown atomic tx type") } - case *UnsignedExportTx: - default: - return errors.New("unknown atomic tx type") - } - if tx.UnsignedTx.(UnsignedAtomicTx).SemanticVerify(vm, tx) != nil { - return errInvalidBlock + if tx.UnsignedTx.(UnsignedAtomicTx).SemanticVerify(vm, tx) != nil { + return errInvalidBlock + } } - _, err := b.vm.chain.InsertChain([]*types.Block{b.ethBlock}) return err } diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 8b3c4f8..31b4681 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/go-ethereum/log" + "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" @@ -126,9 +127,37 @@ func (tx *UnsignedExportTx) SemanticVerify( } // Accept this transaction. -func (tx *UnsignedExportTx) Accept(ctx *snow.Context, batch database.Batch) error { - // TODO: finish this function via gRPC - return nil +func (tx *UnsignedExportTx) Accept(ctx *snow.Context, _ database.Batch) error { + txID := tx.ID() + + elems := make([]*atomic.Element, len(tx.ExportedOutputs)) + for i, out := range tx.ExportedOutputs { + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(i), + }, + Asset: avax.Asset{ID: out.AssetID()}, + Out: out.Out, + } + + utxoBytes, err := Codec.Marshal(utxo) + if err != nil { + return err + } + + elem := &atomic.Element{ + Key: utxo.InputID().Bytes(), + Value: utxoBytes, + } + if out, ok := utxo.Out.(avax.Addressable); ok { + elem.Traits = out.Addresses() + } + + elems[i] = elem + } + + return ctx.SharedMemory.Put(tx.DestinationChain, elems) } // Create a new transaction diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 0910f6d..4ea3793 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -177,11 +177,10 @@ type VM struct { } func (vm *VM) getAtomicTx(block *types.Block) *Tx { + extdata := block.ExtraData() atx := new(Tx) - if extdata := block.ExtraData(); extdata != nil { - if err := vm.codec.Unmarshal(extdata, atx); err != nil { - panic(err) - } + if err := vm.codec.Unmarshal(extdata, atx); err != nil { + return nil } atx.Sign(vm.codec, nil) return atx @@ -284,7 +283,11 @@ func (vm *VM) Initialize( return vm.getLastAccepted().ethBlock }) chain.SetOnExtraStateChange(func(block *types.Block, state *state.StateDB) error { - return vm.getAtomicTx(block).UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) + tx := vm.getAtomicTx(block) + if tx == nil { + return nil + } + return tx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state) }) vm.blockCache = cache.LRU{Size: 2048} vm.blockStatusCache = cache.LRU{Size: 1024} -- cgit v1.2.3-70-g09d2