aboutsummaryrefslogtreecommitdiff
path: root/plugin/evm/import_tx.go
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/evm/import_tx.go')
-rw-r--r--plugin/evm/import_tx.go272
1 files changed, 272 insertions, 0 deletions
diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go
new file mode 100644
index 0000000..35ba8cc
--- /dev/null
+++ b/plugin/evm/import_tx.go
@@ -0,0 +1,272 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+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"
+ "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"
+)
+
+// UnsignedImportTx is an unsigned ImportTx
+type UnsignedImportTx 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 consume the funds from
+ SourceChain ids.ID `serialize:"true" json:"sourceChain"`
+ // Inputs that consume UTXOs produced on the chain
+ ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"`
+ // Outputs
+ Outs []EVMOutput `serialize:"true" json:"outputs"`
+}
+
+// InputUTXOs returns the UTXOIDs of the imported funds
+func (tx *UnsignedImportTx) InputUTXOs() ids.Set {
+ set := ids.Set{}
+ for _, in := range tx.ImportedInputs {
+ set.Add(in.InputID())
+ }
+ return set
+}
+
+// Verify this transaction is well-formed
+func (tx *UnsignedImportTx) 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.SourceChain.IsZero():
+ return errWrongChainID
+ case !tx.SourceChain.Equals(avmID):
+ return errWrongChainID
+ case len(tx.ImportedInputs) == 0:
+ return errNoImportInputs
+ case tx.NetworkID != ctx.NetworkID:
+ return errWrongNetworkID
+ case !ctx.ChainID.Equals(tx.BlockchainID):
+ return errWrongBlockchainID
+ }
+
+ for _, out := range tx.Outs {
+ if err := out.Verify(); err != nil {
+ return err
+ }
+ }
+
+ for _, in := range tx.ImportedInputs {
+ if err := in.Verify(); err != nil {
+ return err
+ }
+ }
+ if !avax.IsSortedAndUniqueTransferableInputs(tx.ImportedInputs) {
+ return errInputsNotSortedUnique
+ }
+
+ tx.syntacticallyVerified = true
+ return nil
+}
+
+// SemanticVerify this transaction is valid.
+func (tx *UnsignedImportTx) 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}
+ }
+
+ // do flow-checking
+ fc := avax.NewFlowChecker()
+ fc.Produce(vm.ctx.AVAXAssetID, vm.txFee)
+
+ for _, out := range tx.Outs {
+ fc.Produce(vm.ctx.AVAXAssetID, out.Amount)
+ }
+
+ for _, in := range tx.ImportedInputs {
+ fc.Consume(in.AssetID(), in.Input().Amount())
+ }
+ if err := fc.Verify(); err != nil {
+ return permError{err}
+ }
+
+ if !vm.ctx.IsBootstrapped() {
+ // Allow for force committing during bootstrapping
+ return nil
+ }
+
+ utxoIDs := make([][]byte, len(tx.ImportedInputs))
+ for i, in := range tx.ImportedInputs {
+ utxoIDs[i] = in.UTXOID.InputID().Bytes()
+ }
+ allUTXOBytes, err := vm.ctx.SharedMemory.Get(tx.SourceChain, utxoIDs)
+ if err != nil {
+ 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]
+
+ utxo := &avax.UTXO{}
+ if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+ return tempError{err}
+ }
+
+ cred := stx.Creds[i]
+
+ utxoAssetID := utxo.AssetID()
+ inAssetID := in.AssetID()
+ if !utxoAssetID.Equals(inAssetID) {
+ return permError{errAssetIDMismatch}
+ }
+
+ if err := vm.fx.VerifyTransfer(tx, in.In, cred, utxo.Out); err != nil {
+ return tempError{err}
+ }
+ }
+ return nil
+}
+
+// Accept this transaction and spend imported inputs
+// We spend imported UTXOs here rather than in semanticVerify because
+// 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(ctx *snow.Context, _ database.Batch) error {
+ // TODO: Is any batch passed in here?
+ utxoIDs := make([][]byte, len(tx.ImportedInputs))
+ for i, in := range tx.ImportedInputs {
+ utxoIDs[i] = in.InputID().Bytes()
+ }
+ return ctx.SharedMemory.Remove(tx.SourceChain, utxoIDs)
+}
+
+// Create a new transaction
+func (vm *VM) newImportTx(
+ chainID ids.ID, // chain to import from
+ to common.Address, // Address of recipient
+ keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds
+) (*Tx, error) {
+ if !vm.ctx.XChainID.Equals(chainID) {
+ return nil, errWrongChainID
+ }
+
+ kc := secp256k1fx.NewKeychain()
+ for _, key := range keys {
+ kc.Add(key)
+ }
+
+ atomicUTXOs, _, _, err := vm.GetAtomicUTXOs(chainID, kc.Addresses(), ids.ShortEmpty, ids.Empty, -1)
+ if err != nil {
+ return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err)
+ }
+
+ importedInputs := []*avax.TransferableInput{}
+ signers := [][]*crypto.PrivateKeySECP256K1R{}
+
+ importedAmount := uint64(0)
+ now := vm.clock.Unix()
+ for _, utxo := range atomicUTXOs {
+ if !utxo.AssetID().Equals(vm.ctx.AVAXAssetID) {
+ continue
+ }
+ inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now)
+ if err != nil {
+ continue
+ }
+ input, ok := inputIntf.(avax.TransferableIn)
+ if !ok {
+ continue
+ }
+ importedAmount, err = math.Add64(importedAmount, input.Amount())
+ if err != nil {
+ return nil, err
+ }
+ importedInputs = append(importedInputs, &avax.TransferableInput{
+ UTXOID: utxo.UTXOID,
+ Asset: utxo.Asset,
+ In: input,
+ })
+ signers = append(signers, utxoSigners)
+ }
+ avax.SortTransferableInputsWithSigners(importedInputs, signers)
+
+ if importedAmount == 0 {
+ 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
+ return nil, errNoFunds
+ } else if importedAmount > vm.txFee {
+ outs = append(outs, EVMOutput{
+ Address: to,
+ Amount: importedAmount - vm.txFee,
+ Nonce: nonce,
+ })
+ }
+
+ // Create the transaction
+ utx := &UnsignedImportTx{
+ NetworkID: vm.ctx.NetworkID,
+ BlockchainID: vm.ctx.ChainID,
+ Outs: outs,
+ ImportedInputs: importedInputs,
+ SourceChain: chainID,
+ }
+ 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)
+}
+
+func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) error {
+ for _, to := range tx.Outs {
+ state.AddBalance(to.Address,
+ new(big.Int).Mul(
+ new(big.Int).SetUint64(to.Amount), x2cRate))
+ if state.GetNonce(to.Address) != to.Nonce {
+ return errInvalidNonce
+ }
+ state.SetNonce(to.Address, to.Nonce+1)
+ }
+ return nil
+}