aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
authorDeterminant <tederminant@gmail.com>2020-08-19 01:25:20 -0400
committerDeterminant <tederminant@gmail.com>2020-08-19 01:25:20 -0400
commit0844c8c6919f6d98ebe8a9501360ef5bc362dff3 (patch)
tree08051cb62d585f4ed1ec1a1200ceb079201e8337 /plugin
parentb989b5f949424f72b125cbec460824b94b7c55ab (diff)
catch up with the new P-Chain cross-chain impl
Diffstat (limited to 'plugin')
-rw-r--r--plugin/evm/atomic_tx.go64
-rw-r--r--plugin/evm/base_tx.go42
-rw-r--r--plugin/evm/block.go21
-rw-r--r--plugin/evm/factory.go8
-rw-r--r--plugin/evm/import_tx.go156
-rw-r--r--plugin/evm/service.go56
-rw-r--r--plugin/evm/tx.go105
-rw-r--r--plugin/evm/user.go26
-rw-r--r--plugin/evm/vm.go83
9 files changed, 310 insertions, 251 deletions
diff --git a/plugin/evm/atomic_tx.go b/plugin/evm/atomic_tx.go
deleted file mode 100644
index e8e48f7..0000000
--- a/plugin/evm/atomic_tx.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package evm
-
-import (
- "fmt"
-
- "github.com/ava-labs/gecko/database"
- "github.com/ava-labs/gecko/ids"
- "github.com/ava-labs/gecko/utils/crypto"
- "github.com/ava-labs/gecko/utils/hashing"
- "github.com/ava-labs/gecko/vms/components/verify"
- "github.com/ava-labs/gecko/vms/secp256k1fx"
-)
-
-// UnsignedAtomicTx ...
-type UnsignedAtomicTx interface {
- initialize(vm *VM, bytes []byte) error
- ID() ids.ID
- // UTXOs this tx consumes
- InputUTXOs() ids.Set
- // Attempts to verify this transaction with the provided state.
- SemanticVerify(db database.Database, creds []verify.Verifiable) TxError
- Accept(database.Batch) error
-}
-
-// AtomicTx is an operation that can be decided without being proposed, but must
-// have special control over database commitment
-type AtomicTx struct {
- UnsignedAtomicTx `serialize:"true"`
- // Credentials that authorize the inputs to be spent
- Credentials []verify.Verifiable `serialize:"true" json:"credentials"`
-}
-
-func (vm *VM) signAtomicTx(tx *AtomicTx, signers [][]*crypto.PrivateKeySECP256K1R) error {
- unsignedBytes, err := vm.codec.Marshal(tx.UnsignedAtomicTx)
- if err != nil {
- return fmt.Errorf("couldn't marshal UnsignedAtomicTx: %w", err)
- }
-
- // Attach credentials
- hash := hashing.ComputeHash256(unsignedBytes)
- tx.Credentials = make([]verify.Verifiable, len(signers))
- for i, credKeys := range signers {
- cred := &secp256k1fx.Credential{
- Sigs: make([][crypto.SECP256K1RSigLen]byte, len(credKeys)),
- }
- for j, key := range credKeys {
- sig, err := key.SignHash(hash) // Sign hash
- if err != nil {
- return fmt.Errorf("problem generating credential: %w", err)
- }
- copy(cred.Sigs[j][:], sig)
- }
- tx.Credentials[i] = cred // Attach credential
- }
-
- txBytes, err := vm.codec.Marshal(tx)
- if err != nil {
- return fmt.Errorf("couldn't marshal AtomicTx: %w", err)
- }
- return tx.initialize(vm, txBytes)
-}
diff --git a/plugin/evm/base_tx.go b/plugin/evm/base_tx.go
index 7fd5a31..b3e85cd 100644
--- a/plugin/evm/base_tx.go
+++ b/plugin/evm/base_tx.go
@@ -2,10 +2,10 @@ package evm
import (
"errors"
- "fmt"
"github.com/ava-labs/gecko/ids"
- "github.com/ava-labs/gecko/vms/components/ava"
+ "github.com/ava-labs/gecko/snow"
+ "github.com/ava-labs/gecko/vms/components/avax"
"github.com/ava-labs/go-ethereum/common"
)
@@ -37,16 +37,9 @@ func (out *EVMOutput) Verify() error {
// struct's serialized fields without doing the same on avm.BaseTx.
// TODO: Factor out this and avm.BaseTX
type BaseTx struct {
- vm *VM
+ avax.Metadata
// true iff this transaction has already passed syntactic verification
syntacticallyVerified bool
- // ID of this tx
- id ids.ID
- // Byte representation of this unsigned tx
- unsignedBytes []byte
- // Byte representation of the signed transaction (ie with credentials)
- bytes []byte
-
// 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.
@@ -55,38 +48,22 @@ type BaseTx struct {
// Outputs
Outs []EVMOutput `serialize:"true" json:"outputs"`
// Inputs consumed by this tx
- Ins []*ava.TransferableInput `serialize:"true" json:"inputs"`
+ Ins []*avax.TransferableInput `serialize:"true" json:"inputs"`
// Memo field contains arbitrary bytes, up to maxMemoSize
Memo []byte `serialize:"true" json:"memo"`
}
-// UnsignedBytes returns the byte representation of this unsigned tx
-func (tx *BaseTx) UnsignedBytes() []byte { return tx.unsignedBytes }
-
-// Bytes returns the byte representation of this tx
-func (tx *BaseTx) Bytes() []byte { return tx.bytes }
-
-// ID returns this transaction's ID
-func (tx *BaseTx) ID() ids.ID { return tx.id }
-
// Verify returns nil iff this tx is well formed
-func (tx *BaseTx) Verify() error {
+func (tx *BaseTx) Verify(ctx *snow.Context) error {
switch {
case tx == nil:
return errNilTx
case tx.syntacticallyVerified: // already passed syntactic verification
return nil
- case tx.id.IsZero():
- return errInvalidID
- case tx.vm == nil:
- return errVMNil
- case tx.NetworkID != tx.vm.ctx.NetworkID:
+ case tx.NetworkID != ctx.NetworkID:
return errWrongNetworkID
- case !tx.vm.ctx.ChainID.Equals(tx.BlockchainID):
+ case !ctx.ChainID.Equals(tx.BlockchainID):
return errWrongBlockchainID
- case len(tx.Memo) > maxMemoSize:
- return fmt.Errorf("memo length, %d, exceeds maximum memo length, %d",
- len(tx.Memo), maxMemoSize)
}
for _, out := range tx.Outs {
if err := out.Verify(); err != nil {
@@ -99,9 +76,8 @@ func (tx *BaseTx) Verify() error {
}
}
switch {
- //case !ava.IsSortedTransferableOutputs(tx.Outs, Codec):
- // return errOutputsNotSorted
- case !ava.IsSortedAndUniqueTransferableInputs(tx.Ins):
+ // TODO: check whether output addreses are sorted?
+ case !avax.IsSortedAndUniqueTransferableInputs(tx.Ins):
return errInputsNotSortedUnique
default:
return nil
diff --git a/plugin/evm/block.go b/plugin/evm/block.go
index 449e261..9c15834 100644
--- a/plugin/evm/block.go
+++ b/plugin/evm/block.go
@@ -61,6 +61,27 @@ func (b *Block) Parent() snowman.Block {
// Verify implements the snowman.Block interface
func (b *Block) Verify() error {
+ p := b
+ path := []*Block{}
+ for {
+ if p.Status() == choices.Accepted {
+ break
+ }
+ path = append(path, p)
+ p = p.Parent().(*Block)
+ }
+ inputs := new(ids.Set)
+ for i := len(path) - 1; i >= 0; i-- {
+ p := path[i]
+ atx := p.vm.getAtomicTx(p.ethBlock)
+ inputs.Union(atx.UnsignedTx.(UnsignedAtomicTx).InputUTXOs())
+ }
+ tx := b.vm.getAtomicTx(b.ethBlock)
+ atx := tx.UnsignedTx.(*UnsignedImportTx)
+ if atx.SemanticVerify(b.vm, tx) != nil {
+ return errInvalidBlock
+ }
+
_, err := b.vm.chain.InsertChain([]*types.Block{b.ethBlock})
return err
}
diff --git a/plugin/evm/factory.go b/plugin/evm/factory.go
index 31a617a..ae2ea27 100644
--- a/plugin/evm/factory.go
+++ b/plugin/evm/factory.go
@@ -14,14 +14,16 @@ var (
// Factory ...
type Factory struct {
- AVA ids.ID
- Fee uint64
+ AVAX ids.ID
+ AVM ids.ID
+ Fee uint64
}
// New ...
func (f *Factory) New() interface{} {
return &VM{
- avaxAssetID: f.AVA,
+ avaxAssetID: f.AVAX,
+ avm: f.AVM,
txFee: f.Fee,
}
}
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)
}
diff --git a/plugin/evm/service.go b/plugin/evm/service.go
index 75e7c31..c05b0a4 100644
--- a/plugin/evm/service.go
+++ b/plugin/evm/service.go
@@ -16,6 +16,8 @@ import (
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/gecko/api"
"github.com/ava-labs/gecko/utils/constants"
+ avacrypto "github.com/ava-labs/gecko/utils/crypto"
+ "github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/common/hexutil"
"github.com/ava-labs/go-ethereum/crypto"
@@ -147,13 +149,12 @@ func (service *AvaAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *Ex
return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err)
}
user := user{db: db}
- if address, err := service.vm.ParseAddress(args.Address); err != nil {
+ if address, err := service.vm.ParseLocalAddress(args.Address); err != nil {
return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err)
} else if sk, err := user.getKey(address); err != nil {
return fmt.Errorf("problem retrieving private key: %w", err)
} else {
- reply.PrivateKey = common.ToHex(crypto.FromECDSA(sk))
- //constants.SecretKeyPrefix + formatting.CB58{Bytes: sk.Bytes()}.String()
+ reply.PrivateKey = constants.SecretKeyPrefix + formatting.CB58{Bytes: sk.Bytes()}.String()
return nil
}
}
@@ -167,6 +168,10 @@ type ImportKeyArgs struct {
// ImportKey adds a private key to the provided user
func (service *AvaAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *api.JsonAddress) error {
service.vm.ctx.Log.Info("Platform: ImportKey called for user '%s'", args.Username)
+ if service.vm.ctx.Keystore == nil {
+ return fmt.Errorf("oh no")
+ }
+ fmt.Sprintf("good")
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("problem retrieving data: %w", err)
@@ -174,35 +179,56 @@ func (service *AvaAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *ap
user := user{db: db}
+ factory := avacrypto.FactorySECP256K1R{}
+
if !strings.HasPrefix(args.PrivateKey, constants.SecretKeyPrefix) {
return fmt.Errorf("private key missing %s prefix", constants.SecretKeyPrefix)
}
- sk, err := crypto.ToECDSA(common.FromHex(args.PrivateKey))
+ trimmedPrivateKey := strings.TrimPrefix(args.PrivateKey, constants.SecretKeyPrefix)
+ formattedPrivateKey := formatting.CB58{}
+ if err := formattedPrivateKey.FromString(trimmedPrivateKey); err != nil {
+ return fmt.Errorf("problem parsing private key: %w", err)
+ }
+
+ skIntf, err := factory.ToPrivateKey(formattedPrivateKey.Bytes)
if err != nil {
- return fmt.Errorf("invalid private key")
+ return fmt.Errorf("problem parsing private key: %w", err)
}
- if err = user.putAddress(sk); err != nil {
+ sk := skIntf.(*avacrypto.PrivateKeySECP256K1R)
+
+ if err := user.putAddress(sk); err != nil {
return fmt.Errorf("problem saving key %w", err)
}
- reply.Address, err = service.vm.FormatAddress(crypto.PubkeyToAddress(sk.PublicKey))
+ // TODO: return eth address here
+ reply.Address, err = service.vm.FormatAddress(
+ crypto.PubkeyToAddress(*(sk.PublicKey().(*avacrypto.PublicKeySECP256K1R).ToECDSA())))
if err != nil {
return fmt.Errorf("problem formatting address: %w", err)
}
return nil
}
-// ImportAVAArgs are the arguments to ImportAVA
-type ImportAVAArgs struct {
+// ImportAVAXArgs are the arguments to ImportAVAX
+type ImportAVAXArgs struct {
api.UserPass
+
+ // Chain the funds are coming from
+ SourceChain string `json:"sourceChain"`
+
// The address that will receive the imported funds
To string `json:"to"`
}
-// ImportAVA returns an unsigned transaction to import AVA from the X-Chain.
-// The AVA must have already been exported from the X-Chain.
-func (service *AvaAPI) ImportAVA(_ *http.Request, args *ImportAVAArgs, response *api.JsonTxID) error {
- service.vm.ctx.Log.Info("Platform: ImportAVA called")
+// ImportAVAX issues a transaction to import AVAX from the X-chain. The AVAX
+// must have already been exported from the X-Chain.
+func (service *AvaAPI) ImportAVAX(_ *http.Request, args *ImportAVAXArgs, response *api.JsonTxID) error {
+ service.vm.ctx.Log.Info("Platform: ImportAVAX called")
+
+ chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain)
+ if err != nil {
+ return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err)
+ }
// Get the user's info
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
@@ -211,7 +237,7 @@ func (service *AvaAPI) ImportAVA(_ *http.Request, args *ImportAVAArgs, response
}
user := user{db: db}
- to, err := service.vm.ParseAddress(args.To)
+ to, err := service.vm.ParseLocalAddress(args.To)
if err != nil { // Parse address
return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err)
}
@@ -221,7 +247,7 @@ func (service *AvaAPI) ImportAVA(_ *http.Request, args *ImportAVAArgs, response
return fmt.Errorf("couldn't get keys controlled by the user: %w", err)
}
- tx, err := service.vm.newImportTx(to, privKeys)
+ tx, err := service.vm.newImportTx(chainID, to, privKeys)
if err != nil {
return err
}
diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go
new file mode 100644
index 0000000..024e54d
--- /dev/null
+++ b/plugin/evm/tx.go
@@ -0,0 +1,105 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+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"
+ "github.com/ava-labs/gecko/utils/crypto"
+ "github.com/ava-labs/gecko/utils/hashing"
+ "github.com/ava-labs/gecko/vms/components/verify"
+ "github.com/ava-labs/gecko/vms/secp256k1fx"
+)
+
+// UnsignedTx is an unsigned transaction
+type UnsignedTx interface {
+ Initialize(unsignedBytes, signedBytes []byte)
+ ID() ids.ID
+ UnsignedBytes() []byte
+ 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
+
+ // 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
+
+ // Accept this transaction with the additionally provided state transitions.
+ Accept(ctx *snow.Context, batch database.Batch) error
+}
+
+// Tx is a signed transaction
+type Tx struct {
+ // The body of this transaction
+ UnsignedTx `serialize:"true" json:"unsignedTx"`
+
+ // The credentials of this transaction
+ Creds []verify.Verifiable `serialize:"true" json:"credentials"`
+}
+
+// Sign this transaction with the provided signers
+func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) error {
+ unsignedBytes, err := c.Marshal(&tx.UnsignedTx)
+ if err != nil {
+ return fmt.Errorf("couldn't marshal UnsignedTx: %w", err)
+ }
+
+ // Attach credentials
+ hash := hashing.ComputeHash256(unsignedBytes)
+ for _, keys := range signers {
+ cred := &secp256k1fx.Credential{
+ Sigs: make([][crypto.SECP256K1RSigLen]byte, len(keys)),
+ }
+ for i, key := range keys {
+ sig, err := key.SignHash(hash) // Sign hash
+ if err != nil {
+ return fmt.Errorf("problem generating credential: %w", err)
+ }
+ copy(cred.Sigs[i][:], sig)
+ }
+ tx.Creds = append(tx.Creds, cred) // Attach credential
+ }
+
+ signedBytes, err := c.Marshal(tx)
+ if err != nil {
+ return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
+ }
+ tx.Initialize(unsignedBytes, signedBytes)
+ return nil
+}
diff --git a/plugin/evm/user.go b/plugin/evm/user.go
index 651f202..5d7a037 100644
--- a/plugin/evm/user.go
+++ b/plugin/evm/user.go
@@ -5,12 +5,13 @@ package evm
import (
"errors"
+ "fmt"
- "crypto/ecdsa"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
+ "github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/go-ethereum/common"
- "github.com/ava-labs/go-ethereum/crypto"
+ ethcrypto "github.com/ava-labs/go-ethereum/crypto"
)
// Key in the database whose corresponding value is the list of
@@ -66,12 +67,13 @@ func (u *user) controlsAddress(address common.Address) (bool, error) {
}
// putAddress persists that this user controls address controlled by [privKey]
-func (u *user) putAddress(privKey *ecdsa.PrivateKey) error {
+func (u *user) putAddress(privKey *crypto.PrivateKeySECP256K1R) error {
if privKey == nil {
return errKeyNil
}
- address := crypto.PubkeyToAddress(privKey.PublicKey) // address the privKey controls
+ address := ethcrypto.PubkeyToAddress(
+ (*privKey.PublicKey().(*crypto.PublicKeySECP256K1R).ToECDSA())) // address the privKey controls
controlsAddress, err := u.controlsAddress(address)
if err != nil {
return err
@@ -80,7 +82,7 @@ func (u *user) putAddress(privKey *ecdsa.PrivateKey) error {
return nil
}
- if err := u.db.Put(address.Bytes(), crypto.FromECDSA(privKey)); err != nil { // Address --> private key
+ if err := u.db.Put(address.Bytes(), privKey.Bytes()); err != nil { // Address --> private key
return err
}
@@ -106,31 +108,35 @@ func (u *user) putAddress(privKey *ecdsa.PrivateKey) error {
}
// Key returns the private key that controls the given address
-func (u *user) getKey(address common.Address) (*ecdsa.PrivateKey, error) {
+func (u *user) getKey(address common.Address) (*crypto.PrivateKeySECP256K1R, error) {
if u.db == nil {
return nil, errDBNil
//} else if address.IsZero() {
// return nil, errEmptyAddress
}
+ factory := crypto.FactorySECP256K1R{}
bytes, err := u.db.Get(address.Bytes())
if err != nil {
return nil, err
}
- sk, err := crypto.ToECDSA(bytes)
+ sk, err := factory.ToPrivateKey(bytes)
if err != nil {
return nil, err
}
- return sk, nil
+ if sk, ok := sk.(*crypto.PrivateKeySECP256K1R); ok {
+ return sk, nil
+ }
+ return nil, fmt.Errorf("expected private key to be type *crypto.PrivateKeySECP256K1R but is type %T", sk)
}
// Return all private keys controlled by this user
-func (u *user) getKeys() ([]*ecdsa.PrivateKey, error) {
+func (u *user) getKeys() ([]*crypto.PrivateKeySECP256K1R, error) {
addrs, err := u.getAddresses()
if err != nil {
return nil, err
}
- keys := make([]*ecdsa.PrivateKey, len(addrs))
+ keys := make([]*crypto.PrivateKeySECP256K1R, len(addrs))
for i, addr := range addrs {
key, err := u.getKey(addr)
if err != nil {
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index 1010d60..4fe4cc0 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -16,6 +16,7 @@ import (
"github.com/ava-labs/coreth"
"github.com/ava-labs/coreth/core"
+ "github.com/ava-labs/coreth/core/state"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/eth"
"github.com/ava-labs/coreth/node"
@@ -33,10 +34,12 @@ 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/constants"
+ //"github.com/ava-labs/gecko/utils/formatting"
avajson "github.com/ava-labs/gecko/utils/json"
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/utils/wrappers"
- "github.com/ava-labs/gecko/vms/components/ava"
+ "github.com/ava-labs/gecko/vms/components/avax"
commonEng "github.com/ava-labs/gecko/snow/engine/common"
)
@@ -69,13 +72,14 @@ const (
)
var (
- errEmptyBlock = errors.New("empty block")
- errCreateBlock = errors.New("couldn't create block")
- errUnknownBlock = errors.New("unknown block")
- errBlockFrequency = errors.New("too frequent block issuance")
- errUnsupportedFXs = errors.New("unsupported feature extensions")
- errInvalidBlock = errors.New("invalid block")
- errInvalidAddr = errors.New("invalid hex address")
+ errEmptyBlock = errors.New("empty block")
+ errCreateBlock = errors.New("couldn't create block")
+ errUnknownBlock = errors.New("unknown block")
+ errBlockFrequency = errors.New("too frequent block issuance")
+ errUnsupportedFXs = errors.New("unsupported feature extensions")
+ errInvalidBlock = errors.New("invalid block")
+ errInvalidAddr = errors.New("invalid hex address")
+ errTooManyAtomicTx = errors.New("too many pending atomix txs")
)
func maxDuration(x, y time.Duration) time.Duration {
@@ -128,13 +132,25 @@ type VM struct {
bdGenWaitFlag bool
bdGenFlag bool
- genlock sync.Mutex
- txSubmitChan <-chan struct{}
- codec codec.Codec
- clock timer.Clock
- avaxAssetID ids.ID
- txFee uint64
- //atomicTxPool []
+ genlock sync.Mutex
+ txSubmitChan <-chan struct{}
+ codec codec.Codec
+ clock timer.Clock
+ avaxAssetID ids.ID
+ avm ids.ID
+ txFee uint64
+ pendingAtomicTxs chan *Tx
+ blockAtomicInputCache cache.LRU
+}
+
+func (vm *VM) getAtomicTx(block *types.Block) *Tx {
+ var atx *Tx
+ if extdata := block.ExtraData(); extdata != nil {
+ if err := vm.codec.Unmarshal(block.ExtraData(), atx); err != nil {
+ panic(err)
+ }
+ }
+ return atx
}
/*
@@ -191,6 +207,11 @@ func (vm *VM) Initialize(
vm.newBlockChan <- nil
return errEmptyBlock
}
+ select {
+ case atx := <-vm.pendingAtomicTxs:
+ raw, _ := vm.codec.Marshal(atx)
+ block.SetExtraData(raw)
+ }
return nil
})
chain.SetOnSealFinish(func(block *types.Block) error {
@@ -211,8 +232,14 @@ func (vm *VM) Initialize(
chain.SetOnQueryAcceptedBlock(func() *types.Block {
return vm.getLastAccepted().ethBlock
})
+ chain.SetOnExtraStateChange(func(block *types.Block, statedb *state.StateDB) error {
+ atx := vm.getAtomicTx(block).UnsignedTx.(*UnsignedImportTx)
+ vm.ctx.Log.Info(atx.ID().String())
+ return nil
+ })
vm.blockCache = cache.LRU{Size: 2048}
vm.blockStatusCache = cache.LRU{Size: 1024}
+ vm.blockAtomicInputCache = cache.LRU{Size: 4096}
vm.newBlockChan = make(chan *Block)
vm.networkChan = toEngine
vm.blockDelayTimer = timer.NewTimer(func() {
@@ -236,6 +263,8 @@ func (vm *VM) Initialize(
vm.bdGenWaitFlag = true
vm.newTxPoolHeadChan = make(chan core.NewTxPoolHeadEvent, 1)
vm.txPoolStabilizedOk = make(chan struct{}, 1)
+ // TODO: read size from options
+ vm.pendingAtomicTxs = make(chan *Tx, 1024)
chain.GetTxPool().SubscribeNewHeadEvent(vm.newTxPoolHeadChan)
// TODO: shutdown this go routine
go ctx.Log.RecoverAndPanic(func() {
@@ -419,7 +448,7 @@ func (vm *VM) CreateHandlers() map[string]*commonEng.HTTPHandler {
return map[string]*commonEng.HTTPHandler{
"/rpc": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler},
- "/ava": newHandler("", &AvaAPI{vm}),
+ "/ava": newHandler("ava", &AvaAPI{vm}),
"/ws": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler.WebsocketHandler([]string{"*"})},
}
}
@@ -584,7 +613,8 @@ func (vm *VM) getLastAccepted() *Block {
return vm.lastAccepted
}
-func (vm *VM) ParseAddress(addrStr string) (common.Address, error) {
+// ParseLocalAddress takes in an address for this chain and produces the ID
+func (vm *VM) ParseLocalAddress(addrStr string) (common.Address, error) {
if !common.IsHexAddress(addrStr) {