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 ++++++------- plugin/evm/service.go | 3 +-- plugin/evm/tx.go | 37 ++++++++---------------------- plugin/evm/user.go | 4 +--- plugin/evm/vm.go | 61 ++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 68 insertions(+), 53 deletions(-) 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 } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 86bc706..3165ca7 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -201,8 +201,7 @@ func (service *AvaAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *ap } // TODO: return eth address here - reply.Address, err = service.vm.FormatAddress( - ethcrypto.PubkeyToAddress(*(sk.PublicKey().(*crypto.PublicKeySECP256K1R).ToECDSA()))) + reply.Address, err = service.vm.FormatAddress(GetEthAddress(sk)) if err != nil { return fmt.Errorf("problem formatting address: %w", err) } diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 6196eb8..90cd232 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/ava-labs/gecko/database" - "github.com/ava-labs/gecko/database/versiondb" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/utils/codec" @@ -38,6 +37,12 @@ func (out *EVMOutput) Verify() error { return nil } +type EVMInput EVMOutput + +func (in *EVMInput) Verify() error { + return nil +} + // UnsignedTx is an unsigned transaction type UnsignedTx interface { Initialize(unsignedBytes, signedBytes []byte) @@ -46,32 +51,6 @@ type UnsignedTx interface { Bytes() []byte } -// UnsignedDecisionTx is an unsigned operation that can be immediately decided -type UnsignedDecisionTx interface { - UnsignedTx - - // Attempts to verify this transaction with the provided state. - SemanticVerify(vm *VM, db database.Database, stx *Tx) ( - onAcceptFunc func() error, - err TxError, - ) -} - -// UnsignedProposalTx is an unsigned operation that can be proposed -type UnsignedProposalTx interface { - UnsignedTx - - // Attempts to verify this transaction with the provided state. - SemanticVerify(vm *VM, db database.Database, stx *Tx) ( - onCommitDB *versiondb.Database, - onAbortDB *versiondb.Database, - onCommitFunc func() error, - onAbortFunc func() error, - err TxError, - ) - InitiallyPrefersCommit(vm *VM) bool -} - // UnsignedAtomicTx is an unsigned operation that can be atomically accepted type UnsignedAtomicTx interface { UnsignedTx @@ -79,7 +58,7 @@ type UnsignedAtomicTx interface { // UTXOs this tx consumes InputUTXOs() ids.Set // Attempts to verify this transaction with the provided state. - SemanticVerify(vm *VM, db database.Database, stx *Tx) TxError + SemanticVerify(vm *VM, stx *Tx) TxError // Accept this transaction with the additionally provided state transitions. Accept(ctx *snow.Context, batch database.Batch) error @@ -94,6 +73,8 @@ type Tx struct { Creds []verify.Verifiable `serialize:"true" json:"credentials"` } +// (*secp256k1fx.Credential) + // Sign this transaction with the provided signers func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) error { unsignedBytes, err := c.Marshal(&tx.UnsignedTx) diff --git a/plugin/evm/user.go b/plugin/evm/user.go index 5d7a037..fbf2981 100644 --- a/plugin/evm/user.go +++ b/plugin/evm/user.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/go-ethereum/common" - ethcrypto "github.com/ava-labs/go-ethereum/crypto" ) // Key in the database whose corresponding value is the list of @@ -72,8 +71,7 @@ func (u *user) putAddress(privKey *crypto.PrivateKeySECP256K1R) error { return errKeyNil } - address := ethcrypto.PubkeyToAddress( - (*privKey.PublicKey().(*crypto.PublicKeySECP256K1R).ToECDSA())) // address the privKey controls + address := GetEthAddress(privKey) // address the privKey controls controlsAddress, err := u.controlsAddress(address) if err != nil { return err diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index baf6106..5bb8ac0 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/go-ethereum/common" + ethcrypto "github.com/ava-labs/go-ethereum/crypto" "github.com/ava-labs/go-ethereum/rlp" "github.com/ava-labs/go-ethereum/rpc" avarpc "github.com/gorilla/rpc/v2" @@ -34,6 +35,7 @@ import ( "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/snow/consensus/snowman" "github.com/ava-labs/gecko/utils/codec" + "github.com/ava-labs/gecko/utils/crypto" avajson "github.com/ava-labs/gecko/utils/json" "github.com/ava-labs/gecko/utils/timer" "github.com/ava-labs/gecko/utils/wrappers" @@ -48,6 +50,7 @@ var ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } + x2cRate = big.NewInt(1000000000) ) const ( @@ -160,6 +163,15 @@ func (vm *VM) getAtomicTx(block *types.Block) *Tx { return atx } +func importTxStateTransfer(tx *UnsignedImportTx, 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) + } +} + /* ****************************************************************************** ********************************* Snowman API ******************************** @@ -213,11 +225,7 @@ func (vm *VM) Initialize( chain.SetOnFinalizeAndAssemble(func(state *state.StateDB, txs []*types.Transaction) ([]byte, error) { select { case atx := <-vm.pendingAtomicTxs: - for _, to := range atx.UnsignedTx.(*UnsignedImportTx).Outs { - amount := new(big.Int) - amount.SetUint64(to.Amount) - state.AddBalance(to.Address, amount) - } + importTxStateTransfer(atx.UnsignedTx.(*UnsignedImportTx), state) raw, _ := vm.codec.Marshal(atx) return raw, nil default: @@ -250,13 +258,9 @@ func (vm *VM) Initialize( chain.SetOnQueryAcceptedBlock(func() *types.Block { return vm.getLastAccepted().ethBlock }) - chain.SetOnExtraStateChange(func(block *types.Block, statedb *state.StateDB) error { + chain.SetOnExtraStateChange(func(block *types.Block, state *state.StateDB) error { atx := vm.getAtomicTx(block).UnsignedTx.(*UnsignedImportTx) - for _, to := range atx.Outs { - amount := new(big.Int) - amount.SetUint64(to.Amount) - statedb.AddBalance(to.Address, amount) - } + importTxStateTransfer(atx, state) return nil }) vm.blockCache = cache.LRU{Size: 2048} @@ -683,3 +687,38 @@ func (vm *VM) GetAtomicUTXOs( }} return utxos, ids.ShortEmpty, ids.Empty, nil } + +func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address { + return ethcrypto.PubkeyToAddress( + (*privKey.PublicKey().(*crypto.PublicKeySECP256K1R).ToECDSA())) +} + +func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount uint64) ([]EVMInput, [][]*crypto.PrivateKeySECP256K1R, error) { + // NOTE: should we use HEAD block or lastAccepted? + state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock) + if err != nil { + return nil, nil, err + } + inputs := []EVMInput{} + signers := [][]*crypto.PrivateKeySECP256K1R{} + for _, key := range keys { + if amount == 0 { + break + } + addr := GetEthAddress(key) + balance := new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64() + if amount < balance { + balance = amount + } + inputs = append(inputs, EVMInput{ + Address: addr, + Amount: balance, + }) + signers = append(signers, []*crypto.PrivateKeySECP256K1R{key}) + amount -= balance + } + if amount > 0 { + return nil, nil, errInsufficientFunds + } + return inputs, signers, nil +} -- cgit v1.2.3