Move Geth to a plugin connected at runtime

* removed coreth + geth dependency for the C-chain, still have nat + crypto dependencies

* Added first past of nat traversal

* Removed go-eth dependency for secp256k1

* renamed db files to not repeat the package title

* Added rpcdb, untested

* Added rpcdb tests

* removed defer from loop

* cleaned up tests

* Started working on the rpc chain VM

* First pass of vm plugin support

* Added http over grpc

* added beta support for http handlers

* updated vms for the rpcvms

* Added error returns for factories

* Added EVM back to the genesis

* Added the EVM factory

* Added EVM aliasing, fixed nil pointer bugs during shutdown

* Added plugin folder

* Removed plugins folder, add default params for the plugin dir

* Added http RPC test

* Added cascade evm genesis

* Removed debug line

* removed more debug lines

* Cleaned up import statement

* Changed default plugin dir to ./plugins

* build evm plugin auto in the build script
This commit is contained in:
Stephen Buttolph 2020-04-16 11:39:20 -04:00 committed by GitHub
parent e750d442cf
commit 42baab2881
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 6179 additions and 1256 deletions

6
.gitignore vendored
View File

@ -29,6 +29,7 @@ awscpu
*.ava
db*
*cpu[0-9]*
*mem[0-9]*
*lock[0-9]*
@ -46,3 +47,8 @@ bin/
build/
keys/staker.*
!*.go
!*.proto
plugins/

View File

@ -6,10 +6,6 @@ RUN apt-get update && apt-get install -y libssl-dev libuv1-dev curl cmake
RUN mkdir -p /go/src/github.com/ava-labs
# Because downloading ethereum takes long it is done separately, so that the docker
# layer, when cached can be re-used
RUN go get -t -v github.com/ava-labs/go-ethereum
WORKDIR $GOPATH/src/github.com/ava-labs/
COPY . gecko

View File

@ -214,7 +214,12 @@ func (m *manager) ForceCreateChain(chain ChainParameters) {
}
// Create the chain
vm := vmFactory.New()
vm, err := vmFactory.New()
if err != nil {
m.log.Error("error while creating vm: %s", err)
return
}
// TODO: Shutdown VM if an error occurs
fxs := make([]*common.Fx, len(chain.FxAliases))
for i, fxAlias := range chain.FxAliases {
@ -231,10 +236,16 @@ func (m *manager) ForceCreateChain(chain ChainParameters) {
return
}
fx, err := fxFactory.New()
if err != nil {
m.log.Error("error while creating fx: %s", err)
return
}
// Create the fx
fxs[i] = &common.Fx{
ID: fxID,
Fx: fxFactory.New(),
Fx: fx,
}
}

268
database/rpcdb/db_client.go Normal file
View File

@ -0,0 +1,268 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcdb
import (
"fmt"
"golang.org/x/net/context"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/nodb"
"github.com/ava-labs/gecko/database/rpcdb/proto"
)
var (
errClosed = fmt.Sprintf("rpc error: code = Unknown desc = %s", database.ErrClosed)
errNotFound = fmt.Sprintf("rpc error: code = Unknown desc = %s", database.ErrNotFound)
)
// DatabaseClient is an implementation of database that talks over RPC.
type DatabaseClient struct{ client proto.DatabaseClient }
// NewClient returns a database instance connected to a remote database instance
func NewClient(client proto.DatabaseClient) *DatabaseClient {
return &DatabaseClient{client: client}
}
// Has returns false, nil
func (db *DatabaseClient) Has(key []byte) (bool, error) {
resp, err := db.client.Has(context.Background(), &proto.HasRequest{
Key: key,
})
if err != nil {
return false, updateError(err)
}
return resp.Has, nil
}
// Get returns nil, error
func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
resp, err := db.client.Get(context.Background(), &proto.GetRequest{
Key: key,
})
if err != nil {
return nil, updateError(err)
}
return resp.Value, nil
}
// Put returns nil
func (db *DatabaseClient) Put(key, value []byte) error {
_, err := db.client.Put(context.Background(), &proto.PutRequest{
Key: key,
Value: value,
})
return updateError(err)
}
// Delete returns nil
func (db *DatabaseClient) Delete(key []byte) error {
_, err := db.client.Delete(context.Background(), &proto.DeleteRequest{
Key: key,
})
return updateError(err)
}
// NewBatch returns a new batch
func (db *DatabaseClient) NewBatch() database.Batch { return &batch{db: db} }
// NewIterator implements the Database interface
func (db *DatabaseClient) NewIterator() database.Iterator {
return db.NewIteratorWithStartAndPrefix(nil, nil)
}
// NewIteratorWithStart implements the Database interface
func (db *DatabaseClient) NewIteratorWithStart(start []byte) database.Iterator {
return db.NewIteratorWithStartAndPrefix(start, nil)
}
// NewIteratorWithPrefix implements the Database interface
func (db *DatabaseClient) NewIteratorWithPrefix(prefix []byte) database.Iterator {
return db.NewIteratorWithStartAndPrefix(nil, prefix)
}
// NewIteratorWithStartAndPrefix returns a new empty iterator
func (db *DatabaseClient) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
resp, err := db.client.NewIteratorWithStartAndPrefix(context.Background(), &proto.NewIteratorWithStartAndPrefixRequest{
Start: start,
Prefix: prefix,
})
if err != nil {
return &nodb.Iterator{Err: updateError(err)}
}
return &iterator{
db: db,
id: resp.Id,
}
}
// Stat returns an error
func (db *DatabaseClient) Stat(property string) (string, error) {
resp, err := db.client.Stat(context.Background(), &proto.StatRequest{
Property: property,
})
if err != nil {
return "", updateError(err)
}
return resp.Stat, nil
}
// Compact returns nil
func (db *DatabaseClient) Compact(start, limit []byte) error {
_, err := db.client.Compact(context.Background(), &proto.CompactRequest{
Start: start,
Limit: limit,
})
return updateError(err)
}
// Close returns nil
func (db *DatabaseClient) Close() error {
_, err := db.client.Close(context.Background(), &proto.CloseRequest{})
return updateError(err)
}
type keyValue struct {
key []byte
value []byte
delete bool
}
type batch struct {
db *DatabaseClient
writes []keyValue
size int
}
func (b *batch) Put(key, value []byte) error {
b.writes = append(b.writes, keyValue{copyBytes(key), copyBytes(value), false})
b.size += len(value)
return nil
}
func (b *batch) Delete(key []byte) error {
b.writes = append(b.writes, keyValue{copyBytes(key), nil, true})
b.size++
return nil
}
func (b *batch) ValueSize() int { return b.size }
func (b *batch) Write() error {
request := &proto.WriteBatchRequest{}
keySet := make(map[string]struct{}, len(b.writes))
for i := len(b.writes) - 1; i >= 0; i-- {
kv := b.writes[i]
key := string(kv.key)
if _, overwritten := keySet[key]; overwritten {
continue
}
keySet[key] = struct{}{}
if kv.delete {
request.Deletes = append(request.Deletes, &proto.DeleteRequest{
Key: kv.key,
})
} else {
request.Puts = append(request.Puts, &proto.PutRequest{
Key: kv.key,
Value: kv.value,
})
}
}
_, err := b.db.client.WriteBatch(context.Background(), request)
return updateError(err)
}
func (b *batch) Reset() {
b.writes = b.writes[:0]
b.size = 0
}
func (b *batch) Replay(w database.KeyValueWriter) error {
for _, keyvalue := range b.writes {
if keyvalue.delete {
if err := w.Delete(keyvalue.key); err != nil {
return err
}
} else if err := w.Put(keyvalue.key, keyvalue.value); err != nil {
return err
}
}
return nil
}
func (b *batch) Inner() database.Batch { return b }
type iterator struct {
db *DatabaseClient
id uint64
key []byte
value []byte
err error
}
// Next returns false
func (it *iterator) Next() bool {
resp, err := it.db.client.IteratorNext(context.Background(), &proto.IteratorNextRequest{
Id: it.id,
})
if err != nil {
it.err = err
return false
}
it.key = resp.Key
it.value = resp.Value
return resp.FoundNext
}
// Error returns any errors
func (it *iterator) Error() error {
if it.err != nil {
return it.err
}
_, err := it.db.client.IteratorError(context.Background(), &proto.IteratorErrorRequest{
Id: it.id,
})
it.err = updateError(err)
return it.err
}
// Key returns nil
func (it *iterator) Key() []byte { return it.key }
// Value returns nil
func (it *iterator) Value() []byte { return it.value }
// Release does nothing
func (it *iterator) Release() {
it.db.client.IteratorRelease(context.Background(), &proto.IteratorReleaseRequest{
Id: it.id,
})
}
func copyBytes(bytes []byte) []byte {
copiedBytes := make([]byte, len(bytes))
copy(copiedBytes, bytes)
return copiedBytes
}
func updateError(err error) error {
if err == nil {
return nil
}
switch err.Error() {
case errClosed:
return database.ErrClosed
case errNotFound:
return database.ErrNotFound
default:
return err
}
}

143
database/rpcdb/db_server.go Normal file
View File

@ -0,0 +1,143 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcdb
import (
"errors"
"golang.org/x/net/context"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/rpcdb/proto"
)
var (
errUnknownIterator = errors.New("unknown iterator")
)
// DatabaseServer is a database that is managed over RPC.
type DatabaseServer struct {
db database.Database
batch database.Batch
nextIteratorID uint64
iterators map[uint64]database.Iterator
}
// NewServer returns a database instance that is managed remotely
func NewServer(db database.Database) *DatabaseServer {
return &DatabaseServer{
db: db,
batch: db.NewBatch(),
iterators: make(map[uint64]database.Iterator),
}
}
// Has ...
func (db *DatabaseServer) Has(_ context.Context, req *proto.HasRequest) (*proto.HasResponse, error) {
has, err := db.db.Has(req.Key)
if err != nil {
return nil, err
}
return &proto.HasResponse{Has: has}, nil
}
// Get ...
func (db *DatabaseServer) Get(_ context.Context, req *proto.GetRequest) (*proto.GetResponse, error) {
value, err := db.db.Get(req.Key)
if err != nil {
return nil, err
}
return &proto.GetResponse{Value: value}, nil
}
// Put ...
func (db *DatabaseServer) Put(_ context.Context, req *proto.PutRequest) (*proto.PutResponse, error) {
return &proto.PutResponse{}, db.db.Put(req.Key, req.Value)
}
// Delete ...
func (db *DatabaseServer) Delete(_ context.Context, req *proto.DeleteRequest) (*proto.DeleteResponse, error) {
return &proto.DeleteResponse{}, db.db.Delete(req.Key)
}
// Stat ...
func (db *DatabaseServer) Stat(_ context.Context, req *proto.StatRequest) (*proto.StatResponse, error) {
stat, err := db.db.Stat(req.Property)
if err != nil {
return nil, err
}
return &proto.StatResponse{Stat: stat}, nil
}
// Compact ...
func (db *DatabaseServer) Compact(_ context.Context, req *proto.CompactRequest) (*proto.CompactResponse, error) {
return &proto.CompactResponse{}, db.db.Compact(req.Start, req.Limit)
}
// Close ...
func (db *DatabaseServer) Close(_ context.Context, _ *proto.CloseRequest) (*proto.CloseResponse, error) {
return &proto.CloseResponse{}, db.db.Close()
}
// WriteBatch ...
func (db *DatabaseServer) WriteBatch(_ context.Context, req *proto.WriteBatchRequest) (*proto.WriteBatchResponse, error) {
db.batch.Reset()
for _, put := range req.Puts {
if err := db.batch.Put(put.Key, put.Value); err != nil {
return nil, err
}
}
for _, del := range req.Deletes {
if err := db.batch.Delete(del.Key); err != nil {
return nil, err
}
}
return &proto.WriteBatchResponse{}, db.batch.Write()
}
// NewIteratorWithStartAndPrefix ...
func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *proto.NewIteratorWithStartAndPrefixRequest) (*proto.NewIteratorWithStartAndPrefixResponse, error) {
id := db.nextIteratorID
it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
db.iterators[id] = it
db.nextIteratorID++
return &proto.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
}
// IteratorNext ...
func (db *DatabaseServer) IteratorNext(_ context.Context, req *proto.IteratorNextRequest) (*proto.IteratorNextResponse, error) {
it, exists := db.iterators[req.Id]
if !exists {
return nil, errUnknownIterator
}
return &proto.IteratorNextResponse{
FoundNext: it.Next(),
Key: it.Key(),
Value: it.Value(),
}, nil
}
// IteratorError ...
func (db *DatabaseServer) IteratorError(_ context.Context, req *proto.IteratorErrorRequest) (*proto.IteratorErrorResponse, error) {
it, exists := db.iterators[req.Id]
if !exists {
return nil, errUnknownIterator
}
return &proto.IteratorErrorResponse{}, it.Error()
}
// IteratorRelease ...
func (db *DatabaseServer) IteratorRelease(_ context.Context, req *proto.IteratorReleaseRequest) (*proto.IteratorReleaseResponse, error) {
it, exists := db.iterators[req.Id]
if exists {
delete(db.iterators, req.Id)
it.Release()
}
return &proto.IteratorReleaseResponse{}, nil
}

51
database/rpcdb/db_test.go Normal file
View File

@ -0,0 +1,51 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcdb
import (
"log"
"net"
"testing"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/database/rpcdb/proto"
)
const (
bufSize = 1 << 20
)
func TestInterface(t *testing.T) {
for _, test := range database.Tests {
listener := bufconn.Listen(bufSize)
server := grpc.NewServer()
proto.RegisterDatabaseServer(server, NewServer(memdb.New()))
go func() {
if err := server.Serve(listener); err != nil {
log.Fatalf("Server exited with error: %v", err)
}
}()
dialer := grpc.WithContextDialer(
func(context.Context, string) (net.Conn, error) {
return listener.Dial()
})
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "", dialer, grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial: %s", err)
}
db := NewClient(proto.NewDatabaseClient(conn))
test(t, db)
conn.Close()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,108 @@
syntax = "proto3";
package proto;
message HasRequest {
bytes key = 1;
}
message HasResponse {
bool has = 1;
}
message GetRequest {
bytes key = 1;
}
message GetResponse {
bytes value = 1;
}
message PutRequest {
bytes key = 1;
bytes value = 2;
}
message PutResponse {}
message DeleteRequest {
bytes key = 1;
}
message DeleteResponse {}
message StatRequest {
string property = 1;
}
message StatResponse {
string stat = 1;
}
message CompactRequest {
bytes start = 1;
bytes limit = 2;
}
message CompactResponse {}
message CloseRequest {}
message CloseResponse {}
message WriteBatchRequest {
repeated PutRequest puts = 1;
repeated DeleteRequest deletes = 2;
}
message WriteBatchResponse {}
message NewIteratorRequest {}
message NewIteratorWithStartAndPrefixRequest {
bytes start = 1;
bytes prefix = 2;
}
message NewIteratorWithStartAndPrefixResponse {
uint64 id = 1;
}
message IteratorNextRequest {
uint64 id = 1;
}
message IteratorNextResponse {
bool foundNext = 1;
bytes key = 2;
bytes value = 3;
}
message IteratorErrorRequest {
uint64 id = 1;
}
message IteratorErrorResponse {}
message IteratorReleaseRequest {
uint64 id = 1;
}
message IteratorReleaseResponse {}
service Database {
rpc Has(HasRequest) returns (HasResponse);
rpc Get(GetRequest) returns (GetResponse);
rpc Put(PutRequest) returns (PutResponse);
rpc Delete(DeleteRequest) returns (DeleteResponse);
rpc Stat(StatRequest) returns (StatResponse);
rpc Compact(CompactRequest) returns (CompactResponse);
rpc Close(CloseRequest) returns (CloseResponse);
rpc WriteBatch(WriteBatchRequest) returns (WriteBatchResponse);
rpc NewIteratorWithStartAndPrefix(NewIteratorWithStartAndPrefixRequest) returns (NewIteratorWithStartAndPrefixResponse);
rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse);
rpc IteratorError(IteratorErrorRequest) returns (IteratorErrorResponse);
rpc IteratorRelease(IteratorReleaseRequest) returns (IteratorReleaseResponse);
}

View File

@ -16,6 +16,8 @@ var (
TestBatchPut,
TestBatchDelete,
TestBatchReset,
TestBatchReuse,
TestBatchRewrite,
TestBatchReplay,
TestBatchInner,
TestIterator,
@ -236,6 +238,105 @@ func TestBatchReset(t *testing.T, db Database) {
}
}
// TestBatchReuse ...
func TestBatchReuse(t *testing.T, db Database) {
key1 := []byte("hello1")
value1 := []byte("world1")
key2 := []byte("hello2")
value2 := []byte("world2")
batch := db.NewBatch()
if batch == nil {
t.Fatalf("db.NewBatch returned nil")
}
if err := batch.Put(key1, value1); err != nil {
t.Fatalf("Unexpected error on batch.Put: %s", err)
}
if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if err := db.Delete(key1); err != nil {
t.Fatalf("Unexpected error on database.Delete: %s", err)
}
if has, err := db.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has unexpectedly returned true on key %s", key1)
}
batch.Reset()
if err := batch.Put(key2, value2); err != nil {
t.Fatalf("Unexpected error on batch.Put: %s", err)
}
if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if has, err := db.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has unexpectedly returned true on key %s", key1)
} else if has, err := db.Has(key2); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if !has {
t.Fatalf("db.Has unexpectedly returned false on key %s", key2)
} else if v, err := db.Get(key2); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value2, v) {
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value2)
}
}
// TestBatchRewrite ...
func TestBatchRewrite(t *testing.T, db Database) {
key := []byte("hello1")
value := []byte("world1")
batch := db.NewBatch()
if batch == nil {
t.Fatalf("db.NewBatch returned nil")
}
if err := batch.Put(key, value); err != nil {
t.Fatalf("Unexpected error on batch.Put: %s", err)
}
if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if err := db.Delete(key); err != nil {
t.Fatalf("Unexpected error on database.Delete: %s", err)
}
if has, err := db.Has(key); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has unexpectedly returned true on key %s", key)
}
if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if has, err := db.Has(key); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if !has {
t.Fatalf("db.Has unexpectedly returned false on key %s", key)
} else if v, err := db.Get(key); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value, v) {
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value)
}
}
// TestBatchReplay ...
func TestBatchReplay(t *testing.T, db Database) {
key1 := []byte("hello1")

View File

@ -6,7 +6,6 @@ package genesis
import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/evm"
"github.com/ava-labs/gecko/vms/nftfx"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/propertyfx"
@ -21,7 +20,7 @@ func Aliases(networkID uint32) (map[string][]string, map[[32]byte][]string, map[
generalAliases := map[string][]string{
"vm/" + platformvm.ID.String(): []string{"vm/platform"},
"vm/" + avm.ID.String(): []string{"vm/avm"},
"vm/" + evm.ID.String(): []string{"vm/evm"},
"vm/" + EVMID.String(): []string{"vm/evm"},
"vm/" + spdagvm.ID.String(): []string{"vm/spdag"},
"vm/" + spchainvm.ID.String(): []string{"vm/spchain"},
"vm/" + timestampvm.ID.String(): []string{"vm/timestamp"},
@ -33,7 +32,7 @@ func Aliases(networkID uint32) (map[string][]string, map[[32]byte][]string, map[
vmAliases := map[[32]byte][]string{
platformvm.ID.Key(): []string{"platform"},
avm.ID.Key(): []string{"avm"},
evm.ID.Key(): []string{"evm"},
EVMID.Key(): []string{"evm"},
spdagvm.ID.Key(): []string{"spdag"},
spchainvm.ID.Key(): []string{"spchain"},
timestampvm.ID.Key(): []string{"timestamp"},
@ -60,7 +59,7 @@ func Aliases(networkID uint32) (map[string][]string, map[[32]byte][]string, map[
case avm.ID.Equals(chain.VMID):
generalAliases["bc/"+chain.ID().String()] = []string{"X", "avm", "bc/X", "bc/avm"}
chainAliases[chain.ID().Key()] = []string{"X", "avm"}
case evm.ID.Equals(chain.VMID):
case EVMID.Equals(chain.VMID):
generalAliases["bc/"+chain.ID().String()] = []string{"C", "evm", "bc/C", "bc/evm"}
chainAliases[chain.ID().Key()] = []string{"C", "evm"}
case spdagvm.ID.Equals(chain.VMID):

View File

@ -5,7 +5,6 @@ package genesis
import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/evm"
)
// Note that since an AVA network has exactly one Platform Chain,
@ -16,8 +15,9 @@ import (
// Config contains the genesis addresses used to construct a genesis
type Config struct {
MintAddresses, FundedAddresses, FundedEVMAddresses, StakerIDs []string
ParsedMintAddresses, ParsedFundedAddresses, ParsedStakerIDs []ids.ShortID
MintAddresses, FundedAddresses, StakerIDs []string
ParsedMintAddresses, ParsedFundedAddresses, ParsedStakerIDs []ids.ShortID
EVMBytes []byte
}
func (c *Config) init() error {
@ -66,9 +66,6 @@ var (
"ZdhZv6oZrmXLyFDy6ovXAu6VxmbTsT2h",
"6cesTteH62Y5mLoDBUASaBvCXuL2AthL",
},
FundedEVMAddresses: []string{
"0x572f4D80f10f663B5049F789546f25f70Bb62a7F",
},
StakerIDs: []string{
"NX4zVkuiRJZYe6Nzzav7GXN3TakUet3Co",
"CMsa8cMw4eib1Hb8GG4xiUKAq5eE1BwUX",
@ -76,6 +73,98 @@ var (
"N86eodVZja3GEyZJTo3DFUPGpxEEvjGHs",
"EkKeGSLUbHrrtuayBtbwgWDRUiAziC3ao",
},
EVMBytes: []byte{
0x7b, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x22, 0x3a, 0x7b, 0x22, 0x63, 0x68, 0x61, 0x69,
0x6e, 0x49, 0x64, 0x22, 0x3a, 0x34, 0x33, 0x31,
0x31, 0x30, 0x2c, 0x22, 0x68, 0x6f, 0x6d, 0x65,
0x73, 0x74, 0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x64,
0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x64, 0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x53,
0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a,
0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x65, 0x69,
0x70, 0x31, 0x35, 0x30, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x65, 0x69,
0x70, 0x31, 0x35, 0x30, 0x48, 0x61, 0x73, 0x68,
0x22, 0x3a, 0x22, 0x30, 0x78, 0x32, 0x30, 0x38,
0x36, 0x37, 0x39, 0x39, 0x61, 0x65, 0x65, 0x62,
0x65, 0x61, 0x65, 0x31, 0x33, 0x35, 0x63, 0x32,
0x34, 0x36, 0x63, 0x36, 0x35, 0x30, 0x32, 0x31,
0x63, 0x38, 0x32, 0x62, 0x34, 0x65, 0x31, 0x35,
0x61, 0x32, 0x63, 0x34, 0x35, 0x31, 0x33, 0x34,
0x30, 0x39, 0x39, 0x33, 0x61, 0x61, 0x63, 0x66,
0x64, 0x32, 0x37, 0x35, 0x31, 0x38, 0x38, 0x36,
0x35, 0x31, 0x34, 0x66, 0x30, 0x22, 0x2c, 0x22,
0x65, 0x69, 0x70, 0x31, 0x35, 0x35, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x65, 0x69, 0x70, 0x31, 0x35, 0x38, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x62, 0x79, 0x7a, 0x61, 0x6e, 0x74, 0x69, 0x75,
0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x63, 0x6f, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x74, 0x69, 0x6e, 0x6f, 0x70, 0x6c,
0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x70, 0x65, 0x74, 0x65, 0x72,
0x73, 0x62, 0x75, 0x72, 0x67, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22,
0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x22,
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74,
0x61, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30, 0x30,
0x22, 0x2c, 0x22, 0x67, 0x61, 0x73, 0x4c, 0x69,
0x6d, 0x69, 0x74, 0x22, 0x3a, 0x22, 0x30, 0x78,
0x35, 0x66, 0x35, 0x65, 0x31, 0x30, 0x30, 0x22,
0x2c, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63,
0x75, 0x6c, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x78,
0x48, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x22, 0x2c, 0x22, 0x63, 0x6f, 0x69, 0x6e,
0x62, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x22, 0x2c, 0x22, 0x61, 0x6c, 0x6c, 0x6f,
0x63, 0x22, 0x3a, 0x7b, 0x22, 0x35, 0x37, 0x32,
0x66, 0x34, 0x64, 0x38, 0x30, 0x66, 0x31, 0x30,
0x66, 0x36, 0x36, 0x33, 0x62, 0x35, 0x30, 0x34,
0x39, 0x66, 0x37, 0x38, 0x39, 0x35, 0x34, 0x36,
0x66, 0x32, 0x35, 0x66, 0x37, 0x30, 0x62, 0x62,
0x36, 0x32, 0x61, 0x37, 0x66, 0x22, 0x3a, 0x7b,
0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x22, 0x3a, 0x22, 0x30, 0x78, 0x33, 0x33, 0x62,
0x32, 0x65, 0x33, 0x63, 0x39, 0x66, 0x64, 0x30,
0x38, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x7d, 0x2c,
0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61,
0x73, 0x68, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22,
0x7d,
},
}
DefaultConfig = Config{
MintAddresses: []string{},
@ -83,10 +172,6 @@ var (
// Private key: ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN
"6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV",
},
FundedEVMAddresses: []string{
// Private key: evm.GenesisTestKey
evm.GenesisTestAddr,
},
StakerIDs: []string{
"7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg",
"MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ",
@ -94,6 +179,98 @@ var (
"GWPcbFJZFfZreETSoWjPimr846mXEKCtu",
"P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5",
},
EVMBytes: []byte{
0x7b, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x22, 0x3a, 0x7b, 0x22, 0x63, 0x68, 0x61, 0x69,
0x6e, 0x49, 0x64, 0x22, 0x3a, 0x34, 0x33, 0x31,
0x31, 0x30, 0x2c, 0x22, 0x68, 0x6f, 0x6d, 0x65,
0x73, 0x74, 0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x64,
0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x64, 0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x53,
0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a,
0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x65, 0x69,
0x70, 0x31, 0x35, 0x30, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x65, 0x69,
0x70, 0x31, 0x35, 0x30, 0x48, 0x61, 0x73, 0x68,
0x22, 0x3a, 0x22, 0x30, 0x78, 0x32, 0x30, 0x38,
0x36, 0x37, 0x39, 0x39, 0x61, 0x65, 0x65, 0x62,
0x65, 0x61, 0x65, 0x31, 0x33, 0x35, 0x63, 0x32,
0x34, 0x36, 0x63, 0x36, 0x35, 0x30, 0x32, 0x31,
0x63, 0x38, 0x32, 0x62, 0x34, 0x65, 0x31, 0x35,
0x61, 0x32, 0x63, 0x34, 0x35, 0x31, 0x33, 0x34,
0x30, 0x39, 0x39, 0x33, 0x61, 0x61, 0x63, 0x66,
0x64, 0x32, 0x37, 0x35, 0x31, 0x38, 0x38, 0x36,
0x35, 0x31, 0x34, 0x66, 0x30, 0x22, 0x2c, 0x22,
0x65, 0x69, 0x70, 0x31, 0x35, 0x35, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x65, 0x69, 0x70, 0x31, 0x35, 0x38, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x62, 0x79, 0x7a, 0x61, 0x6e, 0x74, 0x69, 0x75,
0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x63, 0x6f, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x74, 0x69, 0x6e, 0x6f, 0x70, 0x6c,
0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x70, 0x65, 0x74, 0x65, 0x72,
0x73, 0x62, 0x75, 0x72, 0x67, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22,
0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x22,
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74,
0x61, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30, 0x30,
0x22, 0x2c, 0x22, 0x67, 0x61, 0x73, 0x4c, 0x69,
0x6d, 0x69, 0x74, 0x22, 0x3a, 0x22, 0x30, 0x78,
0x35, 0x66, 0x35, 0x65, 0x31, 0x30, 0x30, 0x22,
0x2c, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63,
0x75, 0x6c, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x78,
0x48, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x22, 0x2c, 0x22, 0x63, 0x6f, 0x69, 0x6e,
0x62, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x22, 0x30,
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x22, 0x2c, 0x22, 0x61, 0x6c, 0x6c, 0x6f,
0x63, 0x22, 0x3a, 0x7b, 0x22, 0x37, 0x35, 0x31,
0x61, 0x30, 0x62, 0x39, 0x36, 0x65, 0x31, 0x30,
0x34, 0x32, 0x62, 0x65, 0x65, 0x37, 0x38, 0x39,
0x34, 0x35, 0x32, 0x65, 0x63, 0x62, 0x32, 0x30,
0x32, 0x35, 0x33, 0x66, 0x62, 0x61, 0x34, 0x30,
0x64, 0x62, 0x65, 0x38, 0x35, 0x22, 0x3a, 0x7b,
0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x22, 0x3a, 0x22, 0x30, 0x78, 0x33, 0x33, 0x62,
0x32, 0x65, 0x33, 0x63, 0x39, 0x66, 0x64, 0x30,
0x38, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x7d, 0x2c,
0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x22,
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61,
0x73, 0x68, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22,
0x7d,
},
}
)

View File

@ -6,14 +6,8 @@ package genesis
import (
"errors"
"fmt"
"math/big"
"time"
"github.com/ava-labs/coreth/core"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/params"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/json"
@ -21,7 +15,6 @@ import (
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/evm"
"github.com/ava-labs/gecko/vms/nftfx"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/propertyfx"
@ -31,6 +24,11 @@ import (
"github.com/ava-labs/gecko/vms/timestampvm"
)
// ID of the EVM VM
var (
EVMID = ids.NewID([32]byte{'e', 'v', 'm'})
)
// Genesis returns the genesis data of the Platform Chain.
// Since an AVA network has exactly one Platform Chain, and the Platform Chain
// defines the genesis state of the network (who is staking, which chains exist,
@ -76,50 +74,6 @@ func FromConfig(networkID uint32, config *Config) ([]byte, error) {
avmSS := avm.StaticService{}
err := avmSS.BuildGenesis(nil, &avmArgs, &avmReply)
if err != nil {
panic(err)
}
// Specify the genesis state of Athereum (the built-in instance of the EVM)
evmBalance, success := new(big.Int).SetString("33b2e3c9fd0804000000000", 16)
if success != true {
return nil, errors.New("problem creating evm genesis state")
}
alloc := core.GenesisAlloc{}
for _, addr := range config.FundedEVMAddresses {
alloc[common.HexToAddress(addr)] = core.GenesisAccount{
Balance: evmBalance,
}
}
evmArgs := core.Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(43110),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
},
Nonce: 0,
Timestamp: 0,
ExtraData: []byte{0},
GasLimit: 100000000,
Difficulty: big.NewInt(0),
Mixhash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
Coinbase: common.HexToAddress("0x0000000000000000000000000000000000000000"),
Alloc: alloc,
Number: 0,
GasUsed: 0,
ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
}
evmSS := evm.StaticService{}
evmReply, err := evmSS.BuildGenesis(nil, &evmArgs)
if err != nil {
return nil, err
}
@ -214,9 +168,9 @@ func FromConfig(networkID uint32, config *Config) ([]byte, error) {
Name: "X-Chain",
},
platformvm.APIChain{
GenesisData: evmReply,
GenesisData: formatting.CB58{Bytes: config.EVMBytes},
SubnetID: platformvm.DefaultSubnetID,
VMID: evm.ID,
VMID: EVMID,
Name: "C-Chain",
},
platformvm.APIChain{

View File

@ -8,7 +8,6 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/evm"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/spchainvm"
"github.com/ava-labs/gecko/vms/spdagvm"
@ -97,7 +96,7 @@ func TestAliases(t *testing.T) {
t.Fatalf("Should have a custom alias from the vm")
} else if _, exists := generalAliases["vm/"+avm.ID.String()]; !exists {
t.Fatalf("Should have a custom alias from the vm")
} else if _, exists := generalAliases["vm/"+evm.ID.String()]; !exists {
} else if _, exists := generalAliases["vm/"+EVMID.String()]; !exists {
t.Fatalf("Should have a custom alias from the vm")
} else if _, exists := generalAliases["vm/"+spdagvm.ID.String()]; !exists {
t.Fatalf("Should have a custom alias from the vm")
@ -135,12 +134,12 @@ func TestVMGenesis(t *testing.T) {
},
{
networkID: CascadeID,
vmID: evm.ID,
vmID: EVMID,
expectedID: "2mUYSXfLrDtigwbzj1LxKVsHwELghc5sisoXrzJwLqAAQHF4i",
},
{
networkID: LocalID,
vmID: evm.ID,
vmID: EVMID,
expectedID: "tZGm6RCkeGpVETUTp11DW3UYFZmm69zfqxchpHrSF7wgy8rmw",
},
}

View File

@ -7,10 +7,10 @@ import (
"fmt"
"path"
"github.com/ava-labs/gecko/nat"
"github.com/ava-labs/gecko/node"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/go-ethereum/p2p/nat"
)
// main is the primary entry point to Ava. This can either create a CLI to an
@ -61,26 +61,11 @@ func main() {
log.Warn("assertions are enabled. This may slow down execution")
}
natChan := make(chan struct{})
defer close(natChan)
mapper := nat.NewDefaultMapper(log, Config.Nat, nat.TCP, "gecko")
defer mapper.UnmapAllPorts()
go nat.Map(
/*nat=*/ Config.Nat,
/*closeChannel=*/ natChan,
/*protocol=*/ "TCP",
/*internetPort=*/ int(Config.StakingIP.Port),
/*localPort=*/ int(Config.StakingIP.Port),
/*name=*/ "Gecko Staking Server",
)
go nat.Map(
/*nat=*/ Config.Nat,
/*closeChannel=*/ natChan,
/*protocol=*/ "TCP",
/*internetPort=*/ int(Config.HTTPPort),
/*localPort=*/ int(Config.HTTPPort),
/*name=*/ "Gecko HTTP Server",
)
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
log.Debug("initializing node state")
// MainNode is a global variable in the node.go file

View File

@ -12,12 +12,11 @@ import (
"path"
"strings"
"github.com/ava-labs/go-ethereum/p2p/nat"
"github.com/ava-labs/gecko/database/leveldb"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/nat"
"github.com/ava-labs/gecko/node"
"github.com/ava-labs/gecko/snow/networking/router"
"github.com/ava-labs/gecko/utils"
@ -90,6 +89,9 @@ func init() {
fs.StringVar(&Config.StakingKeyFile, "staking-tls-key-file", "keys/staker.key", "TLS private key file for staking connections")
fs.StringVar(&Config.StakingCertFile, "staking-tls-cert-file", "keys/staker.crt", "TLS certificate file for staking connections")
// Plugins:
fs.StringVar(&Config.PluginDir, "plugin-dir", "./build/plugins", "Plugin directory for Ava VMs")
// Logging:
logsDir := fs.String("log-dir", "", "Logging directory for Ava")
logLevel := fs.String("log-level", "info", "The log level. Should be one of {verbo, debug, info, warn, error, fatal, off}")
@ -141,12 +143,12 @@ func init() {
Config.DB = memdb.New()
}
Config.Nat = nat.Any()
Config.Nat = nat.NewRouter()
var ip net.IP
// If public IP is not specified, get it using shell command dig
if *consensusIP == "" {
ip, err = Config.Nat.ExternalIP()
ip, err = Config.Nat.IP()
errs.Add(fmt.Errorf(
"%s\n"+
"If you are trying to create a local network, try adding --public-ip=127.0.0.1\n"+

143
nat/mapper.go Normal file
View File

@ -0,0 +1,143 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nat
import (
"sync"
"time"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/wrappers"
)
const (
defaultMappingTimeout = 30 * time.Minute
defaultMappingUpdateInterval = 3 * defaultMappingTimeout / 4
)
// Mapper maps port
type Mapper interface {
MapPort(newInternalPort, newExternalPort uint16) error
UnmapAllPorts() error
}
type mapper struct {
log logging.Logger
router Router
networkProtocol NetworkProtocol
mappingNames string
mappingTimeout time.Duration
mappingUpdateInterval time.Duration
closer chan struct{}
wg sync.WaitGroup
errLock sync.Mutex
errs wrappers.Errs
}
// NewMapper returns a new mapper that can map ports on a router
func NewMapper(
log logging.Logger,
router Router,
networkProtocol NetworkProtocol,
mappingNames string,
mappingTimeout time.Duration,
mappingUpdateInterval time.Duration,
) Mapper {
return &mapper{
log: log,
router: router,
networkProtocol: networkProtocol,
mappingNames: mappingNames,
mappingTimeout: mappingTimeout,
mappingUpdateInterval: mappingUpdateInterval,
closer: make(chan struct{}),
}
}
// NewDefaultMapper returns a new mapper that can map ports on a router with
// default settings
func NewDefaultMapper(
log logging.Logger,
router Router,
networkProtocol NetworkProtocol,
mappingNames string,
) Mapper {
return NewMapper(
log,
router,
networkProtocol,
mappingNames,
defaultMappingTimeout, // uses the default value
defaultMappingUpdateInterval, // uses the default value
)
}
// MapPort maps a local port to a port on the router until UnmapAllPorts is
// called.
func (m *mapper) MapPort(newInternalPort, newExternalPort uint16) error {
m.wg.Add(1)
go m.mapPort(newInternalPort, newExternalPort)
return nil
}
func (m *mapper) mapPort(newInternalPort, newExternalPort uint16) {
// duration is set to 0 here so that the select case will execute
// immediately
updateTimer := time.NewTimer(0)
defer func() {
updateTimer.Stop()
m.errLock.Lock()
m.errs.Add(m.router.UnmapPort(
m.networkProtocol,
newInternalPort,
newExternalPort))
m.errLock.Unlock()
m.log.Debug("Unmapped external port %d to internal port %d",
newExternalPort,
newInternalPort)
m.wg.Done()
}()
for {
select {
case <-updateTimer.C:
err := m.router.MapPort(
m.networkProtocol,
newInternalPort,
newExternalPort,
m.mappingNames,
m.mappingTimeout)
if err != nil {
m.errLock.Lock()
m.errs.Add(err)
m.errLock.Unlock()
m.log.Debug("Failed to add mapping from external port %d to internal port %d due to %s",
newExternalPort,
newInternalPort,
err)
} else {
m.log.Debug("Mapped external port %d to internal port %d",
newExternalPort,
newInternalPort)
}
// remap the port in m.mappingUpdateInterval
updateTimer.Reset(m.mappingUpdateInterval)
case _, _ = <-m.closer:
return // only return when all ports are unmapped
}
}
}
func (m *mapper) UnmapAllPorts() error {
close(m.closer)
m.wg.Wait()
return m.errs.Err
}

28
nat/no_router.go Normal file
View File

@ -0,0 +1,28 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nat
import (
"errors"
"net"
"time"
)
var (
errNoRouter = errors.New("no nat enabled router was discovered")
)
type noRouter struct{}
func (noRouter) MapPort(_ NetworkProtocol, _, _ uint16, _ string, _ time.Duration) error {
return errNoRouter
}
func (noRouter) UnmapPort(_ NetworkProtocol, _, _ uint16) error {
return errNoRouter
}
func (noRouter) IP() (net.IP, error) {
return nil, errNoRouter
}

71
nat/pmp.go Normal file
View File

@ -0,0 +1,71 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nat
import (
"net"
"time"
"github.com/jackpal/gateway"
"github.com/jackpal/go-nat-pmp"
)
var (
pmpClientTimeout = 500 * time.Millisecond
)
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
// the common interface.
type pmpClient struct {
client *natpmp.Client
}
func (pmp *pmpClient) MapPort(
networkProtocol NetworkProtocol,
newInternalPort uint16,
newExternalPort uint16,
mappingName string,
mappingDuration time.Duration) error {
protocol := string(networkProtocol)
internalPort := int(newInternalPort)
externalPort := int(newExternalPort)
// go-nat-pmp uses seconds to denote their lifetime
lifetime := int(mappingDuration / time.Second)
_, err := pmp.client.AddPortMapping(protocol, internalPort, externalPort, lifetime)
return err
}
func (pmp *pmpClient) UnmapPort(
networkProtocol NetworkProtocol,
internalPort uint16,
_ uint16) error {
protocol := string(networkProtocol)
internalPortInt := int(internalPort)
_, err := pmp.client.AddPortMapping(protocol, internalPortInt, 0, 0)
return err
}
func (pmp *pmpClient) IP() (net.IP, error) {
response, err := pmp.client.GetExternalAddress()
if err != nil {
return nil, err
}
return response.ExternalIPAddress[:], nil
}
func getPMPRouter() Router {
gatewayIP, err := gateway.DiscoverGateway()
if err != nil {
return nil
}
pmp := &pmpClient{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)}
if _, err := pmp.IP(); err != nil {
return nil
}
return pmp
}

65
nat/router.go Normal file
View File

@ -0,0 +1,65 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package nat performs network address translation and provides helpers for
// routing ports.
package nat
import (
"net"
"time"
)
// NetworkProtocol is a protocol that will be used through a port
type NetworkProtocol string
// Available protocol
const (
TCP NetworkProtocol = "TCP"
UDP NetworkProtocol = "UDP"
)
// Router provides a standard NAT router functions. Specifically, allowing the
// fetching of public IPs and port forwarding to this computer.
type Router interface {
// mapPort creates a mapping between a port on the local computer to an
// external port on the router.
//
// The mappingName is something displayed on the router, so it is included
// for completeness.
MapPort(
networkProtocol NetworkProtocol,
newInternalPort uint16,
newExternalPort uint16,
mappingName string,
mappingDuration time.Duration) error
// UnmapPort clears a mapping that was previous made by a call to MapPort
UnmapPort(
networkProtocol NetworkProtocol,
internalPort uint16,
externalPort uint16) error
// Returns the routers IP address on the network the router considers
// external
IP() (net.IP, error)
}
// NewRouter returns a new router discovered on the local network
func NewRouter() Router {
routers := make(chan Router)
// Because getting a router can take a noticeable amount of time to error,
// we run these requests in parallel
go func() {
routers <- getUPnPRouter()
}()
go func() {
routers <- getPMPRouter()
}()
for i := 0; i < 2; i++ {
if router := <-routers; router != nil {
return router
}
}
return noRouter{}
}

253
nat/upnp.go Normal file
View File

@ -0,0 +1,253 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nat
import (
"errors"
"fmt"
"net"
"time"
"github.com/huin/goupnp"
"github.com/huin/goupnp/dcps/internetgateway1"
"github.com/huin/goupnp/dcps/internetgateway2"
)
const (
soapTimeout = time.Second
)
var (
errNoGateway = errors.New("Failed to connect to any avaliable gateways")
)
// upnpClient is the interface used by goupnp for their client implementations
type upnpClient interface {
// attempts to map connection using the provided protocol from the external
// port to the internal port for the lease duration.
AddPortMapping(
newRemoteHost string,
newExternalPort uint16,
newProtocol string,
newInternalPort uint16,
newInternalClient string,
newEnabled bool,
newPortMappingDescription string,
newLeaseDuration uint32) error
// attempt to remove any mapping from the external port.
DeletePortMapping(
newRemoteHost string,
newExternalPort uint16,
newProtocol string) error
// attempts to return the external IP address, formatted as a string.
GetExternalIPAddress() (ip string, err error)
// returns if there is rsip available, nat enabled, or an unexpected error.
GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error)
}
type upnpRouter struct {
root *goupnp.RootDevice
client upnpClient
}
func (n *upnpRouter) MapPort(
networkProtocol NetworkProtocol,
newInternalPort uint16,
newExternalPort uint16,
mappingName string,
mappingDuration time.Duration,
) error {
ip, err := n.localAddress()
if err != nil {
return err
}
protocol := string(networkProtocol)
// goupnp uses seconds to denote their lifetime
lifetime := uint32(mappingDuration / time.Second)
// UnmapPort's error is intentionally dropped, because the mapping may not
// exist.
n.UnmapPort(networkProtocol, newInternalPort, newExternalPort)
return n.client.AddPortMapping(
"", // newRemoteHost isn't used to limit the mapping to a host
newExternalPort,
protocol,
newInternalPort,
ip.String(), // newInternalClient is the client traffic should be sent to
true, // newEnabled enables port mappings
mappingName,
lifetime,
)
}
func (n *upnpRouter) UnmapPort(networkProtocol NetworkProtocol, _, externalPort uint16) error {
protocol := string(networkProtocol)
return n.client.DeletePortMapping(
"", // newRemoteHost isn't used to limit the mapping to a host
externalPort,
protocol)
}
func (n *upnpRouter) IP() (net.IP, error) {
ipStr, err := n.client.GetExternalIPAddress()
if err != nil {
return nil, err
}
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, fmt.Errorf("invalid IP %s", ipStr)
}
return ip, nil
}
func (n *upnpRouter) localAddress() (net.IP, error) {
// attempt to get an address on the router
deviceAddr, err := net.ResolveUDPAddr("udp4", n.root.URLBase.Host)
if err != nil {
return nil, err
}
deviceIP := deviceAddr.IP
netInterfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
// attempt to find one of my ips that the router would know about
for _, netInterface := range netInterfaces {
addrs, err := netInterface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
// this is pretty janky, but it seems to be the best way to get the
// ip mask and properly check if the ip references the device we are
// connected to
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
if ipNet.Contains(deviceIP) {
return ipNet.IP, nil
}
}
}
return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP)
}
// getUPnPRouter searches for all Gateway Devices that have avaliable
// connections in the goupnp library and returns the first connection it can
// find.
func getUPnPRouter() Router {
routers := make(chan *upnpRouter)
// Because DiscoverDevices takes a noticeable amount of time to error, we
// run these requests in parallel
go func() {
routers <- connectToGateway(internetgateway1.URN_WANConnectionDevice_1, gateway1)
}()
go func() {
routers <- connectToGateway(internetgateway2.URN_WANConnectionDevice_2, gateway2)
}()
for i := 0; i < 2; i++ {
if router := <-routers; router != nil {
return router
}
}
return nil
}
func gateway1(client goupnp.ServiceClient) upnpClient {
switch client.Service.ServiceType {
case internetgateway1.URN_WANIPConnection_1:
return &internetgateway1.WANIPConnection1{ServiceClient: client}
case internetgateway1.URN_WANPPPConnection_1:
return &internetgateway1.WANPPPConnection1{ServiceClient: client}
default:
return nil
}
}
func gateway2(client goupnp.ServiceClient) upnpClient {
switch client.Service.ServiceType {
case internetgateway2.URN_WANIPConnection_1:
return &internetgateway2.WANIPConnection1{ServiceClient: client}
case internetgateway2.URN_WANIPConnection_2:
return &internetgateway2.WANIPConnection2{ServiceClient: client}
case internetgateway2.URN_WANPPPConnection_1:
return &internetgateway2.WANPPPConnection1{ServiceClient: client}
default:
return nil
}
}
func connectToGateway(deviceType string, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter {
devs, err := goupnp.DiscoverDevices(deviceType)
if err != nil {
return nil
}
// we are iterating over all the network devices, acting a possible roots
for i := range devs {
dev := &devs[i]
if dev.Root == nil {
continue
}
// the root device may be a router, so attempt to connect to that
rootDevice := &dev.Root.Device
if upnp := getRouter(dev, rootDevice, toClient); upnp != nil {
return upnp
}
// attempt to connect to any sub devices
devices := rootDevice.Devices
for i := range devices {
if upnp := getRouter(dev, &devices[i], toClient); upnp != nil {
return upnp
}
}
}
return nil
}
func getRouter(rootDevice *goupnp.MaybeRootDevice, device *goupnp.Device, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter {
for i := range device.Services {
service := &device.Services[i]
soapClient := service.NewSOAPClient()
// make sure the client times out if needed
soapClient.HTTPClient.Timeout = soapTimeout
// attempt to create a client connection
serviceClient := goupnp.ServiceClient{
SOAPClient: soapClient,
RootDevice: rootDevice.Root,
Location: rootDevice.Location,
Service: service,
}
client := toClient(serviceClient)
if client == nil {
continue
}
// check whether port mapping is enabled
if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat {
continue
}
// we found a router!
return &upnpRouter{
root: rootDevice.Root,
client: client,
}
}
return nil
}

View File

@ -4,9 +4,8 @@
package node
import (
"github.com/ava-labs/go-ethereum/p2p/nat"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/nat"
"github.com/ava-labs/gecko/snow/consensus/avalanche"
"github.com/ava-labs/gecko/snow/networking/router"
"github.com/ava-labs/gecko/utils"
@ -16,7 +15,7 @@ import (
// Config contains all of the configurations of an Ava node.
type Config struct {
// protocol to use for opening the network interface
Nat nat.Interface
Nat nat.Router
// ID of the network this node should connect to
NetworkID uint32
@ -56,6 +55,9 @@ type Config struct {
// Logging configuration
LoggingConfig logging.Config
// Plugin directory
PluginDir string
// Consensus configuration
ConsensusParams avalanche.Parameters

View File

@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"path"
"sync"
"unsafe"
@ -39,10 +40,10 @@ import (
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/evm"
"github.com/ava-labs/gecko/vms/nftfx"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/propertyfx"
"github.com/ava-labs/gecko/vms/rpcchainvm"
"github.com/ava-labs/gecko/vms/secp256k1fx"
"github.com/ava-labs/gecko/vms/spchainvm"
"github.com/ava-labs/gecko/vms/spdagvm"
@ -359,7 +360,7 @@ func (n *Node) initNodeID() error {
}
// Create the vmManager and register the following vms:
// AVM, EVM, Simple Payments DAG, Simple Payments Chain
// AVM, Simple Payments DAG, Simple Payments Chain
// The Platform VM is registered in initStaking because
// its factory needs to reference n.chainManager, which is nil right now
func (n *Node) initVMManager() error {
@ -376,7 +377,7 @@ func (n *Node) initVMManager() error {
AVA: avaAssetID,
Platform: ids.Empty,
}),
n.vmManager.RegisterVMFactory(evm.ID, &evm.Factory{}),
n.vmManager.RegisterVMFactory(genesis.EVMID, &rpcchainvm.Factory{Path: path.Join(n.Config.PluginDir, "evm")}),
n.vmManager.RegisterVMFactory(spdagvm.ID, &spdagvm.Factory{TxFee: n.Config.AvaTxFee}),
n.vmManager.RegisterVMFactory(spchainvm.ID, &spchainvm.Factory{}),
n.vmManager.RegisterVMFactory(timestampvm.ID, &timestampvm.Factory{}),

View File

@ -3,18 +3,31 @@
# Ted: contact me when you make any changes
PREFIX="${PREFIX:-$(pwd)/build}"
PLUGIN_PREFIX="$PREFIX/plugins"
SRC_DIR="$(dirname "${BASH_SOURCE[0]}")"
source "$SRC_DIR/env.sh"
CORETH_PKG=github.com/ava-labs/coreth
CORETH_PATH="$GOPATH/src/$CORETH_PKG"
if [[ -d "$CORETH_PATH/.git" ]]; then
cd "$CORETH_PATH"
go get -t -v -d "./..."
cd -
else
go get -t -v -d "$CORETH_PKG/..."
fi
GECKO_PKG=github.com/ava-labs/gecko
GECKO_PATH="$GOPATH/src/$GECKO_PKG"
if [[ -d "$GECKO_PATH/.git" ]]; then
cd "$GECKO_PATH"
go get -t -v "./..."
go get -t -v -d "./..."
cd -
else
go get -t -v "$GECKO_PKG/..."
go get -t -v -d "$GECKO_PKG/..."
fi
go build -o "$PREFIX/ava" "$GECKO_PATH/main/"*.go
go build -o "$PREFIX/xputtest" "$GECKO_PATH/xputtest/"*.go
go build -o "$PLUGIN_PREFIX/evm" "$CORETH_PATH/plugin/"*.go

View File

@ -17,8 +17,6 @@ fi
if [[ ! -d "$WORKPREFIX" ]]; then
mkdir -p "$WORKPREFIX"
git config --global credential.helper cache
git clone https://github.com/ava-labs/coreth.git "$WORKPREFIX/coreth"
git clone --depth 1 https://github.com/ava-labs/go-ethereum.git "$WORKPREFIX/go-ethereum"
git clone https://github.com/ava-labs/gecko.git "$WORKPREFIX/gecko"
fi
GECKO_COMMIT="$(git --git-dir="$WORKPREFIX/gecko/.git" rev-parse --short HEAD)"

View File

@ -8,7 +8,7 @@ import (
)
// LockOption allows the vm to specify their lock option based on their endpoint
type LockOption int
type LockOption uint32
// List of all allowed options
const (

View File

@ -10,7 +10,7 @@ import (
// TODO: Consider renaming Message to, say, VMMessage
// Message is an enum of the message types that vms can send to consensus
type Message int
type Message uint32
const (
// PendingTxs notifies a consensus engine that

View File

@ -41,7 +41,7 @@ func init() {
RSA: &FactoryRSA{},
RSAPSS: &FactoryRSAPSS{},
ED25519: &FactoryED25519{},
SECP256K1: &FactorySECP256K1{},
SECP256K1: &FactorySECP256K1R{},
}
for _, f := range factories {
fKeys := []PublicKey{}

View File

@ -1,149 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package crypto
import (
"crypto/ecdsa"
"crypto/rand"
"math/big"
"github.com/ava-labs/go-ethereum/crypto"
"github.com/ava-labs/go-ethereum/crypto/secp256k1"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/hashing"
)
const (
// SECP256K1SigLen is the number of bytes in a secp2561k signature
SECP256K1SigLen = 64
// SECP256K1SKLen is the number of bytes in a secp2561k private key
SECP256K1SKLen = 32
// SECP256K1PKLen is the number of bytes in a secp2561k public key
SECP256K1PKLen = 33
)
// FactorySECP256K1 ...
type FactorySECP256K1 struct{}
// NewPrivateKey implements the Factory interface
func (*FactorySECP256K1) NewPrivateKey() (PrivateKey, error) {
k, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
if err != nil {
return nil, err
}
return &PrivateKeySECP256K1{sk: k}, nil
}
// ToPublicKey implements the Factory interface
func (*FactorySECP256K1) ToPublicKey(b []byte) (PublicKey, error) {
key, err := crypto.DecompressPubkey(b)
return &PublicKeySECP256K1{
pk: key,
bytes: b,
}, err
}
// ToPrivateKey implements the Factory interface
func (*FactorySECP256K1) ToPrivateKey(b []byte) (PrivateKey, error) {
key, err := crypto.ToECDSA(b)
return &PrivateKeySECP256K1{
sk: key,
bytes: b,
}, err
}
// PublicKeySECP256K1 ...
type PublicKeySECP256K1 struct {
pk *ecdsa.PublicKey
addr ids.ShortID
bytes []byte
}
// Verify implements the PublicKey interface
func (k *PublicKeySECP256K1) Verify(msg, sig []byte) bool {
return k.VerifyHash(hashing.ComputeHash256(msg), sig)
}
// VerifyHash implements the PublicKey interface
func (k *PublicKeySECP256K1) VerifyHash(hash, sig []byte) bool {
if verifySECP256K1SignatureFormat(sig) != nil {
return false
}
return crypto.VerifySignature(k.Bytes(), hash, sig)
}
// Address implements the PublicKey interface
func (k *PublicKeySECP256K1) Address() ids.ShortID {
if k.addr.IsZero() {
addr, err := ids.ToShortID(hashing.PubkeyBytesToAddress(k.Bytes()))
if err != nil {
panic(err)
}
k.addr = addr
}
return k.addr
}
// Bytes implements the PublicKey interface
func (k *PublicKeySECP256K1) Bytes() []byte {
if k.bytes == nil {
k.bytes = crypto.CompressPubkey(k.pk)
}
return k.bytes
}
// PrivateKeySECP256K1 ...
type PrivateKeySECP256K1 struct {
sk *ecdsa.PrivateKey
pk *PublicKeySECP256K1
bytes []byte
}
// PublicKey implements the PrivateKey interface
func (k *PrivateKeySECP256K1) PublicKey() PublicKey {
if k.pk == nil {
k.pk = &PublicKeySECP256K1{pk: (*ecdsa.PublicKey)(&k.sk.PublicKey)}
}
return k.pk
}
// Sign implements the PrivateKey interface
func (k *PrivateKeySECP256K1) Sign(msg []byte) ([]byte, error) {
return k.SignHash(hashing.ComputeHash256(msg))
}
// SignHash implements the PrivateKey interface
func (k *PrivateKeySECP256K1) SignHash(hash []byte) ([]byte, error) {
sig, err := crypto.Sign(hash, k.sk)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], err
}
// Bytes implements the PrivateKey interface
func (k *PrivateKeySECP256K1) Bytes() []byte {
if k.bytes == nil {
k.bytes = make([]byte, SECP256K1SKLen)
bytes := k.sk.D.Bytes()
copy(k.bytes[SECP256K1SKLen-len(bytes):], bytes)
}
return k.bytes
}
func verifySECP256K1SignatureFormat(sig []byte) error {
if len(sig) != SECP256K1SigLen {
return errInvalidSigLen
}
var r, s big.Int
r.SetBytes(sig[:32])
s.SetBytes(sig[32:])
if !crypto.ValidateSignatureValues(0, &r, &s, true) {
return errMutatedSig
}
return nil
}

View File

@ -1,48 +0,0 @@
package crypto
import (
"testing"
"github.com/ava-labs/gecko/utils/hashing"
)
// NumRecoveries is the number of recoveries to run per operation
const NumRecoveries = 1
var (
secpSigs [][]byte
)
func init() {
factory := FactorySECP256K1R{}
hash := hashing.ComputeHash256(nil)
for i := byte(0); i < NumRecoveries; i++ {
key, err := factory.NewPrivateKey()
if err != nil {
panic(err)
}
sig, err := key.SignHash(hash)
if err != nil {
panic(err)
}
secpSigs = append(secpSigs, sig)
}
}
func recover() {
factory := FactorySECP256K1R{}
hash := hashing.ComputeHash256(nil)
for _, sig := range secpSigs {
if _, err := factory.RecoverHashPublicKey(hash, sig); err != nil {
panic(err)
}
}
}
// BenchmarkSecp256k1RecoverVerify runs the benchmark with secp sig
func BenchmarkSecp256k1RecoverVerify(b *testing.B) {
for n := 0; n < b.N; n++ {
recover()
}
}

View File

@ -5,13 +5,11 @@ package crypto
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"math/big"
"errors"
"sort"
"github.com/ava-labs/go-ethereum/crypto"
"github.com/ava-labs/go-ethereum/crypto/secp256k1"
"github.com/decred/dcrd/dcrec/secp256k1"
"github.com/decred/dcrd/dcrec/secp256k1/ecdsa"
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/ids"
@ -31,6 +29,14 @@ const (
// SECP256K1RPKLen is the number of bytes in a secp2561k recoverable public
// key
SECP256K1RPKLen = 33
// from the decred library:
// compactSigMagicOffset is a value used when creating the compact signature
// recovery code inherited from Bitcoin and has no meaning, but has been
// retained for compatibility. For historical purposes, it was originally
// picked to avoid a binary representation that would allow compact
// signatures to be mistaken for other components.
compactSigMagicOffset = 27
)
// FactorySECP256K1R ...
@ -38,16 +44,13 @@ type FactorySECP256K1R struct{ Cache cache.LRU }
// NewPrivateKey implements the Factory interface
func (*FactorySECP256K1R) NewPrivateKey() (PrivateKey, error) {
k, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
if err != nil {
return nil, err
}
return &PrivateKeySECP256K1R{sk: k}, nil
k, err := secp256k1.GeneratePrivateKey()
return &PrivateKeySECP256K1R{sk: k}, err
}
// ToPublicKey implements the Factory interface
func (*FactorySECP256K1R) ToPublicKey(b []byte) (PublicKey, error) {
key, err := crypto.DecompressPubkey(b)
key, err := secp256k1.ParsePubKey(b)
return &PublicKeySECP256K1R{
pk: key,
bytes: b,
@ -56,11 +59,10 @@ func (*FactorySECP256K1R) ToPublicKey(b []byte) (PublicKey, error) {
// ToPrivateKey implements the Factory interface
func (*FactorySECP256K1R) ToPrivateKey(b []byte) (PrivateKey, error) {
key, err := crypto.ToECDSA(b)
return &PrivateKeySECP256K1R{
sk: key,
sk: secp256k1.PrivKeyFromBytes(b),
bytes: b,
}, err
}, nil
}
// RecoverPublicKey returns the public key from a 65 byte signature
@ -75,25 +77,35 @@ func (f *FactorySECP256K1R) RecoverHashPublicKey(hash, sig []byte) (PublicKey, e
copy(cacheBytes[len(hash):], sig)
id := ids.NewID(hashing.ComputeHash256Array(cacheBytes))
if cachedPublicKey, ok := f.Cache.Get(id); ok {
return cachedPublicKey.(*PublicKeySECP256K1), nil
return cachedPublicKey.(*PublicKeySECP256K1R), nil
}
if err := verifySECP256K1RSignatureFormat(sig); err != nil {
return nil, err
}
rawPubkey, err := crypto.SigToPub(hash, sig)
sig, err := sigToRawSig(sig)
if err != nil {
return nil, err
}
pubkey := &PublicKeySECP256K1{pk: rawPubkey}
rawPubkey, compressed, err := ecdsa.RecoverCompact(sig, hash)
if err != nil {
return nil, err
}
if compressed {
return nil, errors.New("wasn't expecting a compresses key")
}
pubkey := &PublicKeySECP256K1R{pk: rawPubkey}
f.Cache.Put(id, pubkey)
return pubkey, nil
}
// PublicKeySECP256K1R ...
type PublicKeySECP256K1R struct {
pk *ecdsa.PublicKey
pk *secp256k1.PublicKey
addr ids.ShortID
bytes []byte
}
@ -105,10 +117,12 @@ func (k *PublicKeySECP256K1R) Verify(msg, sig []byte) bool {
// VerifyHash implements the PublicKey interface
func (k *PublicKeySECP256K1R) VerifyHash(hash, sig []byte) bool {
if verifySECP256K1RSignatureFormat(sig) != nil {
factory := FactorySECP256K1R{}
pk, err := factory.RecoverHashPublicKey(hash, sig)
if err != nil {
return false
}
return crypto.VerifySignature(k.Bytes(), hash, sig[:SECP256K1RSigLen-1])
return k.Address().Equals(pk.Address())
}
// Address implements the PublicKey interface
@ -126,14 +140,14 @@ func (k *PublicKeySECP256K1R) Address() ids.ShortID {
// Bytes implements the PublicKey interface
func (k *PublicKeySECP256K1R) Bytes() []byte {
if k.bytes == nil {
k.bytes = crypto.CompressPubkey(k.pk)
k.bytes = k.pk.SerializeCompressed()
}
return k.bytes
}
// PrivateKeySECP256K1R ...
type PrivateKeySECP256K1R struct {
sk *ecdsa.PrivateKey
sk *secp256k1.PrivateKey
pk *PublicKeySECP256K1R
bytes []byte
}
@ -141,7 +155,7 @@ type PrivateKeySECP256K1R struct {
// PublicKey implements the PrivateKey interface
func (k *PrivateKeySECP256K1R) PublicKey() PublicKey {
if k.pk == nil {
k.pk = &PublicKeySECP256K1R{pk: (*ecdsa.PublicKey)(&k.sk.PublicKey)}
k.pk = &PublicKeySECP256K1R{pk: k.sk.PubKey()}
}
return k.pk
}
@ -153,27 +167,49 @@ func (k *PrivateKeySECP256K1R) Sign(msg []byte) ([]byte, error) {
// SignHash implements the PrivateKey interface
func (k *PrivateKeySECP256K1R) SignHash(hash []byte) ([]byte, error) {
return crypto.Sign(hash, k.sk)
sig := ecdsa.SignCompact(k.sk, hash, false) // returns [v || r || s]
return rawSigToSig(sig)
}
// Bytes implements the PrivateKey interface
func (k *PrivateKeySECP256K1R) Bytes() []byte {
if k.bytes == nil {
k.bytes = make([]byte, SECP256K1RSKLen)
bytes := k.sk.D.Bytes()
copy(k.bytes[SECP256K1RSKLen-len(bytes):], bytes)
k.bytes = k.sk.Serialize()
}
return k.bytes
}
// raw sig has format [v || r || s] whereas the sig has format [r || s || v]
func rawSigToSig(sig []byte) ([]byte, error) {
if len(sig) != SECP256K1RSigLen {
return nil, errInvalidSigLen
}
recCode := sig[0]
copy(sig, sig[1:])
sig[SECP256K1RSigLen-1] = recCode - compactSigMagicOffset
return sig, nil
}
// sig has format [r || s || v] whereas the raw sig has format [v || r || s]
func sigToRawSig(sig []byte) ([]byte, error) {
if len(sig) != SECP256K1RSigLen {
return nil, errInvalidSigLen
}
newSig := make([]byte, SECP256K1RSigLen)
newSig[0] = sig[SECP256K1RSigLen-1] + compactSigMagicOffset
copy(newSig[1:], sig)
return newSig, nil
}
// verifies the signature format in format [r || s || v]
func verifySECP256K1RSignatureFormat(sig []byte) error {
if len(sig) != SECP256K1RSigLen {
return errInvalidSigLen
}
var r, s big.Int
r.SetBytes(sig[:32])
s.SetBytes(sig[32:64])
if !crypto.ValidateSignatureValues(sig[64], &r, &s, true) {
var s secp256k1.ModNScalar
s.SetByteSlice(sig[32:64])
if s.IsOverHalfOrder() {
return errMutatedSig
}
return nil

View File

@ -10,6 +10,8 @@ import (
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/decred/dcrd/dcrec/secp256k1"
"github.com/stretchr/testify/assert"
)
func TestRecover(t *testing.T) {
@ -73,3 +75,24 @@ func TestGenRecreate(t *testing.T) {
}
}
}
func TestVerifyMutatedSignature(t *testing.T) {
factory := FactorySECP256K1R{}
sk, err := factory.NewPrivateKey()
assert.NoError(t, err)
msg := []byte{'h', 'e', 'l', 'l', 'o'}
sig, err := sk.Sign(msg)
assert.NoError(t, err)
var s secp256k1.ModNScalar
s.SetByteSlice(sig[32:64])
s.Negate()
newSBytes := s.Bytes()
copy(sig[32:], newSBytes[:])
_, err = factory.RecoverPublicKey(msg, sig)
assert.Error(t, err)
}

View File

@ -19,9 +19,9 @@ type Factory struct {
}
// New ...
func (f *Factory) New() interface{} {
func (f *Factory) New() (interface{}, error) {
return &VM{
ava: f.AVA,
platform: f.Platform,
}
}, nil
}

View File

@ -198,6 +198,10 @@ func (vm *VM) Initialize(
// Shutdown implements the avalanche.DAGVM interface
func (vm *VM) Shutdown() {
if vm.timer == nil {
return
}
vm.timer.Stop()
if err := vm.baseDB.Close(); err != nil {
vm.ctx.Log.Error("Closing the database failed with %s", err)

View File

@ -83,6 +83,10 @@ func (svm *SnowmanVM) GetBlock(ID ids.ID) (snowman.Block, error) {
// Shutdown this vm
func (svm *SnowmanVM) Shutdown() {
if svm.DB == nil {
return
}
svm.DB.Commit() // Flush DB
svm.DB.GetDatabase().Close() // close underlying database
svm.DB.Close() // close versionDB

View File

@ -1,75 +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/go-ethereum/core/types"
"github.com/ava-labs/go-ethereum/rlp"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowman"
)
// Block implements the snowman.Block interface
type Block struct {
id ids.ID
ethBlock *types.Block
vm *VM
}
// ID implements the snowman.Block interface
func (b *Block) ID() ids.ID { return b.id }
// Accept implements the snowman.Block interface
func (b *Block) Accept() {
b.vm.ctx.Log.Verbo("Block %s is accepted", b.ID())
b.vm.updateStatus(b.ID(), choices.Accepted)
}
// Reject implements the snowman.Block interface
func (b *Block) Reject() {
b.vm.ctx.Log.Verbo("Block %s is rejected", b.ID())
b.vm.updateStatus(b.ID(), choices.Rejected)
}
// Status implements the snowman.Block interface
func (b *Block) Status() choices.Status {
status := b.vm.getCachedStatus(b.ID())
if status == choices.Unknown && b.ethBlock != nil {
return choices.Processing
}
return 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,
}
b.vm.ctx.Log.Verbo("Parent(%s) has status: %s", block.ID(), block.Status())
return block
}
// Verify implements the snowman.Block interface
func (b *Block) Verify() error {
_, err := b.vm.chain.InsertChain([]*types.Block{b.ethBlock})
return err
}
// Bytes implements the snowman.Block interface
func (b *Block) Bytes() []byte {
res, err := rlp.EncodeToBytes(b.ethBlock)
if err != nil {
panic(err)
}
return res
}
func (b *Block) String() string { return fmt.Sprintf("EVM block, ID = %s", b.ID()) }

View File

@ -1,66 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"errors"
"github.com/ava-labs/go-ethereum/ethdb"
"github.com/ava-labs/gecko/database"
)
var (
errOpNotSupported = errors.New("this operation is not supported")
)
// Database implements ethdb.Database
type Database struct{ database.Database }
// HasAncient returns an error as we don't have a backing chain freezer.
func (db Database) HasAncient(kind string, number uint64) (bool, error) {
return false, errOpNotSupported
}
// Ancient returns an error as we don't have a backing chain freezer.
func (db Database) Ancient(kind string, number uint64) ([]byte, error) { return nil, errOpNotSupported }
// Ancients returns an error as we don't have a backing chain freezer.
func (db Database) Ancients() (uint64, error) { return 0, errOpNotSupported }
// AncientSize returns an error as we don't have a backing chain freezer.
func (db Database) AncientSize(kind string) (uint64, error) { return 0, errOpNotSupported }
// AppendAncient returns an error as we don't have a backing chain freezer.
func (db Database) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
return errOpNotSupported
}
// TruncateAncients returns an error as we don't have a backing chain freezer.
func (db Database) TruncateAncients(items uint64) error { return errOpNotSupported }
// Sync returns an error as we don't have a backing chain freezer.
func (db Database) Sync() error { return errOpNotSupported }
// NewBatch implements ethdb.Database
func (db Database) NewBatch() ethdb.Batch { return Batch{db.Database.NewBatch()} }
// NewIterator implements ethdb.Database
func (db Database) NewIterator() ethdb.Iterator { return db.Database.NewIterator() }
// NewIteratorWithPrefix implements ethdb.Database
func (db Database) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator {
return db.NewIteratorWithPrefix(prefix)
}
// NewIteratorWithStart implements ethdb.Database
func (db Database) NewIteratorWithStart(start []byte) ethdb.Iterator {
return db.NewIteratorWithStart(start)
}
// Batch implements ethdb.Batch
type Batch struct{ database.Batch }
// Replay implements ethdb.Batch
func (batch Batch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) }

View File

@ -1,19 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"github.com/ava-labs/gecko/ids"
)
// ID this VM should be referenced by
var (
ID = ids.NewID([32]byte{'e', 'v', 'm'})
)
// Factory ...
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &VM{} }

View File

@ -1,122 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"context"
"crypto/rand"
"fmt"
"math/big"
"github.com/ava-labs/coreth"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/common/hexutil"
"github.com/ava-labs/go-ethereum/core/types"
"github.com/ava-labs/go-ethereum/crypto"
)
const (
version = "Athereum 1.0"
)
// test constants
const (
GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85"
GenesisTestKey = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1"
)
// DebugAPI introduces helper functions for debuging
type DebugAPI struct{ vm *VM }
// SnowmanAPI introduces snowman specific functionality to the evm
type SnowmanAPI struct{ vm *VM }
// NetAPI offers network related API methods
type NetAPI struct{ vm *VM }
// NewNetAPI creates a new net API instance.
func NewNetAPI(vm *VM) *NetAPI { return &NetAPI{vm} }
// Listening returns an indication if the node is listening for network connections.
func (s *NetAPI) Listening() bool { return true } // always listening
// PeerCount returns the number of connected peers
func (s *NetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(0) } // TODO: report number of connected peers
// Version returns the current ethereum protocol version.
func (s *NetAPI) Version() string { return fmt.Sprintf("%d", s.vm.networkID) }
// Web3API offers helper API methods
type Web3API struct{}
// ClientVersion returns the version of the vm running
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) }
// GetAcceptedFrontReply defines the reply that will be sent from the
// GetAcceptedFront API call
type GetAcceptedFrontReply struct {
Hash common.Hash `json:"hash"`
Number *big.Int `json:"number"`
}
// GetAcceptedFront returns the last accepted block's hash and height
func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) {
blk := api.vm.getLastAccepted().ethBlock
return &GetAcceptedFrontReply{
Hash: blk.Hash(),
Number: blk.Number(),
}, nil
}
// GetGenesisBalance returns the current funds in the genesis
func (api *DebugAPI) GetGenesisBalance(ctx context.Context) (*hexutil.Big, error) {
lastAccepted := api.vm.getLastAccepted()
api.vm.ctx.Log.Verbo("Currently accepted block front: %s", lastAccepted.ethBlock.Hash().Hex())
state, err := api.vm.chain.BlockState(lastAccepted.ethBlock)
if err != nil {
return nil, err
}
return (*hexutil.Big)(state.GetBalance(common.HexToAddress(GenesisTestAddr))), nil
}
// SpendGenesis funds
func (api *DebugAPI) SpendGenesis(ctx context.Context, nonce uint64) error {
api.vm.ctx.Log.Info("Spending the genesis")
value := big.NewInt(1000000000000)
gasLimit := 21000
gasPrice := big.NewInt(1000000000)
genPrivateKey, err := crypto.HexToECDSA(GenesisTestKey[2:])
if err != nil {
return err
}
bob, err := coreth.NewKey(rand.Reader)
if err != nil {
return err
}
tx := types.NewTransaction(nonce, bob.Address, value, uint64(gasLimit), gasPrice, nil)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(api.vm.chainID), genPrivateKey)
if err != nil {
return err
}
if err := api.vm.issueRemoteTxs([]*types.Transaction{signedTx}); err != nil {
return err
}
return nil
}
// IssueBlock to the chain
func (api *DebugAPI) IssueBlock(ctx context.Context) error {
api.vm.ctx.Log.Info("Issuing a new block")
return api.vm.tryBlockGen()
}

View File

@ -1,22 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"context"
"encoding/json"
"github.com/ava-labs/coreth/core"
"github.com/ava-labs/gecko/utils/formatting"
)
// StaticService defines the static API services exposed by the evm
type StaticService struct{}
// BuildGenesis returns the UTXOs such that at least one address in [args.Addresses] is
// referenced in the UTXO.
func (*StaticService) BuildGenesis(_ context.Context, args *core.Genesis) (formatting.CB58, error) {
bytes, err := json.Marshal(args)
return formatting.CB58{Bytes: bytes}, err
}

View File

@ -1,64 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"math/big"
"testing"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/params"
"github.com/ava-labs/coreth/core"
)
func TestBuildGenesis(t *testing.T) {
expected := "3wP629bGfSGj9trh1UNBp5qGRGCcma5d8ezLeSmd9hnUJjSMUJesHHoxbZNcVUC9CjH7PEGNA96htNTd1saZCMt1Mf1dZFG7JDhcYNok6RS4TZufejXdxbVVgquohSa7nCCcrXpiVeiRFwzLJAxyQbXzYRhaCRtcDDfCcqfaVdtkFsPbNeQ49pDTbEC5hVkmfopeQ2Zz8tAG5QXKBdbYBCukR3xNHJ4xDxeixmEwPr1odb42yQRYrL7xREKNn2LFoFwAWUjBTsCkf5GPNgY2GvvN9o8wFWXTroW5fp754DhpdxHYxkMTfuE9DGyNWHTyrEbrUHutUdsfitcSHVj5ctFtkN2wGCs3cyv1eRRNvFFMggWTbarjne6AYaeCrJ631qAu3CbrUtrTH5N2E6G2yQKX4sT4Sk3qWPJdsGXuT95iKKcgNn1u5QRHHw9DXXuGPpJjkcKQRGUCuqpXy61iF5RNPEwAwKDa8f2Y25WMmNgWynUuLj8iSAyePj7USPWk54QFUr86ApVzqAdzzdD1qSVScpmudGnGbz9UNXdzHqSot6XLrNTYsgkabiu6TGntFm7qywbCRmtNdBuT9aznGQdUVimjt5QzUz68HXhUxBzTkrz7yXfVGV5JcWxVHQXYS4oc41U5yu83mH3A7WBrZLVq6UyNrvQVbim5nDxeKKbALPxwzVwywjgY5cp39AvzGnY8CX2AtuBNnKmZaAvG8JWAkx3yxjnJrwWhLgpDQYcCvRp2jg1EPBqN8FKJxSPE6eedjDHDJfB57mNzyEtmg22BPnem3eLdiovX8awkhBUHdE7uPrapNSVprnS85u1saW2Kwza3FsS2jAM3LckGW8KdtfPTpHBTRKAUo49zZLuPsyGL5WduedGyAdaM3a2KPoyXuz4UbexTVUWFNypFvvgyoDS8FMxDCNoMMaD7y4yVnoDpSpVFEVZD6EuSGHe9U8Ew57xLPbjhepDx6"
balance, success := new(big.Int).SetString("33b2e3c9fd0804000000000", 16)
if !success {
t.Fatal("Failed to initialize balance")
}
args := core.Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(43110),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
},
Nonce: 0,
Timestamp: 0,
ExtraData: []byte{},
GasLimit: 100000000,
Difficulty: big.NewInt(0),
Mixhash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
Coinbase: common.HexToAddress("0x0000000000000000000000000000000000000000"),
Alloc: core.GenesisAlloc{
common.HexToAddress("751a0b96e1042bee789452ecb20253fba40dbe85"): core.GenesisAccount{
Balance: balance,
},
},
Number: 0,
GasUsed: 0,
ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
}
ss := StaticService{}
result, err := ss.BuildGenesis(nil, &args)
if err != nil {
t.Fatal(err)
}
if result.String() != expected {
t.Fatalf("StaticService.BuildGenesis:\nReturned: %s\nExpected: %s", result, expected)
}
}

View File

@ -1,498 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"math/big"
"sync"
"sync/atomic"
"time"
"github.com/ava-labs/coreth"
"github.com/ava-labs/coreth/core"
"github.com/ava-labs/coreth/eth"
"github.com/ava-labs/coreth/node"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/core/types"
"github.com/ava-labs/go-ethereum/rlp"
"github.com/ava-labs/go-ethereum/rpc"
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"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/timer"
commonEng "github.com/ava-labs/gecko/snow/engine/common"
)
const (
lastAcceptedKey = "snowman_lastAccepted"
)
const (
minBlockTime = 250 * time.Millisecond
maxBlockTime = 1000 * time.Millisecond
batchSize = 250
)
const (
bdTimerStateMin = iota
bdTimerStateMax
bdTimerStateLong
)
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")
)
func maxDuration(x, y time.Duration) time.Duration {
if x > y {
return x
}
return y
}
// VM implements the snowman.ChainVM interface
type VM struct {
ctx *snow.Context
chainID *big.Int
networkID uint64
chain *coreth.ETHChain
chaindb Database
newBlockChan chan *Block
networkChan chan<- commonEng.Message
newTxPoolHeadChan chan core.NewTxPoolHeadEvent
txPoolStabilizedHead common.Hash
txPoolStabilizedOk chan struct{}
txPoolStabilizedLock sync.Mutex
metalock sync.Mutex
blockCache, blockStatusCache cache.LRU
lastAccepted *Block
writingMetadata uint32
bdlock sync.Mutex
blockDelayTimer *timer.Timer
bdTimerState int8
bdGenWaitFlag bool
bdGenFlag bool
genlock sync.Mutex
txSubmitChan <-chan struct{}
}
/*
******************************************************************************
********************************* Snowman API ********************************
******************************************************************************
*/
// Initialize implements the snowman.ChainVM interface
func (vm *VM) Initialize(
ctx *snow.Context,
db database.Database,
b []byte,
toEngine chan<- commonEng.Message,
fxs []*commonEng.Fx,
) error {
if len(fxs) > 0 {
return errUnsupportedFXs
}
vm.ctx = ctx
vm.chaindb = Database{db}
g := new(core.Genesis)
err := json.Unmarshal(b, g)
if err != nil {
return err
}
vm.chainID = g.Config.ChainID
config := eth.DefaultConfig
config.ManualCanonical = true
config.Genesis = g
config.Miner.ManualMining = true
config.Miner.DisableUncle = true
if err := config.SetGCMode("archive"); err != nil {
panic(err)
}
nodecfg := node.Config{NoUSB: true}
chain := coreth.NewETHChain(&config, &nodecfg, nil, vm.chaindb)
vm.chain = chain
vm.networkID = config.NetworkId
chain.SetOnHeaderNew(func(header *types.Header) {
hid := make([]byte, 32)
_, err := rand.Read(hid)
if err != nil {
panic("cannot generate hid")
}
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
}
return nil
})
chain.SetOnSealFinish(func(block *types.Block) error {
vm.ctx.Log.Verbo("EVM sealed a block")
blk := &Block{
id: ids.NewID(block.Hash()),
ethBlock: block,
vm: vm,
}
vm.newBlockChan <- blk
vm.updateStatus(ids.NewID(block.Hash()), choices.Processing)
vm.txPoolStabilizedLock.Lock()
vm.txPoolStabilizedHead = block.Hash()
vm.txPoolStabilizedLock.Unlock()
return nil
})
chain.SetOnQueryAcceptedBlock(func() *types.Block {
return vm.getLastAccepted().ethBlock
})
vm.blockCache = cache.LRU{Size: 2048}
vm.blockStatusCache = cache.LRU{Size: 1024}
vm.newBlockChan = make(chan *Block)
vm.networkChan = toEngine
vm.blockDelayTimer = timer.NewTimer(func() {
vm.bdlock.Lock()
switch vm.bdTimerState {
case bdTimerStateMin:
vm.bdTimerState = bdTimerStateMax
vm.blockDelayTimer.SetTimeoutIn(maxDuration(maxBlockTime-minBlockTime, 0))
case bdTimerStateMax:
vm.bdTimerState = bdTimerStateLong
}
tryAgain := vm.bdGenWaitFlag
vm.bdlock.Unlock()
if tryAgain {
vm.tryBlockGen()
}
})
go ctx.Log.RecoverAndPanic(vm.blockDelayTimer.Dispatch)
vm.bdTimerState = bdTimerStateLong
vm.bdGenWaitFlag = true
vm.newTxPoolHeadChan = make(chan core.NewTxPoolHeadEvent, 1)
vm.txPoolStabilizedOk = make(chan struct{}, 1)
chain.GetTxPool().SubscribeNewHeadEvent(vm.newTxPoolHeadChan)
// TODO: shutdown this go routine
go ctx.Log.RecoverAndPanic(func() {
for {
select {
case h := <-vm.newTxPoolHeadChan:
vm.txPoolStabilizedLock.Lock()
if vm.txPoolStabilizedHead == h.Block.Hash() {
vm.txPoolStabilizedOk <- struct{}{}
vm.txPoolStabilizedHead = common.Hash{}
}
vm.txPoolStabilizedLock.Unlock()
}
}
})
chain.Start()
var lastAccepted *types.Block
if b, err := vm.chaindb.Get([]byte(lastAcceptedKey)); err == nil {
var hash common.Hash
if err = rlp.DecodeBytes(b, &hash); err == nil {
if block := chain.GetBlockByHash(hash); block == nil {
vm.ctx.Log.Debug("lastAccepted block not found in chaindb")
} else {
lastAccepted = block
}
}
}
if lastAccepted == nil {
vm.ctx.Log.Debug("lastAccepted is unavailable, setting to the genesis block")
lastAccepted = chain.GetGenesisBlock()
}
vm.lastAccepted = &Block{
id: ids.NewID(lastAccepted.Hash()),
ethBlock: lastAccepted,
vm: vm,
}
vm.ctx.Log.Info(fmt.Sprintf("lastAccepted = %s", vm.lastAccepted.ethBlock.Hash().Hex()))
// TODO: shutdown this go routine
go vm.ctx.Log.RecoverAndPanic(func() {
vm.txSubmitChan = vm.chain.GetTxSubmitCh()
for {
select {
case <-vm.txSubmitChan:
vm.ctx.Log.Verbo("New tx detected, trying to generate a block")
vm.tryBlockGen()
case <-time.After(5 * time.Second):
vm.tryBlockGen()
}
}
})
return nil
}
// Shutdown implements the snowman.ChainVM interface
func (vm *VM) Shutdown() {
vm.writeBackMetadata()
vm.chain.Stop()
}
// BuildBlock implements the snowman.ChainVM interface
func (vm *VM) BuildBlock() (snowman.Block, error) {
vm.chain.GenBlock()
block := <-vm.newBlockChan
if block == nil {
return nil, errCreateBlock
}
// reset the min block time timer
vm.bdlock.Lock()
vm.bdTimerState = bdTimerStateMin
vm.bdGenWaitFlag = false
vm.bdGenFlag = false
vm.blockDelayTimer.SetTimeoutIn(minBlockTime)
vm.bdlock.Unlock()
vm.ctx.Log.Debug("built block 0x%x", block.ID().Bytes())
// make sure Tx Pool is updated
<-vm.txPoolStabilizedOk
return block, nil
}
// ParseBlock implements the snowman.ChainVM interface
func (vm *VM) ParseBlock(b []byte) (snowman.Block, error) {
vm.metalock.Lock()
defer vm.metalock.Unlock()
ethBlock := new(types.Block)
if err := rlp.DecodeBytes(b, ethBlock); err != nil {
return nil, err
}
block := &Block{
id: ids.NewID(ethBlock.Hash()),
ethBlock: ethBlock,
vm: vm,
}
vm.blockCache.Put(block.ID(), block)
return block, nil
}
// GetBlock implements the snowman.ChainVM interface
func (vm *VM) GetBlock(id ids.ID) (snowman.Block, error) {
vm.metalock.Lock()
defer vm.metalock.Unlock()
block := vm.getBlock(id)
if block == nil {
return nil, errUnknownBlock
}
return block, nil
}
// SetPreference sets what the current tail of the chain is
func (vm *VM) SetPreference(blkID ids.ID) {
err := vm.chain.SetTail(blkID.Key())
vm.ctx.Log.AssertNoError(err)
}
// LastAccepted returns the ID of the block that was last accepted
func (vm *VM) LastAccepted() ids.ID {
vm.metalock.Lock()
defer vm.metalock.Unlock()
return vm.lastAccepted.ID()
}
// CreateHandlers makes new http handlers that can handle API calls
func (vm *VM) CreateHandlers() map[string]*commonEng.HTTPHandler {
handler := vm.chain.NewRPCHandler()
vm.chain.AttachEthService(handler, []string{"eth", "personal", "txpool"})
handler.RegisterName("net", &NetAPI{vm})
handler.RegisterName("snowman", &SnowmanAPI{vm})
handler.RegisterName("web3", &Web3API{})
handler.RegisterName("debug", &DebugAPI{vm})
return map[string]*commonEng.HTTPHandler{
"/rpc": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler},
"/ws": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler.WebsocketHandler([]string{"*"})},
}
}
// CreateStaticHandlers makes new http handlers that can handle API calls
func (vm *VM) CreateStaticHandlers() map[string]*commonEng.HTTPHandler {
handler := rpc.NewServer()
handler.RegisterName("static", &StaticService{})
return map[string]*commonEng.HTTPHandler{
"/rpc": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler},
"/ws": &commonEng.HTTPHandler{LockOptions: commonEng.NoLock, Handler: handler.WebsocketHandler([]string{"*"})},
}
}
/*
******************************************************************************
*********************************** Helpers **********************************
******************************************************************************
*/
func (vm *VM) updateStatus(blockID ids.ID, status choices.Status) {
vm.metalock.Lock()
defer vm.metalock.Unlock()
if status == choices.Accepted {
vm.lastAccepted = vm.getBlock(blockID)
// TODO: improve this naive implementation
if atomic.SwapUint32(&vm.writingMetadata, 1) == 0 {
go vm.ctx.Log.RecoverAndPanic(vm.writeBackMetadata)
}
}
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()
if vm.bdGenFlag {
// skip if one call already generates a block in this round
return nil
}
vm.bdGenWaitFlag = true
vm.genlock.Lock()
defer vm.genlock.Unlock()
// get pending size
size, err := vm.chain.PendingSize()
if err != nil {
return err
}
if size == 0 {
return nil
}
switch vm.bdTimerState {
case bdTimerStateMin:
return nil
case bdTimerStateMax:
if size < batchSize {
return nil
}
case bdTimerStateLong:
// timeout; go ahead and generate a new block anyway
}
select {
case vm.networkChan <- commonEng.PendingTxs:
// successfully push out the notification; this round ends
vm.bdGenFlag = true
default:
return errBlockFrequency
}
return nil
}
func (vm *VM) getCachedStatus(blockID ids.ID) choices.Status {
vm.metalock.Lock()
defer vm.metalock.Unlock()
status := choices.Processing
if statusIntf, ok := vm.blockStatusCache.Get(blockID); ok {
status = statusIntf.(choices.Status)
} else {
blk := vm.chain.GetBlockByHash(blockID.Key())
if blk == nil {
return choices.Unknown
}
acceptedBlk := vm.lastAccepted.ethBlock
// TODO: There must be a better way of doing this.
// Traverse up the chain from the lower block until the indices match
highBlock := blk
lowBlock := acceptedBlk
if highBlock.Number().Cmp(lowBlock.Number()) < 0 {
highBlock, lowBlock = lowBlock, highBlock
}
for highBlock.Number().Cmp(lowBlock.Number()) > 0 {
highBlock = vm.chain.GetBlockByHash(highBlock.ParentHash())
}
if highBlock.Hash() == lowBlock.Hash() { // on the same branch
if blk.Number().Cmp(acceptedBlk.Number()) <= 0 {
status = choices.Accepted
}
} else { // on different branches
status = choices.Rejected
}
}
vm.blockStatusCache.Put(blockID, status)
return status
}
func (vm *VM) getBlock(id ids.ID) *Block {
if blockIntf, ok := vm.blockCache.Get(id); ok {
return blockIntf.(*Block)
}
ethBlock := vm.getCachedBlock(id)
if ethBlock == nil {
return nil
}
block := &Block{
id: ids.NewID(ethBlock.Hash()),
ethBlock: ethBlock,
vm: vm,
}
vm.blockCache.Put(id, block)
return block
}
func (vm *VM) issueRemoteTxs(txs []*types.Transaction) error {
errs := vm.chain.AddRemoteTxs(txs)
for _, err := range errs {
if err != nil {
return err
}
}
return vm.tryBlockGen()
}
func (vm *VM) writeBackMetadata() {
vm.metalock.Lock()
defer vm.metalock.Unlock()
b, err := rlp.EncodeToBytes(vm.lastAccepted.ethBlock.Hash())
if err != nil {
vm.ctx.Log.Error("snowman-eth: error while writing back metadata")
return
}
vm.ctx.Log.Debug("writing back metadata")
vm.chaindb.Put([]byte(lastAcceptedKey), b)
atomic.StoreUint32(&vm.writingMetadata, 0)
}
func (vm *VM) getLastAccepted() *Block {
vm.metalock.Lock()
defer vm.metalock.Unlock()
return vm.lastAccepted
}

View File

@ -1,32 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"encoding/json"
"testing"
"github.com/ava-labs/coreth/core"
)
func TestParseGenesis(t *testing.T) {
genesis := []byte(`{"config":{"chainId":43110,"homesteadBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"751a0b96e1042bee789452ecb20253fba40dbe85":{"balance":"0x33b2e3c9fd0804000000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`)
genesisBlock := new(core.Genesis)
err := json.Unmarshal(genesis, genesisBlock)
if err != nil {
t.Fatal(err)
}
marshalledBytes, err := json.Marshal(genesisBlock)
if err != nil {
t.Fatal(err)
}
secondGenesisBlock := new(core.Genesis)
err = json.Unmarshal(marshalledBytes, secondGenesisBlock)
if err != nil {
t.Fatal(err)
}
}

View File

@ -16,7 +16,7 @@ import (
// A VMFactory creates new instances of a VM
type VMFactory interface {
New() interface{}
New() (interface{}, error)
}
// Manager is a VM manager.
@ -110,10 +110,17 @@ func (m *manager) addStaticAPIEndpoints(vmID ids.ID) {
vmFactory, err := m.GetVMFactory(vmID)
m.log.AssertNoError(err)
m.log.Debug("adding static API for VM with ID %s", vmID)
vm := vmFactory.New()
vm, err := vmFactory.New()
if err != nil {
return
}
staticVM, ok := vm.(common.StaticVM)
if !ok {
staticVM, ok := vm.(common.VM)
if ok {
staticVM.Shutdown()
}
return
}

View File

@ -13,4 +13,4 @@ var (
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &Fx{} }
func (f *Factory) New() (interface{}, error) { return &Fx{}, nil }

View File

@ -6,7 +6,9 @@ import (
func TestFactory(t *testing.T) {
factory := Factory{}
if fx := factory.New(); fx == nil {
if fx, err := factory.New(); err != nil {
t.Fatal(err)
} else if fx == nil {
t.Fatalf("Factory.New returned nil")
}
}

View File

@ -24,12 +24,12 @@ type Factory struct {
}
// New returns a new instance of the Platform Chain
func (f *Factory) New() interface{} {
func (f *Factory) New() (interface{}, error) {
return &VM{
chainManager: f.ChainManager,
validators: f.Validators,
stakingEnabled: f.StakingEnabled,
ava: f.AVA,
avm: f.AVM,
}
}, nil
}

View File

@ -382,6 +382,10 @@ func (vm *VM) createChain(tx *CreateChainTx) {
// Shutdown this blockchain
func (vm *VM) Shutdown() {
if vm.timer == nil {
return
}
vm.timer.Stop()
if err := vm.DB.Close(); err != nil {
vm.Ctx.Log.Error("Closing the database failed with %s", err)

View File

@ -13,4 +13,4 @@ var (
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &Fx{} }
func (f *Factory) New() (interface{}, error) { return &Fx{}, nil }

View File

@ -6,7 +6,9 @@ import (
func TestFactory(t *testing.T) {
factory := Factory{}
if fx := factory.New(); fx == nil {
if fx, err := factory.New(); err != nil {
t.Fatal(err)
} else if fx == nil {
t.Fatalf("Factory.New returned nil")
}
}

54
vms/rpcchainvm/factory.go Normal file
View File

@ -0,0 +1,54 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"errors"
"os/exec"
"github.com/hashicorp/go-plugin"
)
var (
errWrongVM = errors.New("wrong vm type")
)
// Factory ...
type Factory struct {
Path string
}
// New ...
func (f *Factory) New() (interface{}, error) {
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: Handshake,
Plugins: PluginMap,
Cmd: exec.Command("sh", "-c", f.Path),
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolNetRPC,
plugin.ProtocolGRPC,
},
})
rpcClient, err := client.Client()
if err != nil {
client.Kill()
return nil, err
}
raw, err := rpcClient.Dispense("vm")
if err != nil {
client.Kill()
return nil, err
}
vm, ok := raw.(*VMClient)
if !ok {
client.Kill()
return nil, errWrongVM
}
vm.SetProcess(client)
return vm, nil
}

View File

@ -0,0 +1,64 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ghttp
import (
"io/ioutil"
"net/http"
"github.com/ava-labs/gecko/vms/rpcchainvm/ghttp/proto"
)
// Client is an implementation of a messenger channel that talks over RPC.
type Client struct{ client proto.HTTPClient }
// NewClient returns a database instance connected to a remote database instance
func NewClient(client proto.HTTPClient) *Client {
return &Client{client: client}
}
// Handle ...
func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the headers
inboundHeaders := make([]*proto.Header, len(r.Header))[:0]
for key, values := range r.Header {
inboundHeaders = append(inboundHeaders, &proto.Header{
Key: key,
Values: values,
})
}
// get the body
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// execute the call
resp, err := c.client.Handle(r.Context(), &proto.HTTPRequest{
Url: r.RequestURI,
Method: r.Method,
Headers: inboundHeaders,
Body: body,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// return the code
w.WriteHeader(int(resp.Code))
// return the headers
outboundHeaders := w.Header()
for _, header := range resp.Headers {
outboundHeaders[header.Key] = header.Values
}
// return the body
if _, err := w.Write(resp.Body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

View File

@ -0,0 +1,62 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ghttp
import (
"bytes"
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"github.com/ava-labs/gecko/vms/rpcchainvm/ghttp/proto"
)
// Server is a http.Handler that is managed over RPC.
type Server struct{ handler http.Handler }
// NewServer returns a http.Handler instance manage remotely
func NewServer(handler http.Handler) *Server {
return &Server{handler: handler}
}
// Handle ...
func (s Server) Handle(ctx context.Context, req *proto.HTTPRequest) (*proto.HTTPResponse, error) {
// create the request with the current context
r, err := http.NewRequestWithContext(
ctx,
req.Method,
req.Url,
ioutil.NopCloser(bytes.NewReader(req.Body)),
)
if err != nil {
return nil, err
}
// populate the headers
for _, header := range req.Headers {
r.Header[header.Key] = header.Values
}
// send the request to the recorder
recorder := httptest.NewRecorder()
s.handler.ServeHTTP(recorder, r)
// get the returned headers
returnedHeaders := recorder.Header()
headers := make([]*proto.Header, len(returnedHeaders))[:0]
for key, values := range returnedHeaders {
headers = append(headers, &proto.Header{
Key: key,
Values: values,
})
}
// return the response
return &proto.HTTPResponse{
Code: int32(recorder.Code),
Headers: headers,
Body: recorder.Body.Bytes(),
}, nil
}

View File

@ -0,0 +1,96 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ghttp
import (
"bytes"
"log"
"net"
"net/http"
"net/http/httptest"
"testing"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json2"
"github.com/ava-labs/gecko/utils/json"
"github.com/ava-labs/gecko/vms/rpcchainvm/ghttp/proto"
)
const (
bufSize = 1 << 20
)
type service struct{}
type Args struct{}
type Reply struct {
Called bool
}
func (s *service) Call(r *http.Request, args *Args, reply *Reply) error {
reply.Called = true
return nil
}
func TestRPC(t *testing.T) {
rpcServer := rpc.NewServer()
codec := json.NewCodec()
rpcServer.RegisterCodec(codec, "application/json")
rpcServer.RegisterCodec(codec, "application/json;charset=UTF-8")
rpcServer.RegisterService(&service{}, "service")
listener := bufconn.Listen(bufSize)
server := grpc.NewServer()
proto.RegisterHTTPServer(server, NewServer(rpcServer))
go func() {
if err := server.Serve(listener); err != nil {
log.Fatal(err)
}
}()
dialer := grpc.WithContextDialer(
func(context.Context, string) (net.Conn, error) {
return listener.Dial()
})
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "", dialer, grpc.WithInsecure())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
rpcClient := NewClient(proto.NewHTTPClient(conn))
req := &Args{}
buf, err := json2.EncodeClientRequest("service.call", req)
if err != nil {
t.Fatal(err)
}
body := bytes.NewBuffer(buf)
r, err := http.NewRequest("POST", "http://localhost:8080/", body)
if err != nil {
t.Fatal(err)
}
r.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
rpcClient.ServeHTTP(w, r)
res := &Reply{}
if err := json2.DecodeClientResponse(w.Body, res); err != nil {
t.Fatal(err)
}
if !res.Called {
t.Fatalf("Should have called the function")
}
}

View File

@ -0,0 +1,297 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: http.proto
package proto
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Header struct {
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Header) Reset() { *m = Header{} }
func (m *Header) String() string { return proto.CompactTextString(m) }
func (*Header) ProtoMessage() {}
func (*Header) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{0}
}
func (m *Header) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Header.Unmarshal(m, b)
}
func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Header.Marshal(b, m, deterministic)
}
func (m *Header) XXX_Merge(src proto.Message) {
xxx_messageInfo_Header.Merge(m, src)
}
func (m *Header) XXX_Size() int {
return xxx_messageInfo_Header.Size(m)
}
func (m *Header) XXX_DiscardUnknown() {
xxx_messageInfo_Header.DiscardUnknown(m)
}
var xxx_messageInfo_Header proto.InternalMessageInfo
func (m *Header) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *Header) GetValues() []string {
if m != nil {
return m.Values
}
return nil
}
type HTTPRequest struct {
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Headers []*Header `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty"`
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HTTPRequest) Reset() { *m = HTTPRequest{} }
func (m *HTTPRequest) String() string { return proto.CompactTextString(m) }
func (*HTTPRequest) ProtoMessage() {}
func (*HTTPRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{1}
}
func (m *HTTPRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HTTPRequest.Unmarshal(m, b)
}
func (m *HTTPRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HTTPRequest.Marshal(b, m, deterministic)
}
func (m *HTTPRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_HTTPRequest.Merge(m, src)
}
func (m *HTTPRequest) XXX_Size() int {
return xxx_messageInfo_HTTPRequest.Size(m)
}
func (m *HTTPRequest) XXX_DiscardUnknown() {
xxx_messageInfo_HTTPRequest.DiscardUnknown(m)
}
var xxx_messageInfo_HTTPRequest proto.InternalMessageInfo
func (m *HTTPRequest) GetUrl() string {
if m != nil {
return m.Url
}
return ""
}
func (m *HTTPRequest) GetMethod() string {
if m != nil {
return m.Method
}
return ""
}
func (m *HTTPRequest) GetHeaders() []*Header {
if m != nil {
return m.Headers
}
return nil
}
func (m *HTTPRequest) GetBody() []byte {
if m != nil {
return m.Body
}
return nil
}
type HTTPResponse struct {
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Headers []*Header `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty"`
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HTTPResponse) Reset() { *m = HTTPResponse{} }
func (m *HTTPResponse) String() string { return proto.CompactTextString(m) }
func (*HTTPResponse) ProtoMessage() {}
func (*HTTPResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{2}
}
func (m *HTTPResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HTTPResponse.Unmarshal(m, b)
}
func (m *HTTPResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HTTPResponse.Marshal(b, m, deterministic)
}
func (m *HTTPResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_HTTPResponse.Merge(m, src)
}
func (m *HTTPResponse) XXX_Size() int {
return xxx_messageInfo_HTTPResponse.Size(m)
}
func (m *HTTPResponse) XXX_DiscardUnknown() {
xxx_messageInfo_HTTPResponse.DiscardUnknown(m)
}
var xxx_messageInfo_HTTPResponse proto.InternalMessageInfo
func (m *HTTPResponse) GetCode() int32 {
if m != nil {
return m.Code
}
return 0
}
func (m *HTTPResponse) GetHeaders() []*Header {
if m != nil {
return m.Headers
}
return nil
}
func (m *HTTPResponse) GetBody() []byte {
if m != nil {
return m.Body
}
return nil
}
func init() {
proto.RegisterType((*Header)(nil), "proto.Header")
proto.RegisterType((*HTTPRequest)(nil), "proto.HTTPRequest")
proto.RegisterType((*HTTPResponse)(nil), "proto.HTTPResponse")
}
func init() { proto.RegisterFile("http.proto", fileDescriptor_11b04836674e6f94) }
var fileDescriptor_11b04836674e6f94 = []byte{
// 225 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x8f, 0x4f, 0x4b, 0x03, 0x31,
0x14, 0xc4, 0xd9, 0xcd, 0x76, 0xa5, 0xaf, 0x15, 0xe4, 0x09, 0x12, 0x3c, 0x85, 0xbd, 0x98, 0x53,
0xc1, 0xf5, 0xe4, 0x37, 0xd8, 0xa3, 0x84, 0xde, 0x65, 0x6b, 0x1e, 0x2c, 0x18, 0x9b, 0x35, 0x7f,
0x84, 0x7e, 0x7b, 0x49, 0xd2, 0xa5, 0x7a, 0xeb, 0x29, 0x33, 0x93, 0x0c, 0xbf, 0x09, 0xc0, 0x14,
0xc2, 0xbc, 0x9b, 0x9d, 0x0d, 0x16, 0x57, 0xf9, 0xe8, 0x7a, 0x68, 0x07, 0x1a, 0x35, 0x39, 0xbc,
0x03, 0xf6, 0x49, 0x27, 0x5e, 0x89, 0x4a, 0xae, 0x55, 0x92, 0xf8, 0x00, 0xed, 0xcf, 0x68, 0x22,
0x79, 0x5e, 0x0b, 0x26, 0xd7, 0xea, 0xec, 0xba, 0x00, 0x9b, 0x61, 0xbf, 0x7f, 0x53, 0xf4, 0x1d,
0xc9, 0x87, 0x54, 0x8c, 0xce, 0x2c, 0xc5, 0xe8, 0x4c, 0x2a, 0x7e, 0x51, 0x98, 0xac, 0xe6, 0x75,
0x0e, 0xcf, 0x0e, 0x9f, 0xe0, 0x66, 0xca, 0x30, 0xcf, 0x99, 0x60, 0x72, 0xd3, 0xdf, 0x96, 0x31,
0xbb, 0x32, 0x41, 0x2d, 0xb7, 0x88, 0xd0, 0x1c, 0xac, 0x3e, 0xf1, 0x46, 0x54, 0x72, 0xab, 0xb2,
0xee, 0xde, 0x61, 0x5b, 0xa8, 0x7e, 0xb6, 0x47, 0x4f, 0xe9, 0xcd, 0x87, 0xd5, 0x94, 0xb9, 0x2b,
0x95, 0xf5, 0x5f, 0x40, 0x7d, 0x15, 0x80, 0x5d, 0x00, 0xfd, 0x2b, 0x34, 0x09, 0x80, 0xcf, 0xd0,
0x0e, 0xe3, 0x51, 0x1b, 0x42, 0x5c, 0xda, 0x97, 0xdf, 0x3e, 0xde, 0xff, 0xcb, 0xca, 0x96, 0x43,
0x9b, 0xb3, 0x97, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x5a, 0x2a, 0xb5, 0x61, 0x01, 0x00,
0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// HTTPClient is the client API for HTTP service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HTTPClient interface {
Handle(ctx context.Context, in *HTTPRequest, opts ...grpc.CallOption) (*HTTPResponse, error)
}
type hTTPClient struct {
cc grpc.ClientConnInterface
}
func NewHTTPClient(cc grpc.ClientConnInterface) HTTPClient {
return &hTTPClient{cc}
}
func (c *hTTPClient) Handle(ctx context.Context, in *HTTPRequest, opts ...grpc.CallOption) (*HTTPResponse, error) {
out := new(HTTPResponse)
err := c.cc.Invoke(ctx, "/proto.HTTP/Handle", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// HTTPServer is the server API for HTTP service.
type HTTPServer interface {
Handle(context.Context, *HTTPRequest) (*HTTPResponse, error)
}
// UnimplementedHTTPServer can be embedded to have forward compatible implementations.
type UnimplementedHTTPServer struct {
}
func (*UnimplementedHTTPServer) Handle(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Handle not implemented")
}
func RegisterHTTPServer(s *grpc.Server, srv HTTPServer) {
s.RegisterService(&_HTTP_serviceDesc, srv)
}
func _HTTP_Handle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HTTPRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HTTPServer).Handle(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.HTTP/Handle",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HTTPServer).Handle(ctx, req.(*HTTPRequest))
}
return interceptor(ctx, in, info, handler)
}
var _HTTP_serviceDesc = grpc.ServiceDesc{
ServiceName: "proto.HTTP",
HandlerType: (*HTTPServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Handle",
Handler: _HTTP_Handle_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "http.proto",
}

View File

@ -0,0 +1,24 @@
syntax = "proto3";
package proto;
message Header {
string key = 1;
repeated string values = 2;
}
message HTTPRequest {
string url = 1;
string method = 2;
repeated Header headers = 3;
bytes body = 4;
}
message HTTPResponse {
int32 code = 1;
repeated Header headers = 2;
bytes body = 3;
}
service HTTP {
rpc Handle(HTTPRequest) returns (HTTPResponse);
}

View File

@ -0,0 +1,27 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package messenger
import (
"context"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/vms/rpcchainvm/messenger/proto"
)
// Client is an implementation of a messenger channel that talks over RPC.
type Client struct{ client proto.MessengerClient }
// NewClient returns a database instance connected to a remote database instance
func NewClient(client proto.MessengerClient) *Client {
return &Client{client: client}
}
// Notify ...
func (c *Client) Notify(msg common.Message) error {
_, err := c.client.Notify(context.Background(), &proto.NotifyRequest{
Message: uint32(msg),
})
return err
}

View File

@ -0,0 +1,37 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package messenger
import (
"context"
"errors"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/vms/rpcchainvm/messenger/proto"
)
var (
errFullQueue = errors.New("full message queue")
)
// Server is a messenger that is managed over RPC.
type Server struct {
messenger chan<- common.Message
}
// NewServer returns a vm instance connected to a remote vm instance
func NewServer(messenger chan<- common.Message) *Server {
return &Server{messenger: messenger}
}
// Notify ...
func (s *Server) Notify(_ context.Context, req *proto.NotifyRequest) (*proto.NotifyResponse, error) {
msg := common.Message(req.Message)
select {
case s.messenger <- msg:
return &proto.NotifyResponse{}, nil
default:
return nil, errFullQueue
}
}

View File

@ -0,0 +1,194 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: messenger.proto
package proto
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type NotifyRequest struct {
Message uint32 `protobuf:"varint,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *NotifyRequest) Reset() { *m = NotifyRequest{} }
func (m *NotifyRequest) String() string { return proto.CompactTextString(m) }
func (*NotifyRequest) ProtoMessage() {}
func (*NotifyRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b99aba0cbf4e4b91, []int{0}
}
func (m *NotifyRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NotifyRequest.Unmarshal(m, b)
}
func (m *NotifyRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_NotifyRequest.Marshal(b, m, deterministic)
}
func (m *NotifyRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_NotifyRequest.Merge(m, src)
}
func (m *NotifyRequest) XXX_Size() int {
return xxx_messageInfo_NotifyRequest.Size(m)
}
func (m *NotifyRequest) XXX_DiscardUnknown() {
xxx_messageInfo_NotifyRequest.DiscardUnknown(m)
}
var xxx_messageInfo_NotifyRequest proto.InternalMessageInfo
func (m *NotifyRequest) GetMessage() uint32 {
if m != nil {
return m.Message
}
return 0
}
type NotifyResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *NotifyResponse) Reset() { *m = NotifyResponse{} }
func (m *NotifyResponse) String() string { return proto.CompactTextString(m) }
func (*NotifyResponse) ProtoMessage() {}
func (*NotifyResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b99aba0cbf4e4b91, []int{1}
}
func (m *NotifyResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NotifyResponse.Unmarshal(m, b)
}
func (m *NotifyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_NotifyResponse.Marshal(b, m, deterministic)
}
func (m *NotifyResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_NotifyResponse.Merge(m, src)
}
func (m *NotifyResponse) XXX_Size() int {
return xxx_messageInfo_NotifyResponse.Size(m)
}
func (m *NotifyResponse) XXX_DiscardUnknown() {
xxx_messageInfo_NotifyResponse.DiscardUnknown(m)
}
var xxx_messageInfo_NotifyResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*NotifyRequest)(nil), "proto.NotifyRequest")
proto.RegisterType((*NotifyResponse)(nil), "proto.NotifyResponse")
}
func init() { proto.RegisterFile("messenger.proto", fileDescriptor_b99aba0cbf4e4b91) }
var fileDescriptor_b99aba0cbf4e4b91 = []byte{
// 123 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcf, 0x4d, 0x2d, 0x2e,
0x4e, 0xcd, 0x4b, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a,
0x9a, 0x5c, 0xbc, 0x7e, 0xf9, 0x25, 0x99, 0x69, 0x95, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25,
0x42, 0x12, 0x5c, 0xec, 0x20, 0xa5, 0x89, 0xe9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xbc, 0x41,
0x30, 0xae, 0x92, 0x00, 0x17, 0x1f, 0x4c, 0x69, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x91, 0x13,
0x17, 0xa7, 0x2f, 0xcc, 0x58, 0x21, 0x53, 0x2e, 0x36, 0x88, 0xb4, 0x90, 0x08, 0xc4, 0x0a, 0x3d,
0x14, 0x83, 0xa5, 0x44, 0xd1, 0x44, 0x21, 0x66, 0x24, 0xb1, 0x81, 0x45, 0x8d, 0x01, 0x01, 0x00,
0x00, 0xff, 0xff, 0x39, 0x03, 0x19, 0x97, 0xa1, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// MessengerClient is the client API for Messenger service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type MessengerClient interface {
Notify(ctx context.Context, in *NotifyRequest, opts ...grpc.CallOption) (*NotifyResponse, error)
}
type messengerClient struct {
cc grpc.ClientConnInterface
}
func NewMessengerClient(cc grpc.ClientConnInterface) MessengerClient {
return &messengerClient{cc}
}
func (c *messengerClient) Notify(ctx context.Context, in *NotifyRequest, opts ...grpc.CallOption) (*NotifyResponse, error) {
out := new(NotifyResponse)
err := c.cc.Invoke(ctx, "/proto.Messenger/Notify", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MessengerServer is the server API for Messenger service.
type MessengerServer interface {
Notify(context.Context, *NotifyRequest) (*NotifyResponse, error)
}
// UnimplementedMessengerServer can be embedded to have forward compatible implementations.
type UnimplementedMessengerServer struct {
}
func (*UnimplementedMessengerServer) Notify(ctx context.Context, req *NotifyRequest) (*NotifyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented")
}
func RegisterMessengerServer(s *grpc.Server, srv MessengerServer) {
s.RegisterService(&_Messenger_serviceDesc, srv)
}
func _Messenger_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NotifyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessengerServer).Notify(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.Messenger/Notify",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessengerServer).Notify(ctx, req.(*NotifyRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Messenger_serviceDesc = grpc.ServiceDesc{
ServiceName: "proto.Messenger",
HandlerType: (*MessengerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Notify",
Handler: _Messenger_Notify_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "messenger.proto",
}

View File

@ -0,0 +1,12 @@
syntax = "proto3";
package proto;
message NotifyRequest {
uint32 message = 1;
}
message NotifyResponse {}
service Messenger {
rpc Notify(NotifyRequest) returns (NotifyResponse);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
syntax = "proto3";
package proto;
message InitializeRequest {
uint32 dbServer = 1;
bytes genesisBytes = 2;
uint32 engineServer = 3;
}
message InitializeResponse {}
message ShutdownRequest {}
message ShutdownResponse {}
message CreateHandlersRequest {}
message CreateHandlersResponse {
repeated Handler handlers = 1;
}
message Handler {
string prefix = 1;
uint32 lockOptions = 2;
uint32 server = 3;
}
message BuildBlockRequest {}
message BuildBlockResponse {
bytes id = 1;
bytes parentID = 2;
bytes bytes = 3;
// status is always processing
}
message ParseBlockRequest {
bytes bytes = 1;
}
message ParseBlockResponse {
bytes id = 1;
bytes parentID = 2;
uint32 status = 3;
}
message GetBlockRequest {
bytes id = 1;
}
message GetBlockResponse {
bytes parentID = 1;
bytes bytes = 2;
uint32 status = 3;
}
message SetPreferenceRequest {
bytes id = 1;
}
message SetPreferenceResponse {}
message LastAcceptedRequest {}
message LastAcceptedResponse {
bytes id = 1;
}
message BlockVerifyRequest {
bytes id = 1;
}
message BlockVerifyResponse {}
message BlockAcceptRequest {
bytes id = 1;
}
message BlockAcceptResponse {}
message BlockRejectRequest {
bytes id = 1;
}
message BlockRejectResponse {}
service VM {
rpc Initialize(InitializeRequest) returns (InitializeResponse);
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse);
rpc CreateHandlers(CreateHandlersRequest) returns (CreateHandlersResponse);
rpc BuildBlock(BuildBlockRequest) returns (BuildBlockResponse);
rpc ParseBlock(ParseBlockRequest) returns (ParseBlockResponse);
rpc GetBlock(GetBlockRequest) returns (GetBlockResponse);
rpc SetPreference(SetPreferenceRequest) returns (SetPreferenceResponse);
rpc LastAccepted(LastAcceptedRequest) returns (LastAcceptedResponse);
rpc BlockVerify(BlockVerifyRequest) returns (BlockVerifyResponse);
rpc BlockAccept(BlockAcceptRequest) returns (BlockAcceptResponse);
rpc BlockReject(BlockRejectRequest) returns (BlockRejectResponse);
}

50
vms/rpcchainvm/vm.go Normal file
View File

@ -0,0 +1,50 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/hashicorp/go-plugin"
"github.com/ava-labs/gecko/snow/engine/snowman"
"github.com/ava-labs/gecko/vms/rpcchainvm/proto"
)
// Handshake is a common handshake that is shared by plugin and host.
var Handshake = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "VM_PLUGIN",
MagicCookieValue: "dynamic",
}
// PluginMap is the map of plugins we can dispense.
var PluginMap = map[string]plugin.Plugin{
"vm": &Plugin{},
}
// Plugin is the implementation of plugin.Plugin so we can serve/consume this.
// We also implement GRPCPlugin so that this plugin can be served over gRPC.
type Plugin struct {
plugin.NetRPCUnsupportedPlugin
// Concrete implementation, written in Go. This is only used for plugins
// that are written in Go.
vm snowman.ChainVM
}
// New ...
func New(vm snowman.ChainVM) *Plugin { return &Plugin{vm: vm} }
// GRPCServer ...
func (p *Plugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
proto.RegisterVMServer(s, NewServer(p.vm, broker))
return nil
}
// GRPCClient ...
func (p *Plugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
return NewClient(proto.NewVMClient(c), broker), nil
}

336
vms/rpcchainvm/vm_client.go Normal file
View File

@ -0,0 +1,336 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"context"
"errors"
"sync"
"google.golang.org/grpc"
"github.com/hashicorp/go-plugin"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/rpcdb"
"github.com/ava-labs/gecko/ids"
"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/snow/engine/common"
"github.com/ava-labs/gecko/vms/components/missing"
"github.com/ava-labs/gecko/vms/rpcchainvm/ghttp"
"github.com/ava-labs/gecko/vms/rpcchainvm/messenger"
dbproto "github.com/ava-labs/gecko/database/rpcdb/proto"
httpproto "github.com/ava-labs/gecko/vms/rpcchainvm/ghttp/proto"
msgproto "github.com/ava-labs/gecko/vms/rpcchainvm/messenger/proto"
vmproto "github.com/ava-labs/gecko/vms/rpcchainvm/proto"
)
var (
errUnsupportedFXs = errors.New("unsupported feature extensions")
)
// VMClient is an implementation of VM that talks over RPC.
type VMClient struct {
client vmproto.VMClient
broker *plugin.GRPCBroker
proc *plugin.Client
db *rpcdb.DatabaseServer
messenger *messenger.Server
lock sync.Mutex
closed bool
servers []*grpc.Server
conns []*grpc.ClientConn
ctx *snow.Context
blks map[[32]byte]*BlockClient
}
// NewClient returns a database instance connected to a remote database instance
func NewClient(client vmproto.VMClient, broker *plugin.GRPCBroker) *VMClient {
return &VMClient{
client: client,
broker: broker,
}
}
// SetProcess ...
func (vm *VMClient) SetProcess(proc *plugin.Client) {
vm.proc = proc
}
// Initialize ...
func (vm *VMClient) Initialize(
ctx *snow.Context,
db database.Database,
genesisBytes []byte,
toEngine chan<- common.Message,
fxs []*common.Fx,
) error {
if len(fxs) != 0 {
return errUnsupportedFXs
}
vm.ctx = ctx
vm.db = rpcdb.NewServer(db)
vm.messenger = messenger.NewServer(toEngine)
// start the db server
dbBrokerID := vm.broker.NextId()
go vm.broker.AcceptAndServe(dbBrokerID, vm.startDBServer)
// start the messenger server
messengerBrokerID := vm.broker.NextId()
go vm.broker.AcceptAndServe(messengerBrokerID, vm.startMessengerServer)
_, err := vm.client.Initialize(context.Background(), &vmproto.InitializeRequest{
DbServer: dbBrokerID,
GenesisBytes: genesisBytes,
EngineServer: messengerBrokerID,
})
return err
}
func (vm *VMClient) startDBServer(opts []grpc.ServerOption) *grpc.Server {
vm.lock.Lock()
defer vm.lock.Unlock()
server := grpc.NewServer(opts...)
if vm.closed {
server.Stop()
} else {
vm.servers = append(vm.servers, server)
}
dbproto.RegisterDatabaseServer(server, vm.db)
return server
}
func (vm *VMClient) startMessengerServer(opts []grpc.ServerOption) *grpc.Server {
vm.lock.Lock()
defer vm.lock.Unlock()
server := grpc.NewServer(opts...)
if vm.closed {
server.Stop()
} else {
vm.servers = append(vm.servers, server)
}
msgproto.RegisterMessengerServer(server, vm.messenger)
return server
}
// Shutdown ...
func (vm *VMClient) Shutdown() {
vm.lock.Lock()
defer vm.lock.Unlock()
if vm.closed {
return
}
vm.closed = true
vm.client.Shutdown(context.Background(), &vmproto.ShutdownRequest{})
for _, server := range vm.servers {
server.Stop()
}
for _, conn := range vm.conns {
conn.Close()
}
vm.proc.Kill()
}
// CreateHandlers ...
func (vm *VMClient) CreateHandlers() map[string]*common.HTTPHandler {
vm.lock.Lock()
defer vm.lock.Unlock()
if vm.closed {
return nil
}
resp, err := vm.client.CreateHandlers(context.Background(), &vmproto.CreateHandlersRequest{})
vm.ctx.Log.AssertNoError(err)
handlers := make(map[string]*common.HTTPHandler, len(resp.Handlers))
for _, handler := range resp.Handlers {
conn, err := vm.broker.Dial(handler.Server)
vm.ctx.Log.AssertNoError(err)
vm.conns = append(vm.conns, conn)
handlers[handler.Prefix] = &common.HTTPHandler{
LockOptions: common.LockOption(handler.LockOptions),
Handler: ghttp.NewClient(httpproto.NewHTTPClient(conn)),
}
}
return handlers
}
// BuildBlock ...
func (vm *VMClient) BuildBlock() (snowman.Block, error) {
resp, err := vm.client.BuildBlock(context.Background(), &vmproto.BuildBlockRequest{})
if err != nil {
return nil, err
}
id, err := ids.ToID(resp.Id)
vm.ctx.Log.AssertNoError(err)
parentID, err := ids.ToID(resp.ParentID)
vm.ctx.Log.AssertNoError(err)
return &BlockClient{
vm: vm,
id: id,
parentID: parentID,
status: choices.Processing,
bytes: resp.Bytes,
}, nil
}
// ParseBlock ...
func (vm *VMClient) ParseBlock(bytes []byte) (snowman.Block, error) {
resp, err := vm.client.ParseBlock(context.Background(), &vmproto.ParseBlockRequest{
Bytes: bytes,
})
if err != nil {
return nil, err
}
id, err := ids.ToID(resp.Id)
vm.ctx.Log.AssertNoError(err)
if blk, cached := vm.blks[id.Key()]; cached {
return blk, nil
}
parentID, err := ids.ToID(resp.ParentID)
vm.ctx.Log.AssertNoError(err)
status := choices.Status(resp.Status)
vm.ctx.Log.AssertDeferredNoError(status.Valid)
return &BlockClient{
vm: vm,
id: id,
parentID: parentID,
status: status,
bytes: bytes,
}, nil
}
// GetBlock ...
func (vm *VMClient) GetBlock(id ids.ID) (snowman.Block, error) {
if blk, cached := vm.blks[id.Key()]; cached {
return blk, nil
}
resp, err := vm.client.GetBlock(context.Background(), &vmproto.GetBlockRequest{
Id: id.Bytes(),
})
if err != nil {
return nil, err
}
parentID, err := ids.ToID(resp.ParentID)
vm.ctx.Log.AssertNoError(err)
status := choices.Status(resp.Status)
vm.ctx.Log.AssertDeferredNoError(status.Valid)
return &BlockClient{
vm: vm,
id: id,
parentID: parentID,
status: status,
bytes: resp.Bytes,
}, nil
}
// SetPreference ...
func (vm *VMClient) SetPreference(id ids.ID) {
_, err := vm.client.SetPreference(context.Background(), &vmproto.SetPreferenceRequest{
Id: id.Bytes(),
})
vm.ctx.Log.AssertNoError(err)
}
// LastAccepted ...
func (vm *VMClient) LastAccepted() ids.ID {
resp, err := vm.client.LastAccepted(context.Background(), &vmproto.LastAcceptedRequest{})
vm.ctx.Log.AssertNoError(err)
id, err := ids.ToID(resp.Id)
vm.ctx.Log.AssertNoError(err)
return id
}
// BlockClient is an implementation of Block that talks over RPC.
type BlockClient struct {
vm *VMClient
id ids.ID
parentID ids.ID
status choices.Status
bytes []byte
}
// ID ...
func (b *BlockClient) ID() ids.ID { return b.id }
// Accept ...
func (b *BlockClient) Accept() {
delete(b.vm.blks, b.id.Key())
b.status = choices.Accepted
_, err := b.vm.client.BlockAccept(context.Background(), &vmproto.BlockAcceptRequest{
Id: b.id.Bytes(),
})
b.vm.ctx.Log.AssertNoError(err)
}
// Reject ...
func (b *BlockClient) Reject() {
delete(b.vm.blks, b.id.Key())
b.status = choices.Rejected
_, err := b.vm.client.BlockReject(context.Background(), &vmproto.BlockRejectRequest{
Id: b.id.Bytes(),
})
b.vm.ctx.Log.AssertNoError(err)
}
// Status ...
func (b *BlockClient) Status() choices.Status { return b.status }
// Parent ...
func (b *BlockClient) Parent() snowman.Block {
if parent, err := b.vm.GetBlock(b.parentID); err == nil {
return parent
}
return &missing.Block{BlkID: b.parentID}
}
// Verify ...
func (b *BlockClient) Verify() error {
_, err := b.vm.client.BlockVerify(context.Background(), &vmproto.BlockVerifyRequest{
Id: b.id.Bytes(),
})
if err != nil {
return err
}
b.vm.blks[b.id.Key()] = b
return nil
}
// Bytes ...
func (b *BlockClient) Bytes() []byte { return b.bytes }

240
vms/rpcchainvm/vm_server.go Normal file
View File

@ -0,0 +1,240 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"context"
"sync"
"google.golang.org/grpc"
"github.com/hashicorp/go-plugin"
"github.com/ava-labs/gecko/database/rpcdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/snow/engine/snowman"
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/rpcchainvm/ghttp"
"github.com/ava-labs/gecko/vms/rpcchainvm/messenger"
dbproto "github.com/ava-labs/gecko/database/rpcdb/proto"
httpproto "github.com/ava-labs/gecko/vms/rpcchainvm/ghttp/proto"
msgproto "github.com/ava-labs/gecko/vms/rpcchainvm/messenger/proto"
vmproto "github.com/ava-labs/gecko/vms/rpcchainvm/proto"
)
// VMServer is a VM that is managed over RPC.
type VMServer struct {
vm snowman.ChainVM
broker *plugin.GRPCBroker
lock sync.Mutex
closed bool
servers []*grpc.Server
conns []*grpc.ClientConn
toEngine chan common.Message
}
// NewServer returns a vm instance connected to a remote vm instance
func NewServer(vm snowman.ChainVM, broker *plugin.GRPCBroker) *VMServer {
return &VMServer{
vm: vm,
broker: broker,
}
}
// Initialize ...
func (vm *VMServer) Initialize(_ context.Context, req *vmproto.InitializeRequest) (*vmproto.InitializeResponse, error) {
dbConn, err := vm.broker.Dial(req.DbServer)
if err != nil {
return nil, err
}
msgConn, err := vm.broker.Dial(req.EngineServer)
if err != nil {
dbConn.Close()
return nil, err
}
dbClient := rpcdb.NewClient(dbproto.NewDatabaseClient(dbConn))
msgClient := messenger.NewClient(msgproto.NewMessengerClient(msgConn))
toEngine := make(chan common.Message, 1)
go func() {
for msg := range toEngine {
msgClient.Notify(msg)
}
}()
// TODO: Needs to populate a real context
ctx := snow.DefaultContextTest()
if err := vm.vm.Initialize(ctx, dbClient, req.GenesisBytes, toEngine, nil); err != nil {
dbConn.Close()
msgConn.Close()
close(toEngine)
return nil, err
}
vm.conns = append(vm.conns, dbConn)
vm.conns = append(vm.conns, msgConn)
vm.toEngine = toEngine
return &vmproto.InitializeResponse{}, nil
}
// Shutdown ...
func (vm *VMServer) Shutdown(_ context.Context, _ *vmproto.ShutdownRequest) (*vmproto.ShutdownResponse, error) {
vm.lock.Lock()
defer vm.lock.Unlock()
if vm.closed || vm.toEngine == nil {
return &vmproto.ShutdownResponse{}, nil
}
vm.closed = true
vm.vm.Shutdown()
close(vm.toEngine)
errs := wrappers.Errs{}
for _, conn := range vm.conns {
errs.Add(conn.Close())
}
return &vmproto.ShutdownResponse{}, errs.Err
}
// CreateHandlers ...
func (vm *VMServer) CreateHandlers(_ context.Context, req *vmproto.CreateHandlersRequest) (*vmproto.CreateHandlersResponse, error) {
handlers := vm.vm.CreateHandlers()
resp := &vmproto.CreateHandlersResponse{}
for prefix, h := range handlers {
handler := h
// start the messenger server
serverID := vm.broker.NextId()
go vm.broker.AcceptAndServe(serverID, func(opts []grpc.ServerOption) *grpc.Server {
vm.lock.Lock()
defer vm.lock.Unlock()
server := grpc.NewServer(opts...)
if vm.closed {
server.Stop()
} else {
vm.servers = append(vm.servers, server)
}
httpproto.RegisterHTTPServer(server, ghttp.NewServer(handler.Handler))
return server
})
resp.Handlers = append(resp.Handlers, &vmproto.Handler{
Prefix: prefix,
LockOptions: uint32(handler.LockOptions),
Server: serverID,
})
}
return resp, nil
}
// BuildBlock ...
func (vm *VMServer) BuildBlock(_ context.Context, _ *vmproto.BuildBlockRequest) (*vmproto.BuildBlockResponse, error) {
blk, err := vm.vm.BuildBlock()
if err != nil {
return nil, err
}
return &vmproto.BuildBlockResponse{
Id: blk.ID().Bytes(),
ParentID: blk.Parent().ID().Bytes(),
Bytes: blk.Bytes(),
}, nil
}
// ParseBlock ...
func (vm *VMServer) ParseBlock(_ context.Context, req *vmproto.ParseBlockRequest) (*vmproto.ParseBlockResponse, error) {
blk, err := vm.vm.ParseBlock(req.Bytes)
if err != nil {
return nil, err
}
return &vmproto.ParseBlockResponse{
Id: blk.ID().Bytes(),
ParentID: blk.Parent().ID().Bytes(),
Status: uint32(blk.Status()),
}, nil
}
// GetBlock ...
func (vm *VMServer) GetBlock(_ context.Context, req *vmproto.GetBlockRequest) (*vmproto.GetBlockResponse, error) {
id, err := ids.ToID(req.Id)
if err != nil {
return nil, err
}
blk, err := vm.vm.GetBlock(id)
if err != nil {
return nil, err
}
return &vmproto.GetBlockResponse{
ParentID: blk.Parent().ID().Bytes(),
Bytes: blk.Bytes(),
Status: uint32(blk.Status()),
}, nil
}
// SetPreference ...
func (vm *VMServer) SetPreference(_ context.Context, req *vmproto.SetPreferenceRequest) (*vmproto.SetPreferenceResponse, error) {
id, err := ids.ToID(req.Id)
if err != nil {
return nil, err
}
vm.vm.SetPreference(id)
return &vmproto.SetPreferenceResponse{}, nil
}
// LastAccepted ...
func (vm *VMServer) LastAccepted(_ context.Context, _ *vmproto.LastAcceptedRequest) (*vmproto.LastAcceptedResponse, error) {
return &vmproto.LastAcceptedResponse{Id: vm.vm.LastAccepted().Bytes()}, nil
}
// BlockVerify ...
func (vm *VMServer) BlockVerify(_ context.Context, req *vmproto.BlockVerifyRequest) (*vmproto.BlockVerifyResponse, error) {
id, err := ids.ToID(req.Id)
if err != nil {
return nil, err
}
blk, err := vm.vm.GetBlock(id)
if err != nil {
return nil, err
}
return &vmproto.BlockVerifyResponse{}, blk.Verify()
}
// BlockAccept ...
func (vm *VMServer) BlockAccept(_ context.Context, req *vmproto.BlockAcceptRequest) (*vmproto.BlockAcceptResponse, error) {
id, err := ids.ToID(req.Id)
if err != nil {
return nil, err
}
blk, err := vm.vm.GetBlock(id)
if err != nil {
return nil, err
}
blk.Accept()
return &vmproto.BlockAcceptResponse{}, nil
}
// BlockReject ...
func (vm *VMServer) BlockReject(_ context.Context, req *vmproto.BlockRejectRequest) (*vmproto.BlockRejectResponse, error) {
id, err := ids.ToID(req.Id)
if err != nil {
return nil, err
}
blk, err := vm.vm.GetBlock(id)
if err != nil {
return nil, err
}
blk.Reject()
return &vmproto.BlockRejectResponse{}, nil
}

View File

@ -16,4 +16,4 @@ var (
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &Fx{} }
func (f *Factory) New() (interface{}, error) { return &Fx{}, nil }

View File

@ -9,7 +9,9 @@ import (
func TestFactory(t *testing.T) {
factory := Factory{}
if fx := factory.New(); fx == nil {
if fx, err := factory.New(); err != nil {
t.Fatal(err)
} else if fx == nil {
t.Fatalf("Factory.New returned nil")
}
}

View File

@ -16,4 +16,4 @@ var (
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &VM{} }
func (f *Factory) New() (interface{}, error) { return &VM{}, nil }

View File

@ -118,6 +118,10 @@ func (vm *VM) Initialize(
// Shutdown implements the snowman.ChainVM interface
func (vm *VM) Shutdown() {
if vm.timer == nil {
return
}
vm.timer.Stop()
if err := vm.baseDB.Close(); err != nil {
vm.ctx.Log.Error("Closing the database failed with %s", err)

View File

@ -16,6 +16,6 @@ var (
type Factory struct{ TxFee uint64 }
// New ...
func (f *Factory) New() interface{} {
return &VM{TxFee: f.TxFee} // Use the tx fee from the config
func (f *Factory) New() (interface{}, error) {
return &VM{TxFee: f.TxFee}, nil
}

View File

@ -130,6 +130,10 @@ func (vm *VM) Initialize(
// Shutdown implements the avalanche.DAGVM interface
func (vm *VM) Shutdown() {
if vm.timer == nil {
return
}
vm.timer.Stop()
if err := vm.baseDB.Close(); err != nil {
vm.ctx.Log.Error("Closing the database failed with %s", err)

View File

@ -14,4 +14,4 @@ var (
type Factory struct{}
// New ...
func (f *Factory) New() interface{} { return &VM{} }
func (f *Factory) New() (interface{}, error) { return &VM{}, nil }