aboutsummaryrefslogblamecommitdiff
path: root/plugin/evm/import_tx.go
blob: 36750a616f083f84f3fc58dadaeae766e3ad0421 (plain) (tree)































































































































































































































































































































                                                                                                               
// (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()
}