diff options
-rw-r--r-- | plugin/evm/export_tx.go | 155 |
1 files changed, 155 insertions, 0 deletions
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) +} |