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) {
return common.Address{}, errInvalidAddr
}
@@ -595,14 +625,25 @@ func (vm *VM) FormatAddress(addr common.Address) (string, error) {
return addr.Hex(), nil
}
-func (vm *VM) issueTx(tx *AtomicTx) error {
+func (vm *VM) issueTx(tx *Tx) error {
+ select {
+ case vm.pendingAtomicTxs <- tx:
+ default:
+ return errTooManyAtomicTx
+ }
return nil
}
// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is
// referenced in.
-func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) {
+func (vm *VM) GetAtomicUTXOs(
+ chainID ids.ID,
+ addrs ids.ShortSet,
+ startAddr ids.ShortID,
+ startUTXOID ids.ID,
+ limit int,
+) ([]*avax.UTXO, ids.ShortID, ids.ID, error) {
// TODO: finish this function via gRPC
- utxos := []*ava.UTXO{}
- return utxos, nil
+ utxos := []*avax.UTXO{}
+ return utxos, ids.ShortEmpty, ids.Empty, nil
}
#n1746'>1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911