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/block.go | 15 ++++++++-- plugin/evm/import_tx.go | 54 +++++++++++++++++++++++++++++++--- plugin/evm/vm.go | 78 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 124 insertions(+), 23 deletions(-) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index d75ea1d..b3412a0 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -27,9 +27,18 @@ func (b *Block) ID() ids.ID { return b.id } // Accept implements the snowman.Block interface func (b *Block) Accept() error { - b.vm.ctx.Log.Verbo("Block %s is accepted", b.ID()) - b.vm.updateStatus(b.ID(), choices.Accepted) - return nil + vm := b.vm + + vm.ctx.Log.Verbo("Block %s is accepted", b.ID()) + vm.updateStatus(b.ID(), choices.Accepted) + + tx := vm.getAtomicTx(b.ethBlock) + utx, ok := tx.UnsignedTx.(UnsignedAtomicTx) + if !ok { + return errors.New("unknown atomic tx type") + } + + return utx.Accept(vm.ctx, nil) } // Reject implements the snowman.Block interface 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 diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index fffc0d5..f439f59 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/formatting" geckojson "github.com/ava-labs/gecko/utils/json" + "github.com/ava-labs/gecko/utils/logging" "github.com/ava-labs/gecko/utils/timer" "github.com/ava-labs/gecko/utils/wrappers" "github.com/ava-labs/gecko/vms/components/avax" @@ -63,6 +64,8 @@ const ( minBlockTime = 250 * time.Millisecond maxBlockTime = 1000 * time.Millisecond batchSize = 250 + + maxUTXOsToFetch = 1024 ) const ( @@ -117,13 +120,16 @@ func init() { errs.Add( Codec.RegisterType(&UnsignedImportTx{}), Codec.RegisterType(&UnsignedExportTx{}), + ) + Codec.Skip(3) + errs.Add( Codec.RegisterType(&secp256k1fx.TransferInput{}), - Codec.RegisterType(&secp256k1fx.Input{}), - Codec.RegisterType(&secp256k1fx.Credential{}), + Codec.RegisterType(&secp256k1fx.MintOutput{}), Codec.RegisterType(&secp256k1fx.TransferOutput{}), - Codec.RegisterType(&secp256k1fx.OutputOwners{}), Codec.RegisterType(&secp256k1fx.MintOperation{}), - Codec.RegisterType(&secp256k1fx.MintOutput{}), + Codec.RegisterType(&secp256k1fx.Credential{}), + Codec.RegisterType(&secp256k1fx.Input{}), + Codec.RegisterType(&secp256k1fx.OutputOwners{}), ) if errs.Errored() { panic(errs.Err) @@ -166,6 +172,8 @@ type VM struct { txFee uint64 pendingAtomicTxs chan *Tx blockAtomicInputCache cache.LRU + + fx secp256k1fx.Fx } func (vm *VM) getAtomicTx(block *types.Block) *Tx { @@ -178,6 +186,15 @@ func (vm *VM) getAtomicTx(block *types.Block) *Tx { return atx } +// Codec implements the secp256k1fx interface +func (vm *VM) Codec() codec.Codec { return codec.NewDefault() } + +// Clock implements the secp256k1fx interface +func (vm *VM) Clock() *timer.Clock { return &vm.clock } + +// Logger implements the secp256k1fx interface +func (vm *VM) Logger() logging.Logger { return vm.ctx.Log } + /* ****************************************************************************** ********************************* Snowman API ******************************** @@ -353,16 +370,16 @@ func (vm *VM) Initialize( }) vm.codec = Codec - return nil + return vm.fx.Initialize(vm) } // Bootstrapping notifies this VM that the consensus engine is performing // bootstrapping -func (vm *VM) Bootstrapping() error { return nil } +func (vm *VM) Bootstrapping() error { return vm.fx.Bootstrapping() } // Bootstrapped notifies this VM that the consensus engine has finished // bootstrapping -func (vm *VM) Bootstrapped() error { return nil } +func (vm *VM) Bootstrapped() error { return vm.fx.Bootstrapped() } // Shutdown implements the snowman.ChainVM interface func (vm *VM) Shutdown() error { @@ -705,15 +722,44 @@ func (vm *VM) GetAtomicUTXOs( startUTXOID ids.ID, limit int, ) ([]*avax.UTXO, ids.ShortID, ids.ID, error) { - // TODO: finish this function via gRPC - utxos := []*avax.UTXO{{ - UTXOID: avax.UTXOID{TxID: ids.Empty}, - Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: 100, - }, - }} - return utxos, ids.ShortEmpty, ids.Empty, nil + if limit <= 0 || limit > maxUTXOsToFetch { + limit = maxUTXOsToFetch + } + + addrsList := make([][]byte, addrs.Len()) + for i, addr := range addrs.List() { + addrsList[i] = addr.Bytes() + } + + allUTXOBytes, lastAddr, lastUTXO, err := vm.ctx.SharedMemory.Indexed( + chainID, + addrsList, + startAddr.Bytes(), + startUTXOID.Bytes(), + limit, + ) + if err != nil { + return nil, ids.ShortID{}, ids.ID{}, fmt.Errorf("error fetching atomic UTXOs: %w", err) + } + + lastAddrID, err := ids.ToShortID(lastAddr) + if err != nil { + lastAddrID = ids.ShortEmpty + } + lastUTXOID, err := ids.ToID(lastUTXO) + if err != nil { + lastAddrID = ids.ShortEmpty + } + + utxos := make([]*avax.UTXO, len(allUTXOBytes)) + for i, utxoBytes := range allUTXOBytes { + utxo := &avax.UTXO{} + if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil { + return nil, ids.ShortID{}, ids.ID{}, fmt.Errorf("error parsing UTXO: %w", err) + } + utxos[i] = utxo + } + return utxos, lastAddrID, lastUTXOID, nil } func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address { -- cgit v1.2.3