aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Buttolph <[email protected]>2020-08-25 13:49:24 -0400
committerGitHub <[email protected]>2020-08-25 13:49:24 -0400
commit5f9c76b5f4eb5bb08b8d3e3125b223f3737631d3 (patch)
tree9de0af77831340eb38c28c613849f1e7b65f3a54
parent1bb314f767785fe617c3c5efeca1a64127339506 (diff)
parentb05d11d451fb73843abcf6bd5fc8664b069433a4 (diff)
Merge pull request #11 from ava-labs/TS_fix_gasv0.2.13
Fix gas handling
-rw-r--r--consensus/clique/clique.go2
-rw-r--r--consensus/dummy/consensus.go11
-rw-r--r--consensus/ethash/consensus.go2
-rw-r--r--core/blockchain.go4
-rw-r--r--core/genesis.go2
-rw-r--r--core/rawdb/accessors_chain.go2
-rw-r--r--core/types/block.go14
-rw-r--r--go.mod4
-rw-r--r--go.sum13
-rw-r--r--miner/worker.go1
-rw-r--r--plugin/evm/block.go87
-rw-r--r--plugin/evm/error.go19
-rw-r--r--plugin/evm/export_tx.go223
-rw-r--r--plugin/evm/factory.go10
-rw-r--r--plugin/evm/import_tx.go272
-rw-r--r--plugin/evm/service.go194
-rw-r--r--plugin/evm/tx.go112
-rw-r--r--plugin/evm/user.go146
-rw-r--r--plugin/evm/vm.go373
19 files changed, 1434 insertions, 57 deletions
diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go
index d3ad1d9..3714733 100644
--- a/consensus/clique/clique.go
+++ b/consensus/clique/clique.go
@@ -566,7 +566,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainReader, header *types.
header.UncleHash = types.CalcUncleHash(nil)
// Assemble and return the final block for sealing
- return types.NewBlock(header, txs, nil, receipts), nil
+ return types.NewBlock(header, txs, nil, receipts, nil), nil
}
// Authorize injects a private key into the consensus engine to mint new blocks
diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go
index 9d8db1e..494e4be 100644
--- a/consensus/dummy/consensus.go
+++ b/consensus/dummy/consensus.go
@@ -19,7 +19,7 @@ import (
)
type OnFinalizeCallbackType = func(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header)
-type OnFinalizeAndAssembleCallbackType = func(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt)
+type OnFinalizeAndAssembleCallbackType = func(state *state.StateDB, txs []*types.Transaction) ([]byte, error)
type OnAPIsCallbackType = func(consensus.ChainReader) []rpc.API
type OnExtraStateChangeType = func(block *types.Block, statedb *state.StateDB) error
@@ -261,14 +261,19 @@ func (self *DummyEngine) Finalize(
func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
+ var extdata []byte
if self.cb.OnFinalizeAndAssemble != nil {
- self.cb.OnFinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
+ ret, err := self.cb.OnFinalizeAndAssemble(state, txs)
+ extdata = ret
+ if err != nil {
+ return nil, err
+ }
}
// commit the final state root
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return
- return types.NewBlock(header, txs, uncles, receipts), nil
+ return types.NewBlock(header, txs, uncles, receipts, extdata), nil
}
func (self *DummyEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) (err error) {
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index c325eea..dc88a79 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -576,7 +576,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainReader, header *t
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return
- return types.NewBlock(header, txs, uncles, receipts), nil
+ return types.NewBlock(header, txs, uncles, receipts, nil), nil
}
// SealHash returns the hash of a block prior to it being sealed.
diff --git a/core/blockchain.go b/core/blockchain.go
index 64c2451..6e316c7 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -732,6 +732,8 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
// GetBlockByHash retrieves a block from the database by hash, caching it if found.
func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
+ bc.chainmu.Lock()
+ defer bc.chainmu.Unlock()
number := bc.hc.GetBlockNumber(hash)
if number == nil {
return nil
@@ -742,6 +744,8 @@ func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
// GetBlockByNumber retrieves a block from the database by number, caching it
// (associated with its hash) if found.
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
+ bc.chainmu.Lock()
+ defer bc.chainmu.Unlock()
hash := rawdb.ReadCanonicalHash(bc.db, number)
if hash == (common.Hash{}) {
return nil
diff --git a/core/genesis.go b/core/genesis.go
index ef490bf..7d21d00 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -294,7 +294,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true)
- return types.NewBlock(head, nil, nil, nil)
+ return types.NewBlock(head, nil, nil, nil, nil)
}
// Commit writes the block and state of a genesis specification to the database.
diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go
index 7620eac..fdfd6ec 100644
--- a/core/rawdb/accessors_chain.go
+++ b/core/rawdb/accessors_chain.go
@@ -475,7 +475,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil {
return nil
}
- return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles)
+ return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles, body.Version, body.ExtData)
}
// WriteBlock serializes a block into the database, header and body separately.
diff --git a/core/types/block.go b/core/types/block.go
index b3dbcd3..4096d86 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -144,6 +144,8 @@ func rlpHash(x interface{}) (h common.Hash) {
type Body struct {
Transactions []*Transaction
Uncles []*Header
+ Version uint32
+ ExtData []byte `rlp:"nil"`
}
// Block represents an entire block in the Ethereum blockchain.
@@ -212,7 +214,7 @@ type storageblock struct {
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles
// and receipts.
-func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
+func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, extdata []byte) *Block {
b := &Block{header: CopyHeader(header), td: new(big.Int)}
// TODO: panic if len(txs) != len(receipts)
@@ -241,6 +243,9 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
}
}
+ b.extdata = make([]byte, len(extdata))
+ copy(b.extdata, extdata)
+
return b
}
@@ -387,7 +392,7 @@ func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Ext
func (b *Block) Header() *Header { return CopyHeader(b.header) }
// Body returns the non-header content of the block.
-func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
+func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.version, b.extdata} }
// Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previsouly cached value.
@@ -434,13 +439,16 @@ func (b *Block) WithSeal(header *Header) *Block {
}
// WithBody returns a new block with the given transaction and uncle contents.
-func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
+func (b *Block) WithBody(transactions []*Transaction, uncles []*Header, version uint32, extdata []byte) *Block {
block := &Block{
header: CopyHeader(b.header),
transactions: make([]*Transaction, len(transactions)),
uncles: make([]*Header, len(uncles)),
+ extdata: make([]byte, len(extdata)),
+ version: version,
}
copy(block.transactions, transactions)
+ copy(block.extdata, extdata)
for i := range uncles {
block.uncles[i] = CopyHeader(uncles[i])
}
diff --git a/go.mod b/go.mod
index ff6b943..1d8840c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/ava-labs/coreth
go 1.14
require (
- github.com/ava-labs/gecko v0.5.7
+ github.com/ava-labs/gecko v0.6.1-rc.1
github.com/ava-labs/go-ethereum v1.9.3
github.com/cespare/cp v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1
@@ -11,7 +11,9 @@ 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
github.com/hashicorp/go-plugin v1.3.0
github.com/hashicorp/golang-lru v0.5.4
diff --git a/go.sum b/go.sum
index f1916c2..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=
@@ -181,6 +189,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -222,6 +231,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -311,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=
@@ -319,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=
@@ -349,6 +361,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/miner/worker.go b/miner/worker.go
index feab0b2..4a6303b 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -751,6 +751,7 @@ func (w *worker) updateSnapshot() {
w.current.txs,
uncles,
w.current.receipts,
+ nil,
)
w.snapshotState = w.current.state.Copy()
diff --git a/plugin/evm/block.go b/plugin/evm/block.go
index 449e261..5c30fe1 100644
--- a/plugin/evm/block.go
+++ b/plugin/evm/block.go
@@ -4,6 +4,7 @@
package evm
import (
+ "errors"
"fmt"
"github.com/ava-labs/coreth/core/types"
@@ -12,6 +13,7 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowman"
+ "github.com/ava-labs/gecko/vms/components/missing"
)
// Block implements the snowman.Block interface
@@ -26,9 +28,21 @@ func (b *Block) ID() ids.ID { return b.id }
// Accept implements the snowman.Block interface
func (b *Block) Accept() error {
- b.vm.ctx.Log.Verbo("Block %s is accepted", b.ID())
- b.vm.updateStatus(b.ID(), choices.Accepted)
- return nil
+ vm := b.vm
+
+ vm.ctx.Log.Verbo("Block %s is accepted", b.ID())
+ vm.updateStatus(b.ID(), choices.Accepted)
+
+ tx := vm.getAtomicTx(b.ethBlock)
+ if tx == nil {
+ return nil
+ }
+ utx, ok := tx.UnsignedTx.(UnsignedAtomicTx)
+ if !ok {
+ return errors.New("unknown tx type")
+ }
+
+ return utx.Accept(vm.ctx, nil)
}
// Reject implements the snowman.Block interface
@@ -50,17 +64,72 @@ func (b *Block) Status() choices.Status {
// Parent implements the snowman.Block interface
func (b *Block) Parent() snowman.Block {
parentID := ids.NewID(b.ethBlock.ParentHash())
- block := &Block{
- id: parentID,
- ethBlock: b.vm.getCachedBlock(parentID),
- vm: b.vm,
+ if block := b.vm.getBlock(parentID); block != nil {
+ b.vm.ctx.Log.Verbo("Parent(%s) has status: %s", parentID, block.Status())
+ return block
}
- b.vm.ctx.Log.Verbo("Parent(%s) has status: %s", block.ID(), block.Status())
- return block
+ b.vm.ctx.Log.Verbo("Parent(%s) has status: %s", parentID, choices.Unknown)
+ return &missing.Block{BlkID: parentID}
}
// Verify implements the snowman.Block interface
func (b *Block) Verify() error {
+ // Only enforce a minimum fee when bootstrapping has finished
+ if b.vm.ctx.IsBootstrapped() {
+ // Ensure the minimum gas price is paid for every transaction
+ for _, tx := range b.ethBlock.Transactions() {
+ if tx.GasPrice().Cmp(minGasPrice) < 0 {
+ return errInvalidBlock
+ }
+ }
+ }
+
+ vm := b.vm
+ tx := vm.getAtomicTx(b.ethBlock)
+ if tx != nil {
+ switch atx := tx.UnsignedTx.(type) {
+ case *UnsignedImportTx:
+ if b.ethBlock.Hash() == vm.genesisHash {
+ return nil
+ }
+ p := b.Parent()
+ path := []*Block{}
+ inputs := new(ids.Set)
+ for {
+ if p.Status() == choices.Accepted || p.(*Block).ethBlock.Hash() == vm.genesisHash {
+ break
+ }
+ if ret, hit := vm.blockAtomicInputCache.Get(p.ID()); hit {
+ inputs = ret.(*ids.Set)
+ break
+ }
+ path = append(path, p.(*Block))
+ p = p.Parent().(*Block)
+ }
+ for i := len(path) - 1; i >= 0; i-- {
+ inputsCopy := new(ids.Set)
+ p := path[i]
+ atx := vm.getAtomicTx(p.ethBlock)
+ if atx != nil {
+ inputs.Union(atx.UnsignedTx.(UnsignedAtomicTx).InputUTXOs())
+ inputsCopy.Union(*inputs)
+ }
+ vm.blockAtomicInputCache.Put(p.ID(), inputsCopy)
+ }
+ for _, in := range atx.InputUTXOs().List() {
+ if inputs.Contains(in) {
+ return errInvalidBlock
+ }
+ }
+ case *UnsignedExportTx:
+ default:
+ return errors.New("unknown atomic tx type")
+ }
+
+ if tx.UnsignedTx.(UnsignedAtomicTx).SemanticVerify(vm, tx) != nil {
+ return errInvalidBlock
+ }
+ }
_, err := b.vm.chain.InsertChain([]*types.Block{b.ethBlock})
return err
}
diff --git a/plugin/evm/error.go b/plugin/evm/error.go
new file mode 100644
index 0000000..0554349
--- /dev/null
+++ b/plugin/evm/error.go
@@ -0,0 +1,19 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+// TxError provides the ability for errors to be distinguished as permenant or
+// temporary
+type TxError interface {
+ error
+ Temporary() bool
+}
+
+type tempError struct{ error }
+
+func (tempError) Temporary() bool { return true }
+
+type permError struct{ error }
+
+func (permError) Temporary() bool { return false }
diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go
new file mode 100644
index 0000000..31b4681
--- /dev/null
+++ b/plugin/evm/export_tx.go
@@ -0,0 +1,223 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ava-labs/coreth/core/state"
+ "github.com/ava-labs/go-ethereum/log"
+
+ "github.com/ava-labs/gecko/chains/atomic"
+ "github.com/ava-labs/gecko/database"
+ "github.com/ava-labs/gecko/ids"
+ "github.com/ava-labs/gecko/snow"
+ "github.com/ava-labs/gecko/utils/crypto"
+ safemath "github.com/ava-labs/gecko/utils/math"
+ "github.com/ava-labs/gecko/vms/components/avax"
+ "github.com/ava-labs/gecko/vms/secp256k1fx"
+)
+
+// UnsignedExportTx is an unsigned ExportTx
+type UnsignedExportTx struct {
+ avax.Metadata
+ // true iff this transaction has already passed syntactic verification
+ syntacticallyVerified bool
+ // ID of the network on which this tx was issued
+ NetworkID uint32 `serialize:"true" json:"networkID"`
+ // ID of this blockchain.
+ BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
+ // Which chain to send the funds to
+ DestinationChain ids.ID `serialize:"true" json:"destinationChain"`
+ // Inputs
+ Ins []EVMInput `serialize:"true" json:"inputs"`
+ // Outputs that are exported to the chain
+ ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"`
+}
+
+// InputUTXOs returns an empty set
+func (tx *UnsignedExportTx) InputUTXOs() ids.Set { return ids.Set{} }
+
+// Verify this transaction is well-formed
+func (tx *UnsignedExportTx) 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.DestinationChain.IsZero():
+ return errWrongChainID
+ case !tx.DestinationChain.Equals(avmID):
+ return errWrongChainID
+ case len(tx.ExportedOutputs) == 0:
+ return errNoExportOutputs
+ case tx.NetworkID != ctx.NetworkID:
+ return errWrongNetworkID
+ case !ctx.ChainID.Equals(tx.BlockchainID):
+ return errWrongBlockchainID
+ }
+
+ for _, in := range tx.Ins {
+ if err := in.Verify(); err != nil {
+ return err
+ }
+ }
+
+ for _, out := range tx.ExportedOutputs {
+ if err := out.Verify(); err != nil {
+ return err
+ }
+ }
+ if !avax.IsSortedTransferableOutputs(tx.ExportedOutputs, Codec) {
+ return errOutputsNotSorted
+ }
+
+ tx.syntacticallyVerified = true
+ return nil
+}
+
+// SemanticVerify this transaction is valid.
+func (tx *UnsignedExportTx) SemanticVerify(
+ vm *VM,
+ stx *Tx,
+) TxError {
+ if err := tx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil {
+ return permError{err}
+ }
+
+ f := crypto.FactorySECP256K1R{}
+ for i, cred := range stx.Creds {
+ if err := cred.Verify(); err != nil {
+ return permError{err}
+ }
+ pubKey, err := f.RecoverPublicKey(tx.UnsignedBytes(), cred.(*secp256k1fx.Credential).Sigs[0][:])
+ if err != nil {
+ return permError{err}
+ }
+ if tx.Ins[i].Address != PublicKeyToEthAddress(pubKey) {
+ return permError{errPublicKeySignatureMismatch}
+ }
+ }
+
+ // do flow-checking
+ fc := avax.NewFlowChecker()
+ fc.Produce(vm.ctx.AVAXAssetID, vm.txFee)
+
+ for _, out := range tx.ExportedOutputs {
+ fc.Produce(out.AssetID(), out.Output().Amount())
+ }
+
+ for _, in := range tx.Ins {
+ fc.Consume(vm.ctx.AVAXAssetID, in.Amount)
+ }
+
+ if err := fc.Verify(); err != nil {
+ return permError{err}
+ }
+
+ // TODO: verify UTXO outputs via gRPC
+ return nil
+}
+
+// Accept this transaction.
+func (tx *UnsignedExportTx) Accept(ctx *snow.Context, _ database.Batch) error {
+ txID := tx.ID()
+
+ elems := make([]*atomic.Element, len(tx.ExportedOutputs))
+ for i, out := range tx.ExportedOutputs {
+ utxo := &avax.UTXO{
+ UTXOID: avax.UTXOID{
+ TxID: txID,
+ OutputIndex: uint32(i),
+ },
+ Asset: avax.Asset{ID: out.AssetID()},
+ Out: out.Out,
+ }
+
+ utxoBytes, err := Codec.Marshal(utxo)
+ if err != nil {
+ return err
+ }
+
+ elem := &atomic.Element{
+ Key: utxo.InputID().Bytes(),
+ Value: utxoBytes,
+ }
+ if out, ok := utxo.Out.(avax.Addressable); ok {
+ elem.Traits = out.Addresses()
+ }
+
+ elems[i] = elem
+ }
+
+ return ctx.SharedMemory.Put(tx.DestinationChain, elems)
+}
+
+// Create a new transaction
+func (vm *VM) newExportTx(
+ amount uint64, // Amount of tokens to export
+ chainID ids.ID, // Chain to send the UTXOs to
+ to ids.ShortID, // Address of chain recipient
+ keys []*crypto.PrivateKeySECP256K1R, // Pay the fee and provide the tokens
+) (*Tx, error) {
+ if !vm.ctx.XChainID.Equals(chainID) {
+ return nil, errWrongChainID
+ }
+
+ toBurn, err := safemath.Add64(amount, vm.txFee)
+ if err != nil {
+ return nil, errOverflowExport
+ }
+ ins, signers, err := vm.GetSpendableCanonical(keys, toBurn)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err)
+ }
+
+ // Create the transaction
+ utx := &UnsignedExportTx{
+ NetworkID: vm.ctx.NetworkID,
+ BlockchainID: vm.ctx.ChainID,
+ DestinationChain: chainID,
+ Ins: ins,
+ ExportedOutputs: []*avax.TransferableOutput{{ // Exported to X-Chain
+ Asset: avax.Asset{ID: vm.ctx.AVAXAssetID},
+ Out: &secp256k1fx.TransferOutput{
+ Amt: amount,
+ OutputOwners: secp256k1fx.OutputOwners{
+ Locktime: 0,
+ Threshold: 1,
+ Addrs: []ids.ShortID{to},
+ },
+ },
+ }},
+ }
+ tx := &Tx{UnsignedTx: utx}
+ if err := tx.Sign(vm.codec, signers); err != nil {
+ return nil, err
+ }
+ return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID)
+}
+
+func (tx *UnsignedExportTx) EVMStateTransfer(state *state.StateDB) error {
+ for _, from := range tx.Ins {
+ log.Info("consume", "in", from.Address, "amount", from.Amount, "nonce", from.Nonce, "nonce0", state.GetNonce(from.Address))
+ amount := new(big.Int).Mul(
+ new(big.Int).SetUint64(from.Amount), x2cRate)
+ if state.GetBalance(from.Address).Cmp(amount) < 0 {
+ return errInsufficientFunds
+ }
+ state.SubBalance(from.Address, amount)
+ if state.GetNonce(from.Address) != from.Nonce {
+ return errInvalidNonce
+ }
+ state.SetNonce(from.Address, from.Nonce+1)
+ }
+ return nil
+}
diff --git a/plugin/evm/factory.go b/plugin/evm/factory.go
index a4c0eca..6ae62ae 100644
--- a/plugin/evm/factory.go
+++ b/plugin/evm/factory.go
@@ -13,7 +13,13 @@ var (
)
// Factory ...
-type Factory struct{}
+type Factory struct {
+ Fee uint64
+}
// New ...
-func (f *Factory) New() interface{} { return &VM{} }
+func (f *Factory) New() interface{} {
+ return &VM{
+ txFee: f.Fee,
+ }
+}
diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go
new file mode 100644
index 0000000..35ba8cc
--- /dev/null
+++ b/plugin/evm/import_tx.go
@@ -0,0 +1,272 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ava-labs/coreth/core/state"
+
+ "github.com/ava-labs/gecko/database"
+ "github.com/ava-labs/gecko/ids"
+ "github.com/ava-labs/gecko/snow"
+ "github.com/ava-labs/gecko/utils/crypto"
+ "github.com/ava-labs/gecko/utils/math"
+ "github.com/ava-labs/gecko/vms/components/avax"
+ "github.com/ava-labs/gecko/vms/secp256k1fx"
+ "github.com/ava-labs/go-ethereum/common"
+)
+
+// UnsignedImportTx is an unsigned ImportTx
+type UnsignedImportTx struct {
+ avax.Metadata
+ // true iff this transaction has already passed syntactic verification
+ syntacticallyVerified bool
+ // ID of the network on which this tx was issued
+ NetworkID uint32 `serialize:"true" json:"networkID"`
+ // ID of this blockchain.
+ BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
+ // Which chain to consume the funds from
+ SourceChain ids.ID `serialize:"true" json:"sourceChain"`
+ // Inputs that consume UTXOs produced on the chain
+ ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"`
+ // Outputs
+ Outs []EVMOutput `serialize:"true" json:"outputs"`
+}
+
+// InputUTXOs returns the UTXOIDs of the imported funds
+func (tx *UnsignedImportTx) InputUTXOs() ids.Set {
+ set := ids.Set{}
+ for _, in := range tx.ImportedInputs {
+ set.Add(in.InputID())
+ }
+ return set
+}
+
+// Verify this transaction is well-formed
+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):
+ return errWrongChainID
+ case len(tx.ImportedInputs) == 0:
+ return errNoImportInputs
+ case tx.NetworkID != ctx.NetworkID:
+ return errWrongNetworkID
+ case !ctx.ChainID.Equals(tx.BlockchainID):
+ return errWrongBlockchainID
+ }
+
+ for _, out := range tx.Outs {
+ if err := out.Verify(); err != nil {
+ return err
+ }
+ }
+
+ for _, in := range tx.ImportedInputs {
+ if err := in.Verify(); err != nil {
+ return err
+ }
+ }
+ if !avax.IsSortedAndUniqueTransferableInputs(tx.ImportedInputs) {
+ return errInputsNotSortedUnique
+ }
+
+ tx.syntacticallyVerified = true
+ return nil
+}
+
+// SemanticVerify this transaction is valid.
+func (tx *UnsignedImportTx) SemanticVerify(
+ vm *VM,
+ stx *Tx,
+) TxError {
+ if err := tx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil {
+ return permError{err}
+ }
+
+ // do flow-checking
+ fc := avax.NewFlowChecker()
+ fc.Produce(vm.ctx.AVAXAssetID, vm.txFee)
+
+ for _, out := range tx.Outs {
+ fc.Produce(vm.ctx.AVAXAssetID, out.Amount)
+ }
+
+ for _, in := range tx.ImportedInputs {
+ fc.Consume(in.AssetID(), in.Input().Amount())
+ }
+ if err := fc.Verify(); err != nil {
+ return permError{err}
+ }
+
+ if !vm.ctx.IsBootstrapped() {
+ // Allow for force committing during bootstrapping
+ return nil
+ }
+
+ utxoIDs := make([][]byte, len(tx.ImportedInputs))
+ for i, in := range tx.ImportedInputs {
+ utxoIDs[i] = in.UTXOID.InputID().Bytes()
+ }
+ allUTXOBytes, err := vm.ctx.SharedMemory.Get(tx.SourceChain, utxoIDs)
+ if err != nil {
+ return tempError{err}
+ }
+
+ utxos := make([]*avax.UTXO, len(tx.ImportedInputs))
+ for i, utxoBytes := range allUTXOBytes {
+ utxo := &avax.UTXO{}
+ if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+ return tempError{err}
+ }
+ utxos[i] = utxo
+ }
+
+ for i, in := range tx.ImportedInputs {
+ utxoBytes := allUTXOBytes[i]
+
+ utxo := &avax.UTXO{}
+ if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+ return tempError{err}
+ }
+
+ cred := stx.Creds[i]
+
+ utxoAssetID := utxo.AssetID()
+ inAssetID := in.AssetID()
+ if !utxoAssetID.Equals(inAssetID) {
+ return permError{errAssetIDMismatch}
+ }
+
+ if err := vm.fx.VerifyTransfer(tx, in.In, cred, utxo.Out); err != nil {
+ return tempError{err}
+ }
+ }
+ return nil
+}
+
+// Accept this transaction and spend imported inputs
+// We spend imported UTXOs here rather than in semanticVerify because
+// we don't want to remove an imported UTXO in semanticVerify
+// only to have the transaction not be Accepted. This would be inconsistent.
+// Recall that imported UTXOs are not kept in a versionDB.
+func (tx *UnsignedImportTx) Accept(ctx *snow.Context, _ database.Batch) error {
+ // TODO: Is any batch passed in here?
+ utxoIDs := make([][]byte, len(tx.ImportedInputs))
+ for i, in := range tx.ImportedInputs {
+ utxoIDs[i] = in.InputID().Bytes()
+ }
+ return ctx.SharedMemory.Remove(tx.SourceChain, utxoIDs)
+}
+
+// Create a new transaction
+func (vm *VM) newImportTx(
+ chainID ids.ID, // chain to import from
+ to common.Address, // Address of recipient
+ keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds
+) (*Tx, error) {
+ if !vm.ctx.XChainID.Equals(chainID) {
+ return nil, errWrongChainID
+ }
+
+ kc := secp256k1fx.NewKeychain()
+ for _, key := range keys {
+ kc.Add(key)
+ }
+
+ 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 := []*avax.TransferableInput{}
+ signers := [][]*crypto.PrivateKeySECP256K1R{}
+
+ importedAmount := uint64(0)
+ now := vm.clock.Unix()
+ for _, utxo := range atomicUTXOs {
+ if !utxo.AssetID().Equals(vm.ctx.AVAXAssetID) {
+ continue
+ }
+ inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now)
+ if err != nil {
+ continue
+ }
+ input, ok := inputIntf.(avax.TransferableIn)
+ if !ok {
+ continue
+ }
+ importedAmount, err = math.Add64(importedAmount, input.Amount())
+ if err != nil {
+ return nil, err
+ }
+ importedInputs = append(importedInputs, &avax.TransferableInput{
+ UTXOID: utxo.UTXOID,
+ Asset: utxo.Asset,
+ In: input,
+ })
+ signers = append(signers, utxoSigners)
+ }
+ avax.SortTransferableInputsWithSigners(importedInputs, signers)
+
+ if importedAmount == 0 {
+ return nil, errNoFunds // No imported UTXOs were spendable
+ }
+
+ nonce, err := vm.GetAcceptedNonce(to)
+ if err != nil {
+ return nil, err
+ }
+
+ outs := []EVMOutput{}
+ if importedAmount < vm.txFee { // imported amount goes toward paying tx fee
+ // TODO: spend EVM balance to compensate vm.txFee-importedAmount
+ return nil, errNoFunds
+ } else if importedAmount > vm.txFee {
+ outs = append(outs, EVMOutput{
+ Address: to,
+ Amount: importedAmount - vm.txFee,
+ Nonce: nonce,
+ })
+ }
+
+ // Create the transaction
+ utx := &UnsignedImportTx{
+ NetworkID: vm.ctx.NetworkID,
+ BlockchainID: vm.ctx.ChainID,
+ Outs: outs,
+ ImportedInputs: importedInputs,
+ SourceChain: chainID,
+ }
+ tx := &Tx{UnsignedTx: utx}
+ if err := tx.Sign(vm.codec, signers); err != nil {
+ return nil, err
+ }
+ return tx, utx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID)
+}
+
+func (tx *UnsignedImportTx) EVMStateTransfer(state *state.StateDB) error {
+ for _, to := range tx.Outs {
+ state.AddBalance(to.Address,
+ new(big.Int).Mul(
+ new(big.Int).SetUint64(to.Amount), x2cRate))
+ if state.GetNonce(to.Address) != to.Nonce {
+ return errInvalidNonce
+ }
+ state.SetNonce(to.Address, to.Nonce+1)
+ }
+ return nil
+}
diff --git a/plugin/evm/service.go b/plugin/evm/service.go
index 62b124f..29ef35d 100644
--- a/plugin/evm/service.go
+++ b/plugin/evm/service.go
@@ -6,15 +6,23 @@ package evm
import (
"context"
"crypto/rand"
+ "errors"
"fmt"
"math/big"
+ "net/http"
+ "strings"
"github.com/ava-labs/coreth"
"github.com/ava-labs/coreth/core/types"
+ "github.com/ava-labs/gecko/api"
+ "github.com/ava-labs/gecko/utils/constants"
+ "github.com/ava-labs/gecko/utils/crypto"
+ "github.com/ava-labs/gecko/utils/formatting"
+ "github.com/ava-labs/gecko/utils/json"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/common/hexutil"
- "github.com/ava-labs/go-ethereum/crypto"
+ ethcrypto "github.com/ava-labs/go-ethereum/crypto"
)
const (
@@ -36,6 +44,8 @@ type SnowmanAPI struct{ vm *VM }
// NetAPI offers network related API methods
type NetAPI struct{ vm *VM }
+type AvaAPI struct{ vm *VM }
+
// NewNetAPI creates a new net API instance.
func NewNetAPI(vm *VM) *NetAPI { return &NetAPI{vm} }
@@ -55,7 +65,7 @@ type Web3API struct{}
func (s *Web3API) ClientVersion() string { return version }
// Sha3 returns the bytes returned by hashing [input] with Keccak256
-func (s *Web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return crypto.Keccak256(input) }
+func (s *Web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return ethcrypto.Keccak256(input) }
// GetAcceptedFrontReply defines the reply that will be sent from the
// GetAcceptedFront API call
@@ -92,7 +102,7 @@ func (api *DebugAPI) SpendGenesis(ctx context.Context, nonce uint64) error {
gasLimit := 21000
gasPrice := big.NewInt(1000000000)
- genPrivateKey, err := crypto.HexToECDSA(GenesisTestKey[2:])
+ genPrivateKey, err := ethcrypto.HexToECDSA(GenesisTestKey[2:])
if err != nil {
return err
}
@@ -120,3 +130,181 @@ func (api *DebugAPI) IssueBlock(ctx context.Context) error {
return api.vm.tryBlockGen()
}
+
+// ExportKeyArgs are arguments for ExportKey
+type ExportKeyArgs struct {
+ api.UserPass
+ Address string `json:"address"`
+}
+
+// ExportKeyReply is the response for ExportKey
+type ExportKeyReply struct {
+ // The decrypted PrivateKey for the Address provided in the arguments
+ PrivateKey string `json:"privateKey"`
+}
+
+// ExportKey returns a private key from the provided user
+func (service *AvaAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error {
+ service.vm.ctx.Log.Info("Platform: ExportKey called")
+ db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
+ if err != nil {
+ return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err)
+ }
+ user := user{db: db}
+ if address, err := service.vm.ParseEthAddress(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 = constants.SecretKeyPrefix + formatting.CB58{Bytes: sk.Bytes()}.String()
+ return nil
+ }
+}
+
+// ImportKeyArgs are arguments for ImportKey
+type ImportKeyArgs struct {
+ api.UserPass
+ PrivateKey string `json:"privateKey"`
+}
+
+// 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)
+ }
+
+ user := user{db: db}
+
+ factory := crypto.FactorySECP256K1R{}
+
+ if !strings.HasPrefix(args.PrivateKey, constants.SecretKeyPrefix) {
+ return fmt.Errorf("private key missing %s prefix", constants.SecretKeyPrefix)
+ }
+ 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("problem parsing private key: %w", err)
+ }
+ sk := skIntf.(*crypto.PrivateKeySECP256K1R)
+
+ if err := user.putAddress(sk); err != nil {
+ return fmt.Errorf("problem saving key %w", err)
+ }
+
+ // TODO: return eth address here
+ reply.Address, err = service.vm.FormatEthAddress(GetEthAddress(sk))
+ if err != nil {
+ return fmt.Errorf("problem formatting address: %w", err)
+ }
+ return nil
+}
+
+// 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"`
+}
+
+// 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)
+ if err != nil {
+ return fmt.Errorf("couldn't get user '%s': %w", args.Username, err)
+ }
+ user := user{db: db}
+
+ to, err := service.vm.ParseEthAddress(args.To)
+ if err != nil { // Parse address
+ return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err)
+ }
+
+ privKeys, err := user.getKeys()
+ if err != nil { // Get keys
+ return fmt.Errorf("couldn't get keys controlled by the user: %w", err)
+ }
+
+ tx, err := service.vm.newImportTx(chainID, to, privKeys)
+ if err != nil {
+ return err
+ }
+
+ response.TxID = tx.ID()
+ return service.vm.issueTx(tx)
+}
+
+// ExportAVAXArgs are the arguments to ExportAVAX
+type ExportAVAXArgs struct {
+ api.UserPass
+
+ // Amount of AVAX to send
+ Amount json.Uint64 `json:"amount"`
+
+ // ID of the address that will receive the AVAX. This address includes the
+ // chainID, which is used to determine what the destination chain is.
+ To string `json:"to"`
+}
+
+// ExportAVAX exports AVAX from the P-Chain to the X-Chain
+// It must be imported on the X-Chain to complete the transfer
+func (service *AvaAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JsonTxID) error {
+ service.vm.ctx.Log.Info("Platform: ExportAVAX called")
+
+ if args.Amount == 0 {
+ return errors.New("argument 'amount' must be > 0")
+ }
+
+ chainID, to, err := service.vm.ParseAddress(args.To)
+ if err != nil {
+ return err
+ }
+
+ // Get this user's data
+ db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
+ if err != nil {
+ return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err)
+ }
+ user := user{db: db}
+ privKeys, err := user.getKeys()
+ if err != nil {
+ return fmt.Errorf("couldn't get addresses controlled by the user: %w", err)
+ }
+
+ // Create the transaction
+ tx, err := service.vm.newExportTx(
+ uint64(args.Amount), // Amount
+ chainID, // ID of the chain to send the funds to
+ to, // Address
+ privKeys, // Private keys
+ )
+ if err != nil {
+ return fmt.Errorf("couldn't create tx: %w", err)
+ }
+
+ response.TxID = tx.ID()
+ return service.vm.issueTx(tx)
+}
diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go
new file mode 100644
index 0000000..789ce56
--- /dev/null
+++ b/plugin/evm/tx.go
@@ -0,0 +1,112 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/ava-labs/coreth/core/state"
+
+ "github.com/ava-labs/gecko/database"
+ "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"
+ "github.com/ava-labs/go-ethereum/common"
+)
+
+// Max size of memo field
+// Don't change without also changing avm.maxMemoSize
+const maxMemoSize = 256
+
+var (
+ errWrongBlockchainID = errors.New("wrong blockchain ID provided")
+ errWrongNetworkID = errors.New("tx was issued with a different network ID")
+ errNilTx = errors.New("tx is nil")
+)
+
+type EVMOutput struct {
+ Address common.Address `serialize:"true" json:"address"`
+ Amount uint64 `serialize:"true" json:"amount"`
+ Nonce uint64 `serialize:"true" json:"nonce"`
+}
+
+func (out *EVMOutput) Verify() error {
+ return nil
+}
+
+type EVMInput EVMOutput
+
+func (in *EVMInput) Verify() error {
+ return nil
+}
+
+// UnsignedTx is an unsigned transaction
+type UnsignedTx interface {
+ Initialize(unsignedBytes, signedBytes []byte)
+ ID() ids.ID
+ UnsignedBytes() []byte
+ Bytes() []byte
+}
+
+// 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, stx *Tx) TxError
+
+ // Accept this transaction with the additionally provided state transitions.
+ Accept(ctx *snow.Context, batch database.Batch) error
+
+ EVMStateTransfer(state *state.StateDB) 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"`
+}
+
+// (*secp256k1fx.Credential)
+
+// Sign this transaction with the provided signers
+func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) error {
+ unsignedBytes, err := c.Marshal(&tx.UnsignedTx)
+ 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
new file mode 100644
index 0000000..fbf2981
--- /dev/null
+++ b/plugin/evm/user.go
@@ -0,0 +1,146 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+ "errors"
+ "fmt"
+
+ "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"
+)
+
+// Key in the database whose corresponding value is the list of
+// addresses this user controls
+var addressesKey = ids.Empty.Bytes()
+
+var (
+ errDBNil = errors.New("db uninitialized")
+ errKeyNil = errors.New("key uninitialized")
+ errEmptyAddress = errors.New("address is empty")
+)
+
+type user struct {
+ // This user's database, acquired from the keystore
+ db database.Database
+}
+
+// Get the addresses controlled by this user
+func (u *user) getAddresses() ([]common.Address, error) {
+ if u.db == nil {
+ return nil, errDBNil
+ }
+
+ // If user has no addresses, return empty list
+ hasAddresses, err := u.db.Has(addressesKey)
+ if err != nil {
+ return nil, err
+ }
+ if !hasAddresses {
+ return nil, nil
+ }
+
+ // User has addresses. Get them.
+ bytes, err := u.db.Get(addressesKey)
+ if err != nil {
+ return nil, err
+ }
+ addresses := []common.Address{}
+ if err := Codec.Unmarshal(bytes, &addresses); err != nil {
+ return nil, err
+ }
+ return addresses, nil
+}
+
+// controlsAddress returns true iff this user controls the given address
+func (u *user) controlsAddress(address common.Address) (bool, error) {
+ if u.db == nil {
+ return false, errDBNil
+ //} else if address.IsZero() {
+ // return false, errEmptyAddress
+ }
+ return u.db.Has(address.Bytes())
+}
+
+// putAddress persists that this user controls address controlled by [privKey]
+func (u *user) putAddress(privKey *crypto.PrivateKeySECP256K1R) error {
+ if privKey == nil {
+ return errKeyNil
+ }
+
+ address := GetEthAddress(privKey) // address the privKey controls
+ controlsAddress, err := u.controlsAddress(address)
+ if err != nil {
+ return err
+ }
+ if controlsAddress { // user already controls this address. Do nothing.
+ return nil
+ }
+
+ if err := u.db.Put(address.Bytes(), privKey.Bytes()); err != nil { // Address --> private key
+ return err
+ }
+
+ addresses := make([]common.Address, 0) // Add address to list of addresses user controls
+ userHasAddresses, err := u.db.Has(addressesKey)
+ if err != nil {
+ return err
+ }
+ if userHasAddresses { // Get addresses this user already controls, if they exist
+ if addresses, err = u.getAddresses(); err != nil {
+ return err
+ }
+ }
+ addresses = append(addresses, address)
+ bytes, err := Codec.Marshal(addresses)
+ if err != nil {
+ return err
+ }
+ if err := u.db.Put(addressesKey, bytes); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Key returns the private key that controls the given address
+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 := factory.ToPrivateKey(bytes)
+ if err != nil {
+ return nil, err
+ }
+ 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() ([]*crypto.PrivateKeySECP256K1R, error) {
+ addrs, err := u.getAddresses()
+ if err != nil {
+ return nil, err
+ }
+ keys := make([]*crypto.PrivateKeySECP256K1R, len(addrs))
+ for i, addr := range addrs {
+ key, err := u.getKey(addr)
+ if err != nil {
+ return nil, err
+ }
+ keys[i] = key
+ }
+ return keys, nil
+}
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index cf5ef8a..e1fdc33 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -16,13 +16,17 @@ 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"
+ "github.com/ava-labs/coreth/params"
- "github.com/ava-labs/coreth/rpc"
"github.com/ava-labs/go-ethereum/common"
+ ethcrypto "github.com/ava-labs/go-ethereum/crypto"
"github.com/ava-labs/go-ethereum/rlp"
+ "github.com/ava-labs/go-ethereum/rpc"
+ geckorpc "github.com/gorilla/rpc/v2"
"github.com/ava-labs/gecko/api/admin"
"github.com/ava-labs/gecko/cache"
@@ -31,7 +35,17 @@ import (
"github.com/ava-labs/gecko/snow"
"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/crypto"
+ "github.com/ava-labs/gecko/utils/formatting"
+ geckojson "github.com/ava-labs/gecko/utils/json"
+ "github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/timer"
+ "github.com/ava-labs/gecko/utils/units"
+ "github.com/ava-labs/gecko/utils/wrappers"
+ "github.com/ava-labs/gecko/vms/components/avax"
+ "github.com/ava-labs/gecko/vms/secp256k1fx"
commonEng "github.com/ava-labs/gecko/snow/engine/common"
)
@@ -41,6 +55,7 @@ var (
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
+ x2cRate = big.NewInt(1000000000)
)
const (
@@ -48,9 +63,11 @@ const (
)
const (
- minBlockTime = 250 * time.Millisecond
- maxBlockTime = 1000 * time.Millisecond
- batchSize = 250
+ minBlockTime = 250 * time.Millisecond
+ maxBlockTime = 1000 * time.Millisecond
+ batchSize = 250
+ maxUTXOsToFetch = 1024
+ blockCacheSize = 1 << 17 // 131072
)
const (
@@ -59,13 +76,39 @@ const (
bdTimerStateLong
)
+const (
+ addressSep = "-"
+)
+
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")
+ // minGasPrice is the number of nAVAX required per gas unit for a transaction
+ // to be valid, measured in wei
+ minGasPrice = big.NewInt(47 * params.GWei)
+
+ txFee = units.MilliAvax
+
+ 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 atomic txs")
+ errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo")
+ errWrongNumberOfCredentials = errors.New("should have the same number of credentials as inputs")
+ errNoInputs = errors.New("tx has no inputs")
+ errNoImportInputs = errors.New("tx has no imported inputs")
+ errInputsNotSortedUnique = errors.New("inputs not sorted and unique")
+ errPublicKeySignatureMismatch = errors.New("signature doesn't match public key")
+ errUnknownAsset = errors.New("unknown asset ID")
+ errNoFunds = errors.New("no spendable funds were found")
+ errWrongChainID = errors.New("tx has wrong chain ID")
+ errInsufficientFunds = errors.New("insufficient funds")
+ errNoExportOutputs = errors.New("no export outputs")
+ errOutputsNotSorted = errors.New("outputs not sorted")
+ errOverflowExport = errors.New("overflow when computing export amount + txFee")
+ errInvalidNonce = errors.New("invalid nonce")
)
func maxDuration(x, y time.Duration) time.Duration {
@@ -75,6 +118,32 @@ func maxDuration(x, y time.Duration) time.Duration {
return y
}
+// Codec does serialization and deserialization
+var Codec codec.Codec
+
+func init() {
+ Codec = codec.NewDefault()
+
+ errs := wrappers.Errs{}
+ errs.Add(
+ Codec.RegisterType(&UnsignedImportTx{}),
+ Codec.RegisterType(&UnsignedExportTx{}),
+ )
+ Codec.Skip(3)
+ errs.Add(
+ Codec.RegisterType(&secp256k1fx.TransferInput{}),
+ Codec.RegisterType(&secp256k1fx.MintOutput{}),
+ Codec.RegisterType(&secp256k1fx.TransferOutput{}),
+ Codec.RegisterType(&secp256k1fx.MintOperation{}),
+ Codec.RegisterType(&secp256k1fx.Credential{}),
+ Codec.RegisterType(&secp256k1fx.Input{}),
+ Codec.RegisterType(&secp256k1fx.OutputOwners{}),
+ )
+ if errs.Errored() {
+ panic(errs.Err)
+ }
+}
+
// VM implements the snowman.ChainVM interface
type VM struct {
ctx *snow.Context
@@ -103,10 +172,37 @@ type VM struct {
bdGenWaitFlag bool
bdGenFlag bool
- genlock sync.Mutex
- txSubmitChan <-chan struct{}
+ genlock sync.Mutex
+ txSubmitChan <-chan struct{}
+ atomicTxSubmitChan chan struct{}
+ codec codec.Codec
+ clock timer.Clock
+ txFee uint64
+ pendingAtomicTxs chan *Tx
+ blockAtomicInputCache cache.LRU
+
+ fx secp256k1fx.Fx
+}
+
+func (vm *VM) getAtomicTx(block *types.Block) *Tx {
+ extdata := block.ExtraData()
+ atx := new(Tx)
+ if err := vm.codec.Unmarshal(extdata, atx); err != nil {
+ return nil
+ }
+ atx.Sign(vm.codec, nil)
+ return atx
}
+// Codec implements the secp256k1fx interface
+func (vm *VM) Codec() codec.Codec { return codec.NewDefault() }
+
+// Clock implements the secp256k1fx interface
+func (vm *VM) Clock() *timer.Clock { return &vm.clock }
+
+// Logger implements the secp256k1fx interface
+func (vm *VM) Logger() logging.Logger { return vm.ctx.Log }
+
/*
******************************************************************************
********************************* Snowman API ********************************
@@ -134,12 +230,20 @@ func (vm *VM) Initialize(
}
vm.chainID = g.Config.ChainID
+ vm.txFee = txFee
config := eth.DefaultConfig
config.ManualCanonical = true
config.Genesis = g
config.Miner.ManualMining = true
config.Miner.DisableUncle = true
+
+ // Set minimum price for mining and default gas price oracle value to the min
+ // gas price to prevent so transactions and blocks all use the correct fees
+ config.Miner.GasPrice = minGasPrice
+ config.GPO.Default = minGasPrice
+ config.TxPool.PriceLimit = minGasPrice.Uint64()
+
if err := config.SetGCMode("archive"); err != nil {
panic(err)
}
@@ -155,13 +259,23 @@ func (vm *VM) Initialize(
}
header.Extra = append(header.Extra, hid...)
})
- chain.SetOnSeal(func(block *types.Block) error {
- if len(block.Transactions()) == 0 {
- // this could happen due to the async logic of geth tx pool
- vm.newBlockChan <- nil
- return errEmptyBlock
+ chain.SetOnFinalizeAndAssemble(func(state *state.StateDB, txs []*types.Transaction) ([]byte, error) {
+ select {
+ case atx := <-vm.pendingAtomicTxs:
+ if err := atx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state); err != nil {
+ vm.newBlockChan <- nil
+ return nil, err
+ }
+ raw, _ := vm.codec.Marshal(atx)
+ return raw, nil
+ default:
+ if len(txs) == 0 {
+ // this could happen due to the async logic of geth tx pool
+ vm.newBlockChan <- nil
+ return nil, errEmptyBlock
+ }
}
- return nil
+ return nil, nil
})
chain.SetOnSealFinish(func(block *types.Block) error {
vm.ctx.Log.Verbo("EVM sealed a block")
@@ -171,6 +285,9 @@ func (vm *VM) Initialize(
ethBlock: block,
vm: vm,
}
+ if blk.Verify() != nil {
+ return errInvalidBlock
+ }
vm.newBlockChan <- blk
vm.updateStatus(ids.NewID(block.Hash()), choices.Processing)
vm.txPoolStabilizedLock.Lock()
@@ -181,8 +298,16 @@ func (vm *VM) Initialize(
chain.SetOnQueryAcceptedBlock(func() *types.Block {
return vm.getLastAccepted().ethBlock
})
- vm.blockCache = cache.LRU{Size: 2048}
- vm.blockStatusCache = cache.LRU{Size: 1024}
+ chain.SetOnExtraStateChange(func(block *types.Block, state *state.StateDB) error {
+ tx := vm.getAtomicTx(block)
+ if tx == nil {
+ return nil
+ }
+ return tx.UnsignedTx.(UnsignedAtomicTx).EVMStateTransfer(state)
+ })
+ vm.blockCache = cache.LRU{Size: blockCacheSize}
+ vm.blockStatusCache = cache.LRU{Size: blockCacheSize}
+ vm.blockAtomicInputCache = cache.LRU{Size: blockCacheSize}
vm.newBlockChan = make(chan *Block)
vm.networkChan = toEngine
vm.blockDelayTimer = timer.NewTimer(func() {
@@ -206,6 +331,9 @@ 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)
+ vm.atomicTxSubmitChan = make(chan struct{}, 1)
chain.GetTxPool().SubscribeNewHeadEvent(vm.newTxPoolHeadChan)
// TODO: shutdown this go routine
go ctx.Log.RecoverAndPanic(func() {
@@ -254,22 +382,26 @@ func (vm *VM) Initialize(
case <-vm.txSubmitChan:
vm.ctx.Log.Verbo("New tx detected, trying to generate a block")
vm.tryBlockGen()
+ case <-vm.atomicTxSubmitChan:
+ vm.ctx.Log.Verbo("New atomic Tx detected, trying to generate a block")
+ vm.tryBlockGen()
case <-time.After(5 * time.Second):
vm.tryBlockGen()
}
}
})
+ vm.codec = Codec
- return nil
+ return vm.fx.Initialize(vm)
}
// Bootstrapping notifies this VM that the consensus engine is performing
// bootstrapping
-func (vm *VM) Bootstrapping() error { return nil }
+func (vm *VM) Bootstrapping() error { return vm.fx.Bootstrapping() }
// Bootstrapped notifies this VM that the consensus engine has finished
// bootstrapping
-func (vm *VM) Bootstrapped() error { return nil }
+func (vm *VM) Bootstrapped() error { return vm.fx.Bootstrapped() }
// Shutdown implements the snowman.ChainVM interface
func (vm *VM) Shutdown() error {
@@ -356,6 +488,26 @@ func (vm *VM) LastAccepted() ids.ID {
return vm.lastAccepted.ID()
}
+// NewHandler returns a new Handler for a service where:
+// * The handler's functionality is defined by [service]
+// [service] should be a gorilla RPC service (see https://www.gorillatoolkit.org/pkg/rpc/v2)
+// * The name of the service is [name]
+// * The LockOption is the first element of [lockOption]
+// By default the LockOption is WriteLock
+// [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored.
+func newHandler(name string, service interface{}, lockOption ...commonEng.LockOption) *commonEng.HTTPHandler {
+ server := geckorpc.NewServer()
+ server.RegisterCodec(geckojson.NewCodec(), "application/json")
+ server.RegisterCodec(geckojson.NewCodec(), "application/json;charset=UTF-8")
+ server.RegisterService(service, name)
+
+ var lock commonEng.LockOption = commonEng.WriteLock
+ if len(lockOption) != 0 {
+ lock = lockOption[0]
+ }
+ return &commonEng.HTTPHandler{LockOptions: lock, Handler: server}
+}
+
// CreateHandlers makes new http handlers that can handle API calls
func (vm *VM) CreateHandlers() map[string]*commonEng.HTTPHandler {
handler := vm.chain.NewRPCHandler()
@@ -368,6 +520,7 @@ func (vm *VM) CreateHandlers() map[string]*commonEng.HTTPHandler {
return map[string]*commonEng.HTTPHandler{
"/rpc": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler},
+ "/ava": newHandler("ava", &AvaAPI{vm}),
"/ws": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler.WebsocketHandler([]string{"*"})},
}
}
@@ -402,10 +555,6 @@ func (vm *VM) updateStatus(blockID ids.ID, status choices.Status) {
vm.blockStatusCache.Put(blockID, status)
}
-func (vm *VM) getCachedBlock(blockID ids.ID) *types.Block {
- return vm.chain.GetBlockByHash(blockID.Key())
-}
-
func (vm *VM) tryBlockGen() error {
vm.bdlock.Lock()
defer vm.bdlock.Unlock()
@@ -422,7 +571,7 @@ func (vm *VM) tryBlockGen() error {
if err != nil {
return err
}
- if size == 0 {
+ if size == 0 && len(vm.pendingAtomicTxs) == 0 {
return nil
}
@@ -454,10 +603,11 @@ func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
if statusIntf, ok := vm.blockStatusCache.Get(blockID); ok {
status = statusIntf.(choices.Status)
} else {
- blk := vm.chain.GetBlockByHash(blockID.Key())
- if blk == nil {
+ wrappedBlk := vm.getBlock(blockID)
+ if wrappedBlk == nil {
return choices.Unknown
}
+ blk := wrappedBlk.ethBlock
acceptedBlk := vm.lastAccepted.ethBlock
// TODO: There must be a better way of doing this.
@@ -468,7 +618,12 @@ func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
highBlock, lowBlock = lowBlock, highBlock
}
for highBlock.Number().Cmp(lowBlock.Number()) > 0 {
- highBlock = vm.chain.GetBlockByHash(highBlock.ParentHash())
+ parentBlock := vm.getBlock(ids.NewID(highBlock.ParentHash()))
+ if parentBlock == nil {
+ vm.blockStatusCache.Put(blockID, choices.Processing)
+ return choices.Processing
+ }
+ highBlock = parentBlock.ethBlock
}
if highBlock.Hash() == lowBlock.Hash() { // on the same branch
@@ -488,7 +643,7 @@ func (vm *VM) getBlock(id ids.ID) *Block {
if blockIntf, ok := vm.blockCache.Get(id); ok {
return blockIntf.(*Block)
}
- ethBlock := vm.getCachedBlock(id)
+ ethBlock := vm.chain.GetBlockByHash(id.Key())
if ethBlock == nil {
return nil
}
@@ -531,3 +686,157 @@ func (vm *VM) getLastAccepted() *Block {
return vm.lastAccepted
}
+
+func (vm *VM) ParseEthAddress(addrStr string) (common.Address, error) {
+ if !common.IsHexAddress(addrStr) {
+ return common.Address{}, errInvalidAddr
+ }
+ return common.HexToAddress(addrStr), nil
+}
+
+func (vm *VM) FormatEthAddress(addr common.Address) (string, error) {
+ return addr.Hex(), nil
+}
+
+// ParseAddress takes in an address and produces the ID of the chain it's for
+// the ID of the address
+func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) {
+ chainIDAlias, hrp, addrBytes, err := formatting.ParseAddress(addrStr)
+ if err != nil {
+ return ids.ID{}, ids.ShortID{}, err
+ }
+
+ chainID, err := vm.ctx.BCLookup.Lookup(chainIDAlias)
+ if err != nil {
+ return ids.ID{}, ids.ShortID{}, err
+ }
+
+ expectedHRP := constants.GetHRP(vm.ctx.NetworkID)
+ if hrp != expectedHRP {
+ return ids.ID{}, ids.ShortID{}, fmt.Errorf("expected hrp %q but got %q",
+ expectedHRP, hrp)
+ }
+
+ addr, err := ids.ToShortID(addrBytes)
+ if err != nil {
+ return ids.ID{}, ids.ShortID{}, err
+ }
+ return chainID, addr, nil
+}
+
+func (vm *VM) issueTx(tx *Tx) error {
+ select {
+ case vm.pendingAtomicTxs <- tx:
+ select {
+ case vm.atomicTxSubmitChan <- struct{}{}:
+ default:
+ }
+ default:
+ return errTooManyAtomicTx
+ }
+ return nil
+}
+
+// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is
+// referenced in.
+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) {
+ if limit <= 0 || limit > maxUTXOsToFetch {
+ limit = maxUTXOsToFetch
+ }
+
+ addrsList := make([][]byte, addrs.Len())
+ for i, addr := range addrs.List() {
+ addrsList[i] = addr.Bytes()
+ }
+
+ allUTXOBytes, lastAddr, lastUTXO, err := vm.ctx.SharedMemory.Indexed(
+ chainID,
+ addrsList,
+ startAddr.Bytes(),
+ startUTXOID.Bytes(),
+ limit,
+ )
+ if err != nil {
+ return nil, ids.ShortID{}, ids.ID{}, fmt.Errorf("error fetching atomic UTXOs: %w", err)
+ }
+
+ lastAddrID, err := ids.ToShortID(lastAddr)
+ if err != nil {
+ lastAddrID = ids.ShortEmpty
+ }
+ lastUTXOID, err := ids.ToID(lastUTXO)
+ if err != nil {
+ lastUTXOID = ids.Empty
+ }
+
+ utxos := make([]*avax.UTXO, len(allUTXOBytes))
+ for i, utxoBytes := range allUTXOBytes {
+ utxo := &avax.UTXO{}
+ if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+ return nil, ids.ShortID{}, ids.ID{}, fmt.Errorf("error parsing UTXO: %w", err)
+ }
+ utxos[i] = utxo
+ }
+ return utxos, lastAddrID, lastUTXOID, nil
+}
+
+func GetEthAddress(privKey *crypto.PrivateKeySECP256K1R) common.Address {
+ return PublicKeyToEthAddress(privKey.PublicKey())
+}
+
+func PublicKeyToEthAddress(pubKey crypto.PublicKey) common.Address {
+ return ethcrypto.PubkeyToAddress(
+ (*pubKey.(*crypto.PublicKeySECP256K1R).ToECDSA()))
+}
+
+func (vm *VM) GetSpendableCanonical(keys []*crypto.PrivateKeySECP256K1R, amount uint64) ([]EVMInput, [][]*crypto.PrivateKeySECP256K1R, error) {
+ // NOTE: should we use HEAD block or lastAccepted?
+ state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock)
+ if err != nil {
+ return nil, nil, err
+ }
+ inputs := []EVMInput{}
+ signers := [][]*crypto.PrivateKeySECP256K1R{}
+ for _, key := range keys {
+ if amount == 0 {
+ break
+ }
+ addr := GetEthAddress(key)
+ balance := new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64()
+ if balance == 0 {
+ continue
+ }
+ if amount < balance {
+ balance = amount
+ }
+ nonce, err := vm.GetAcceptedNonce(addr)
+ if err != nil {
+ return nil, nil, err
+ }
+ inputs = append(inputs, EVMInput{
+ Address: addr,
+ Amount: balance,
+ Nonce: nonce,
+ })
+ signers = append(signers, []*crypto.PrivateKeySECP256K1R{key})
+ amount -= balance
+ }
+ if amount > 0 {
+ return nil, nil, errInsufficientFunds
+ }
+ return inputs, signers, nil
+}
+
+func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) {
+ state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock)
+ if err != nil {
+ return 0, err
+ }
+ return state.GetNonce(address), nil
+}