aboutsummaryrefslogtreecommitdiff
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
parentb989b5f949424f72b125cbec460824b94b7c55ab (diff)
catch up with the new P-Chain cross-chain impl
-rw-r--r--go.mod1
-rw-r--r--go.sum10
-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
11 files changed, 321 insertions, 251 deletions
diff --git a/go.mod b/go.mod
index 752be2f..5ef12a7 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
github.com/edsrzf/mmap-go v1.0.0
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
+ github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/golang/snappy v0.0.1
github.com/gorilla/rpc v1.2.0
github.com/gorilla/websocket v1.4.2
diff --git a/go.sum b/go.sum
index 832a8bf..d4c1885 100644
--- a/go.sum
+++ b/go.sum
@@ -74,6 +74,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
+github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -108,11 +110,14 @@ github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36j
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd h1:rNuUHR+CvK1IS89MMtcF0EpcVMZtjKfPRp4MEmt/aTs=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
+github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8=
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
@@ -123,6 +128,7 @@ github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQ
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -153,6 +159,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -163,6 +170,7 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
+github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
@@ -313,6 +321,7 @@ google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 h1:jB9+PJSvu5tBfmJHy/OVapFdjDF3WvpkqRhxqrmzoEU=
google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -321,6 +330,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
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{}