mirror of https://github.com/poanetwork/gecko.git
172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package core
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/gorilla/rpc/v2"
|
|
|
|
"github.com/ava-labs/gecko/database"
|
|
"github.com/ava-labs/gecko/database/versiondb"
|
|
"github.com/ava-labs/gecko/ids"
|
|
"github.com/ava-labs/gecko/snow"
|
|
"github.com/ava-labs/gecko/snow/choices"
|
|
"github.com/ava-labs/gecko/snow/consensus/snowman"
|
|
"github.com/ava-labs/gecko/snow/engine/common"
|
|
"github.com/ava-labs/gecko/utils/json"
|
|
"github.com/ava-labs/gecko/vms/components/state"
|
|
)
|
|
|
|
var (
|
|
errUnmarshalBlockUndefined = errors.New("vm's UnmarshalBlock member is undefined")
|
|
errBadData = errors.New("got unexpected value from database")
|
|
)
|
|
|
|
// If the status of this ID is not choices.Accepted,
|
|
// the db has not yet been initialized
|
|
var dbInitializedID = ids.NewID([32]byte{'d', 'b', ' ', 'i', 'n', 'i', 't'})
|
|
|
|
// SnowmanVM provides the core functionality shared by most snowman vms
|
|
type SnowmanVM struct {
|
|
State SnowmanState
|
|
|
|
// VersionDB on top of underlying database
|
|
// Important note: In order for writes to [DB] to be persisted,
|
|
// DB.Commit() must be called
|
|
// We use a versionDB here so user can do atomic commits as they see fit
|
|
DB *versiondb.Database
|
|
|
|
// The context of this vm
|
|
Ctx *snow.Context
|
|
|
|
// ID of the preferred block
|
|
preferred ids.ID
|
|
|
|
// ID of the last accepted block
|
|
lastAccepted ids.ID
|
|
|
|
// unmarshals bytes to a block
|
|
unmarshalBlockFunc func([]byte) (snowman.Block, error)
|
|
|
|
// channel to send messages to the consensus engine
|
|
ToEngine chan<- common.Message
|
|
}
|
|
|
|
// SetPreference sets the block with ID [ID] as the preferred block
|
|
func (svm *SnowmanVM) SetPreference(ID ids.ID) { svm.preferred = ID }
|
|
|
|
// Preferred returns the ID of the preferred block
|
|
func (svm *SnowmanVM) Preferred() ids.ID { return svm.preferred }
|
|
|
|
// LastAccepted returns the block most recently accepted
|
|
func (svm *SnowmanVM) LastAccepted() ids.ID { return svm.lastAccepted }
|
|
|
|
// ParseBlock parses [bytes] to a block
|
|
func (svm *SnowmanVM) ParseBlock(bytes []byte) (snowman.Block, error) {
|
|
return svm.unmarshalBlockFunc(bytes)
|
|
}
|
|
|
|
// GetBlock returns the block with ID [ID]
|
|
func (svm *SnowmanVM) GetBlock(ID ids.ID) (snowman.Block, error) {
|
|
block, err := svm.State.Get(svm.DB, state.BlockTypeID, ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if block, ok := block.(snowman.Block); ok {
|
|
return block, nil
|
|
}
|
|
return nil, errBadData // Should never happen
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// DBInitialized returns true iff [svm]'s database has values in it already
|
|
func (svm *SnowmanVM) DBInitialized() bool {
|
|
status := svm.State.GetStatus(svm.DB, dbInitializedID)
|
|
if status == choices.Accepted {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SetDBInitialized marks the database as initialized
|
|
func (svm *SnowmanVM) SetDBInitialized() {
|
|
svm.State.PutStatus(svm.DB, dbInitializedID, choices.Accepted)
|
|
}
|
|
|
|
// SaveBlock saves [block] to state
|
|
func (svm *SnowmanVM) SaveBlock(db database.Database, block snowman.Block) error {
|
|
return svm.State.Put(db, state.BlockTypeID, block.ID(), block)
|
|
}
|
|
|
|
// NotifyBlockReady tells the consensus engine that a new block
|
|
// is ready to be created
|
|
func (svm *SnowmanVM) NotifyBlockReady() {
|
|
select {
|
|
case svm.ToEngine <- common.PendingTxs:
|
|
default:
|
|
svm.Ctx.Log.Warn("dropping message to consensus engine")
|
|
}
|
|
}
|
|
|
|
// NewHandler returns a new Handler for a service where:
|
|
// * The handler's functionality is defined by [service]
|
|
// [service] should be a gorilla RPC service (see https://www.gorillatoolkit.org/pkg/rpc/v2)
|
|
// * The name of the service is [name]
|
|
// * The LockOption is the first element of [lockOption]
|
|
// By default the LockOption is WriteLock
|
|
// [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored.
|
|
func (svm *SnowmanVM) NewHandler(name string, service interface{}, lockOption ...common.LockOption) *common.HTTPHandler {
|
|
server := rpc.NewServer()
|
|
server.RegisterCodec(json.NewCodec(), "application/json")
|
|
server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8")
|
|
server.RegisterService(service, name)
|
|
|
|
var lock common.LockOption = common.WriteLock
|
|
if len(lockOption) != 0 {
|
|
lock = lockOption[0]
|
|
}
|
|
return &common.HTTPHandler{LockOptions: lock, Handler: server}
|
|
}
|
|
|
|
// Initialize this vm.
|
|
// If there is data in [db], sets [svm.lastAccepted] using data in the database,
|
|
// and sets [svm.preferred] to the last accepted block.
|
|
func (svm *SnowmanVM) Initialize(
|
|
ctx *snow.Context,
|
|
db database.Database,
|
|
unmarshalBlockFunc func([]byte) (snowman.Block, error),
|
|
toEngine chan<- common.Message,
|
|
) error {
|
|
svm.Ctx = ctx
|
|
svm.ToEngine = toEngine
|
|
svm.DB = versiondb.New(db)
|
|
|
|
var err error
|
|
svm.State, err = NewSnowmanState(unmarshalBlockFunc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if svm.DBInitialized() {
|
|
if svm.lastAccepted, err = svm.State.GetLastAccepted(svm.DB); err != nil {
|
|
return err
|
|
}
|
|
svm.preferred = svm.lastAccepted
|
|
}
|
|
|
|
return nil
|
|
}
|