From 88cc3698b3663972cd9b60faf5c14a7e1bbee965 Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 13 Aug 2020 21:11:56 -0400 Subject: WIP: X-to-C transfer --- plugin/evm/import_tx.go | 320 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 plugin/evm/import_tx.go (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go new file mode 100644 index 0000000..36750a6 --- /dev/null +++ b/plugin/evm/import_tx.go @@ -0,0 +1,320 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "crypto/ecdsa" + "errors" + "fmt" + + //"github.com/ava-labs/gecko/chains/atomic" + "github.com/ava-labs/gecko/database" + //"github.com/ava-labs/gecko/database/versiondb" + "github.com/ava-labs/gecko/ids" + avacrypto "github.com/ava-labs/gecko/utils/crypto" + "github.com/ava-labs/gecko/utils/hashing" + "github.com/ava-labs/gecko/utils/math" + "github.com/ava-labs/gecko/vms/components/ava" + "github.com/ava-labs/gecko/vms/components/verify" + "github.com/ava-labs/gecko/vms/secp256k1fx" + "github.com/ava-labs/go-ethereum/common" + "github.com/ava-labs/go-ethereum/crypto" +) + +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") +) + +// UnsignedImportTx is an unsigned ImportTx +type UnsignedImportTx struct { + BaseTx `serialize:"true"` + // Inputs that consume UTXOs produced on the X-Chain + ImportedInputs []*ava.TransferableInput `serialize:"true" json:"importedInputs"` +} + +// initialize [tx]. Sets [tx.vm], [tx.unsignedBytes], [tx.bytes], [tx.id] +func (tx *UnsignedImportTx) initialize(vm *VM, bytes []byte) error { + if tx.vm != nil { // already been initialized + return nil + } + tx.vm = vm + tx.bytes = bytes + tx.id = ids.NewID(hashing.ComputeHash256Array(bytes)) + var err error + tx.unsignedBytes, err = Codec.Marshal(interface{}(tx)) + if err != nil { + return fmt.Errorf("couldn't marshal UnsignedImportTx: %w", err) + } + return nil +} + +// 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 +} + +var ( + errInputOverflow = errors.New("inputs overflowed uint64") + errOutputOverflow = errors.New("outputs overflowed uint64") +) + +// Verify that: +// * inputs are all AVAX +// * sum(inputs{unlocked}) >= sum(outputs{unlocked}) + burnAmount{unlocked} +func syntacticVerifySpend( + ins []*ava.TransferableInput, + unlockedOuts []EVMOutput, + burnedUnlocked uint64, + avaxAssetID ids.ID, +) error { + // AVAX consumed in this tx + consumedUnlocked := uint64(0) + for _, in := range ins { + if assetID := in.AssetID(); !assetID.Equals(avaxAssetID) { // all inputs must be AVAX + return fmt.Errorf("input has unexpected asset ID %s expected %s", assetID, avaxAssetID) + } + + in := in.Input() + consumed := in.Amount() + newConsumed, err := math.Add64(consumedUnlocked, consumed) + if err != nil { + return errInputOverflow + } + consumedUnlocked = newConsumed + } + + // AVAX produced in this tx + producedUnlocked := burnedUnlocked + for _, out := range unlockedOuts { + produced := out.Amount + newProduced, err := math.Add64(producedUnlocked, produced) + if err != nil { + return errOutputOverflow + } + producedUnlocked = newProduced + } + + if producedUnlocked > consumedUnlocked { + return fmt.Errorf("tx unlocked outputs (%d) + burn amount (%d) > inputs (%d)", + producedUnlocked-burnedUnlocked, burnedUnlocked, consumedUnlocked) + } + return nil +} + +// Verify this transaction is well-formed +func (tx *UnsignedImportTx) Verify() error { + switch { + case tx == nil: + return errNilTx + case tx.syntacticallyVerified: // already passed syntactic verification + return nil + case len(tx.ImportedInputs) == 0: + return errNoImportInputs + } + + if err := tx.BaseTx.Verify(); err != nil { + return err + } + + for _, in := range tx.ImportedInputs { + if err := in.Verify(); err != nil { + return err + } + } + if !ava.IsSortedAndUniqueTransferableInputs(tx.ImportedInputs) { + return errInputsNotSortedUnique + } + + allIns := make([]*ava.TransferableInput, len(tx.Ins)+len(tx.ImportedInputs)) + copy(allIns, tx.Ins) + copy(allIns[len(tx.Ins):], tx.ImportedInputs) + if err := syntacticVerifySpend(allIns, tx.Outs, tx.vm.txFee, tx.vm.avaxAssetID); err != nil { + return err + } + + tx.syntacticallyVerified = true + return nil +} + +// SemanticVerify this transaction is valid. +func (tx *UnsignedImportTx) SemanticVerify(db database.Database, creds []verify.Verifiable) TxError { + if err := tx.Verify(); err != nil { + return permError{err} + } + + //// Verify (but don't spend) imported inputs + //smDB := tx.vm.ctx.SharedMemory.GetDatabase(tx.vm.avm) + //defer tx.vm.ctx.SharedMemory.ReleaseDatabase(tx.vm.avm) + + //utxos := make([]*ava.UTXO, len(tx.Ins)+len(tx.ImportedInputs)) + //for index, input := range tx.Ins { + // utxoID := input.UTXOID.InputID() + // utxo, err := tx.vm.getUTXO(db, utxoID) + // if err != nil { + // return tempError{err} + // } + // utxos[index] = utxo + //} + + //state := ava.NewPrefixedState(smDB, Codec) + //for index, input := range tx.ImportedInputs { + // utxoID := input.UTXOID.InputID() + // utxo, err := state.AVMUTXO(utxoID) + // if err != nil { // Get the UTXO + // return tempError{err} + // } + // utxos[index+len(tx.Ins)] = utxo + //} + + //ins := make([]*ava.TransferableInput, len(tx.Ins)+len(tx.ImportedInputs)) + //copy(ins, tx.Ins) + //copy(ins[len(tx.Ins):], tx.ImportedInputs) + + //// Verify the flowcheck + //if err := tx.vm.semanticVerifySpendUTXOs(tx, utxos, ins, tx.Outs, creds); err != nil { + // return err + //} + + //txID := tx.ID() + + //// Consume the UTXOS + //if err := tx.vm.consumeInputs(db, tx.Ins); err != nil { + // return tempError{err} + //} + //// Produce the UTXOS + //if err := tx.vm.produceOutputs(db, txID, tx.Outs); 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(batch database.Batch) error { + //smDB := tx.vm.ctx.SharedMemory.GetDatabase(tx.vm.avm) + //defer tx.vm.ctx.SharedMemory.ReleaseDatabase(tx.vm.avm) + + //vsmDB := versiondb.New(smDB) + //state := ava.NewPrefixedState(vsmDB, Codec) + + //// Spend imported UTXOs + //for _, in := range tx.ImportedInputs { + // utxoID := in.InputID() + // if err := state.SpendAVMUTXO(utxoID); err != nil { + // return err + // } + //} + + //sharedBatch, err := vsmDB.CommitBatch() + //if err != nil { + // return err + //} + //return atomic.WriteAll(batch, sharedBatch) + return nil +} + +// Create a new transaction +func (vm *VM) newImportTx( + to common.Address, // Address of recipient + keys []*ecdsa.PrivateKey, // Keys to import the funds +) (*AtomicTx, error) { + kc := secp256k1fx.NewKeychain() + factory := &avacrypto.FactorySECP256K1R{} + for _, key := range keys { + sk, err := factory.ToPrivateKey(crypto.FromECDSA(key)) + if err != nil { + panic(err) + } + kc.Add(sk.(*avacrypto.PrivateKeySECP256K1R)) + } + + addrSet := ids.Set{} + for _, addr := range kc.Addresses().List() { + addrSet.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes()))) + } + atomicUTXOs, err := vm.GetAtomicUTXOs(addrSet) + if err != nil { + return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) + } + + importedInputs := []*ava.TransferableInput{} + signers := [][]*avacrypto.PrivateKeySECP256K1R{} + + importedAmount := uint64(0) + now := vm.clock.Unix() + for _, utxo := range atomicUTXOs { + if !utxo.AssetID().Equals(vm.avaxAssetID) { + continue + } + inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now) + if err != nil { + continue + } + input, ok := inputIntf.(ava.TransferableIn) + if !ok { + continue + } + importedAmount, err = math.Add64(importedAmount, input.Amount()) + if err != nil { + return nil, err + } + importedInputs = append(importedInputs, &ava.TransferableInput{ + UTXOID: utxo.UTXOID, + Asset: utxo.Asset, + In: input, + }) + signers = append(signers, utxoSigners) + } + ava.SortTransferableInputsWithSigners(importedInputs, signers) + + if importedAmount == 0 { + return nil, errNoFunds // No imported UTXOs were spendable + } + + ins := []*ava.TransferableInput{} + outs := []EVMOutput{} + if importedAmount < vm.txFee { // imported amount goes toward paying tx fee + //var baseSigners [][]*avacrypto.PrivateKeySECP256K1R + //ins, outs, _, baseSigners, err = vm.spend(vm.DB, keys, 0, vm.txFee-importedAmount) + //if err != nil { + // return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) + //} + //signers = append(baseSigners, signers...) + } else if importedAmount > vm.txFee { + outs = append(outs, EVMOutput{ + Address: to, + Amount: importedAmount - vm.txFee}) + } + + // Create the transaction + utx := &UnsignedImportTx{ + BaseTx: BaseTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + Outs: outs, + Ins: ins, + }, + ImportedInputs: importedInputs, + } + tx := &AtomicTx{UnsignedAtomicTx: utx} + if err := vm.signAtomicTx(tx, signers); err != nil { + return nil, err + } + return tx, utx.Verify() +} -- cgit v1.2.3-70-g09d2