diff options
Diffstat (limited to 'plugin/evm/import_tx.go')
-rw-r--r-- | plugin/evm/import_tx.go | 272 |
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 +} |