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 From b989b5f949424f72b125cbec460824b94b7c55ab Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 13 Aug 2020 21:30:56 -0400 Subject: ... --- plugin/evm/import_tx.go | 75 ++----------------------------------------------- 1 file changed, 3 insertions(+), 72 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 36750a6..bbbdb95 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -8,9 +8,7 @@ import ( "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" @@ -153,51 +151,7 @@ func (tx *UnsignedImportTx) SemanticVerify(db database.Database, creds []verify. 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} - //} - + // TODO: verify UTXO inputs via gRPC return nil } @@ -207,25 +161,7 @@ func (tx *UnsignedImportTx) SemanticVerify(db database.Database, creds []verify. // 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) + // TODO: finish this function via gRPC return nil } @@ -290,12 +226,7 @@ func (vm *VM) newImportTx( 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...) + // TODO: spend EVM balance to compensate vm.txFee-importedAmount } else if importedAmount > vm.txFee { outs = append(outs, EVMOutput{ Address: to, -- cgit v1.2.3 From 0844c8c6919f6d98ebe8a9501360ef5bc362dff3 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 01:25:20 -0400 Subject: catch up with the new P-Chain cross-chain impl --- plugin/evm/import_tx.go | 156 ++++++++++++++++-------------------------------- 1 file changed, 51 insertions(+), 105 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index bbbdb95..84f28de 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -4,20 +4,20 @@ package evm import ( - "crypto/ecdsa" + //"crypto/ecdsa" "errors" "fmt" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/snow" 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/components/avax" + //"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" + //"github.com/ava-labs/go-ethereum/crypto" ) var ( @@ -29,29 +29,18 @@ var ( 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") ) // 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 + // 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"` } // InputUTXOs returns the UTXOIDs of the imported funds @@ -63,66 +52,28 @@ func (tx *UnsignedImportTx) InputUTXOs() ids.Set { 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 { +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): + // TODO: remove this check if we allow for P->C swaps + return errWrongChainID case len(tx.ImportedInputs) == 0: return errNoImportInputs } - if err := tx.BaseTx.Verify(); err != nil { + if err := tx.BaseTx.Verify(ctx); err != nil { return err } @@ -131,27 +82,24 @@ func (tx *UnsignedImportTx) Verify() error { return err } } - if !ava.IsSortedAndUniqueTransferableInputs(tx.ImportedInputs) { + if !avax.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 { +func (tx *UnsignedImportTx) SemanticVerify( + vm *VM, + stx *Tx, +) TxError { + if err := tx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID); err != nil { return permError{err} } - // TODO: verify UTXO inputs via gRPC + // TODO: verify using avax.VerifyTx(vm.txFee, vm.avaxAssetID, tx.Ins, outs) + // TODO: verify UTXO inputs via gRPC (with creds) return nil } @@ -167,29 +115,25 @@ func (tx *UnsignedImportTx) Accept(batch database.Batch) error { // Create a new transaction func (vm *VM) newImportTx( + chainID ids.ID, // chain to import from to common.Address, // Address of recipient - keys []*ecdsa.PrivateKey, // Keys to import the funds -) (*AtomicTx, error) { + keys []*avacrypto.PrivateKeySECP256K1R, // Keys to import the funds +) (*Tx, error) { + if !vm.avm.Equals(chainID) { + return nil, errWrongChainID + } + 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)) + kc.Add(key) } - addrSet := ids.Set{} - for _, addr := range kc.Addresses().List() { - addrSet.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes()))) - } - atomicUTXOs, err := vm.GetAtomicUTXOs(addrSet) + 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 := []*ava.TransferableInput{} + importedInputs := []*avax.TransferableInput{} signers := [][]*avacrypto.PrivateKeySECP256K1R{} importedAmount := uint64(0) @@ -202,7 +146,7 @@ func (vm *VM) newImportTx( if err != nil { continue } - input, ok := inputIntf.(ava.TransferableIn) + input, ok := inputIntf.(avax.TransferableIn) if !ok { continue } @@ -210,27 +154,28 @@ func (vm *VM) newImportTx( if err != nil { return nil, err } - importedInputs = append(importedInputs, &ava.TransferableInput{ + importedInputs = append(importedInputs, &avax.TransferableInput{ UTXOID: utxo.UTXOID, Asset: utxo.Asset, In: input, }) signers = append(signers, utxoSigners) } - ava.SortTransferableInputsWithSigners(importedInputs, signers) + avax.SortTransferableInputsWithSigners(importedInputs, signers) if importedAmount == 0 { return nil, errNoFunds // No imported UTXOs were spendable } - ins := []*ava.TransferableInput{} + ins := []*avax.TransferableInput{} outs := []EVMOutput{} if importedAmount < vm.txFee { // imported amount goes toward paying tx fee // TODO: spend EVM balance to compensate vm.txFee-importedAmount } else if importedAmount > vm.txFee { outs = append(outs, EVMOutput{ Address: to, - Amount: importedAmount - vm.txFee}) + Amount: importedAmount - vm.txFee, + }) } // Create the transaction @@ -241,11 +186,12 @@ func (vm *VM) newImportTx( Outs: outs, Ins: ins, }, + SourceChain: chainID, ImportedInputs: importedInputs, } - tx := &AtomicTx{UnsignedAtomicTx: utx} - if err := vm.signAtomicTx(tx, signers); err != nil { + tx := &Tx{UnsignedTx: utx} + if err := tx.Sign(vm.codec, signers); err != nil { return nil, err } - return tx, utx.Verify() + return tx, utx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID) } -- cgit v1.2.3 From ec103c07738b3db70501dee7a7da113edbb68875 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 16:13:57 -0400 Subject: ... --- plugin/evm/import_tx.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 84f28de..ec2ffa4 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -17,6 +17,7 @@ import ( //"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/log" //"github.com/ava-labs/go-ethereum/crypto" ) @@ -191,6 +192,7 @@ func (vm *VM) newImportTx( } tx := &Tx{UnsignedTx: utx} if err := tx.Sign(vm.codec, signers); err != nil { + log.Info("hey here1", "err", err, "utx", utx) return nil, err } return tx, utx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID) -- cgit v1.2.3 From 1e9599e88a5d88e0090b0ebddfae756e343e605a Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 20:32:18 -0400 Subject: make the basic X-to-C logic work --- plugin/evm/import_tx.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index ec2ffa4..a61adaa 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -4,21 +4,17 @@ package evm import ( - //"crypto/ecdsa" "errors" "fmt" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" - avacrypto "github.com/ava-labs/gecko/utils/crypto" + 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/components/verify" "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/go-ethereum/common" - "github.com/ava-labs/go-ethereum/log" - //"github.com/ava-labs/go-ethereum/crypto" ) var ( @@ -68,7 +64,6 @@ func (tx *UnsignedImportTx) Verify( case tx.SourceChain.IsZero(): return errWrongChainID case !tx.SourceChain.Equals(avmID): - // TODO: remove this check if we allow for P->C swaps return errWrongChainID case len(tx.ImportedInputs) == 0: return errNoImportInputs @@ -118,7 +113,7 @@ func (tx *UnsignedImportTx) Accept(batch database.Batch) error { func (vm *VM) newImportTx( chainID ids.ID, // chain to import from to common.Address, // Address of recipient - keys []*avacrypto.PrivateKeySECP256K1R, // Keys to import the funds + keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds ) (*Tx, error) { if !vm.avm.Equals(chainID) { return nil, errWrongChainID @@ -135,7 +130,7 @@ func (vm *VM) newImportTx( } importedInputs := []*avax.TransferableInput{} - signers := [][]*avacrypto.PrivateKeySECP256K1R{} + signers := [][]*crypto.PrivateKeySECP256K1R{} importedAmount := uint64(0) now := vm.clock.Unix() @@ -172,6 +167,7 @@ func (vm *VM) newImportTx( 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, @@ -192,7 +188,6 @@ func (vm *VM) newImportTx( } tx := &Tx{UnsignedTx: utx} if err := tx.Sign(vm.codec, signers); err != nil { - log.Info("hey here1", "err", err, "utx", utx) return nil, err } return tx, utx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID) -- cgit v1.2.3 From b9b9f523b3a745ca0c5809ea2d1c76c80a4134cb Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 21:02:34 -0400 Subject: finish flow-checking --- plugin/evm/import_tx.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index a61adaa..0e77efc 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -94,7 +94,22 @@ func (tx *UnsignedImportTx) SemanticVerify( if err := tx.Verify(vm.avm, vm.ctx, vm.txFee, vm.avaxAssetID); err != nil { return permError{err} } - // TODO: verify using avax.VerifyTx(vm.txFee, vm.avaxAssetID, tx.Ins, outs) + + // do flow-checking + fc := avax.NewFlowChecker() + fc.Produce(vm.avaxAssetID, vm.txFee) + + for _, out := range tx.Outs { + fc.Produce(vm.avaxAssetID, out.Amount) + } + + for _, in := range tx.ImportedInputs { + fc.Consume(in.AssetID(), in.Input().Amount()) + } + if err := fc.Verify(); err != nil { + return permError{err} + } + // TODO: verify UTXO inputs via gRPC (with creds) return nil } -- cgit v1.2.3 From 0b934ef60fd652de038a21e6f25691b11cea7121 Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 21:20:58 -0400 Subject: remove base_tx.go --- plugin/evm/import_tx.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 0e77efc..2e49493 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -31,7 +31,18 @@ var ( // UnsignedImportTx is an unsigned ImportTx type UnsignedImportTx struct { - BaseTx `serialize:"true"` + 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. In practice is always the empty ID. + // This is only here to match avm.BaseTx's format + BlockchainID ids.ID `serialize:"true" json:"blockchainID"` + // Outputs + Outs []EVMOutput `serialize:"true" json:"outputs"` + // Memo field contains arbitrary bytes, up to maxMemoSize + Memo []byte `serialize:"true" json:"memo"` // Which chain to consume the funds from SourceChain ids.ID `serialize:"true" json:"sourceChain"` @@ -67,10 +78,16 @@ func (tx *UnsignedImportTx) Verify( return errWrongChainID case len(tx.ImportedInputs) == 0: return errNoImportInputs + case tx.NetworkID != ctx.NetworkID: + return errWrongNetworkID + case !ctx.ChainID.Equals(tx.BlockchainID): + return errWrongBlockchainID } - if err := tx.BaseTx.Verify(ctx); err != nil { - return err + for _, out := range tx.Outs { + if err := out.Verify(); err != nil { + return err + } } for _, in := range tx.ImportedInputs { @@ -178,7 +195,6 @@ func (vm *VM) newImportTx( return nil, errNoFunds // No imported UTXOs were spendable } - ins := []*avax.TransferableInput{} outs := []EVMOutput{} if importedAmount < vm.txFee { // imported amount goes toward paying tx fee // TODO: spend EVM balance to compensate vm.txFee-importedAmount @@ -192,14 +208,11 @@ func (vm *VM) newImportTx( // Create the transaction utx := &UnsignedImportTx{ - BaseTx: BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Outs: outs, - Ins: ins, - }, - SourceChain: chainID, + 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 { -- cgit v1.2.3 From 40879ea67433b73b464bd8b012a9809fbb646cfd Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 23:34:12 -0400 Subject: WIP: C-to-X transfer --- plugin/evm/import_tx.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 2e49493..68f34b0 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -27,6 +27,7 @@ var ( 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 @@ -36,19 +37,16 @@ type UnsignedImportTx struct { syntacticallyVerified bool // ID of the network on which this tx was issued NetworkID uint32 `serialize:"true" json:"networkID"` - // ID of this blockchain. In practice is always the empty ID. - // This is only here to match avm.BaseTx's format + // ID of this blockchain. BlockchainID ids.ID `serialize:"true" json:"blockchainID"` - // Outputs - Outs []EVMOutput `serialize:"true" json:"outputs"` - // Memo field contains arbitrary bytes, up to maxMemoSize - Memo []byte `serialize:"true" json:"memo"` - // 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"` + // Memo field contains arbitrary bytes, up to maxMemoSize + Memo []byte `serialize:"true" json:"memo"` } // InputUTXOs returns the UTXOIDs of the imported funds @@ -147,7 +145,7 @@ func (vm *VM) newImportTx( to common.Address, // Address of recipient keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds ) (*Tx, error) { - if !vm.avm.Equals(chainID) { + if !vm.ctx.XChainID.Equals(chainID) { return nil, errWrongChainID } -- cgit v1.2.3 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/import_tx.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'plugin/evm/import_tx.go') 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) + } +} -- cgit v1.2.3 From 2d409cb9790e55fd014546222f448786bbefa46b Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 19 Aug 2020 23:48:47 -0400 Subject: ... --- plugin/evm/import_tx.go | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) (limited to 'plugin/evm/import_tx.go') 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) { -- cgit v1.2.3 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/import_tx.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'plugin/evm/import_tx.go') 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 } -- cgit v1.2.3 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/import_tx.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'plugin/evm/import_tx.go') 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 } -- cgit v1.2.3 From 1c078567367ef45884da079cecc09a71d1761b01 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 20 Aug 2020 01:09:43 -0400 Subject: Added first pass of importTx verification --- plugin/evm/import_tx.go | 54 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) (limited to 'plugin/evm/import_tx.go') diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 2b4f995..35ba8cc 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -112,7 +112,49 @@ func (tx *UnsignedImportTx) SemanticVerify( return permError{err} } - // TODO: verify UTXO inputs via gRPC (with creds) + 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 } @@ -121,9 +163,13 @@ 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(ctx *snow.Context, batch database.Batch) error { - // TODO: finish this function via gRPC - return nil +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 -- cgit v1.2.3