mirror of https://github.com/poanetwork/gecko.git
152 lines
5.1 KiB
Go
152 lines
5.1 KiB
Go
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||
|
// See the file LICENSE for licensing terms.
|
||
|
|
||
|
package platformvm
|
||
|
|
||
|
import (
|
||
|
"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/consensus/snowman"
|
||
|
"github.com/ava-labs/gecko/vms/components/core"
|
||
|
)
|
||
|
|
||
|
// ProposalTx is an operation that can be proposed
|
||
|
type ProposalTx interface {
|
||
|
initialize(vm *VM) error
|
||
|
// Attempts to verify this transaction with the provided state.
|
||
|
SemanticVerify(database.Database) (onCommitDB *versiondb.Database, onAbortDB *versiondb.Database, onCommitFunc func(), onAbortFunc func(), err error)
|
||
|
InitiallyPrefersCommit() bool
|
||
|
}
|
||
|
|
||
|
// ProposalBlock is a proposal to change the chain's state.
|
||
|
// A proposal may be to:
|
||
|
// 1. Advance the chain's timestamp (*AdvanceTimeTx)
|
||
|
// 2. Remove a staker from the staker set (*RewardStakerTx)
|
||
|
// 3. Add a new staker to the set of pending (future) stakers (*AddStakerTx)
|
||
|
// The proposal will be enacted (change the chain's state) if the proposal block
|
||
|
// is accepted and followed by an accepted Commit block
|
||
|
type ProposalBlock struct {
|
||
|
CommonBlock `serialize:"true"`
|
||
|
|
||
|
Tx ProposalTx `serialize:"true"`
|
||
|
|
||
|
// The database that the chain will have if this block's proposal is committed
|
||
|
onCommitDB *versiondb.Database
|
||
|
// The database that the chain will have if this block's proposal is aborted
|
||
|
onAbortDB *versiondb.Database
|
||
|
// The function to execute if this block's proposal is committed
|
||
|
onCommitFunc func()
|
||
|
// The function to execute if this block's proposal is aborted
|
||
|
onAbortFunc func()
|
||
|
}
|
||
|
|
||
|
// Initialize this block.
|
||
|
// Sets [pb.vm] to [vm] and populates non-serialized fields
|
||
|
// This method should be called when a block is unmarshaled from bytes
|
||
|
func (pb *ProposalBlock) initialize(vm *VM, bytes []byte) error {
|
||
|
pb.vm = vm
|
||
|
pb.Block.Initialize(bytes, vm.SnowmanVM)
|
||
|
return pb.Tx.initialize(vm)
|
||
|
}
|
||
|
|
||
|
// setBaseDatabase sets this block's base database to [db]
|
||
|
func (pb *ProposalBlock) setBaseDatabase(db database.Database) {
|
||
|
if err := pb.onCommitDB.SetDatabase(db); err != nil {
|
||
|
pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
|
||
|
}
|
||
|
if err := pb.onAbortDB.SetDatabase(db); err != nil {
|
||
|
pb.vm.Ctx.Log.Error("problem while setting base database: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// onCommit should only be called after Verify is called.
|
||
|
// onCommit returns:
|
||
|
// 1. A database that contains the state of the chain assuming this proposal
|
||
|
// is enacted. (That is, if this block is accepted and followed by an
|
||
|
// accepted Commit block.)
|
||
|
// 2. A function be be executed when this block's proposal is committed.
|
||
|
// This function should not write to state.
|
||
|
func (pb *ProposalBlock) onCommit() (*versiondb.Database, func()) {
|
||
|
return pb.onCommitDB, pb.onCommitFunc
|
||
|
}
|
||
|
|
||
|
// onAbort should only be called after Verify is called.
|
||
|
// onAbort returns a database that contains the state of the chain assuming this
|
||
|
// block's proposal is rejected. (That is, if this block is accepted and
|
||
|
// followed by an accepted Abort block.)
|
||
|
func (pb *ProposalBlock) onAbort() (*versiondb.Database, func()) {
|
||
|
return pb.onAbortDB, pb.onAbortFunc
|
||
|
}
|
||
|
|
||
|
// Verify this block is valid.
|
||
|
//
|
||
|
// The parent block must either be a Commit or an Abort block.
|
||
|
//
|
||
|
// If this block is valid, this function also sets pas.onCommit and pas.onAbort.
|
||
|
func (pb *ProposalBlock) Verify() error {
|
||
|
// pdb is the database if this block's parent is accepted
|
||
|
var pdb database.Database
|
||
|
parent := pb.parentBlock()
|
||
|
// The parent of a proposal block (ie this block) must be a decision block
|
||
|
if parent, ok := parent.(decision); ok {
|
||
|
pdb = parent.onAccept()
|
||
|
} else {
|
||
|
return errInvalidBlockType
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
pb.onCommitDB, pb.onAbortDB, pb.onCommitFunc, pb.onAbortFunc, err = pb.Tx.SemanticVerify(pdb)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
pb.vm.currentBlocks[pb.ID().Key()] = pb
|
||
|
parent.addChild(pb)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Options returns the possible children of this block in preferential order.
|
||
|
func (pb *ProposalBlock) Options() [2]snowman.Block {
|
||
|
blockID := pb.ID()
|
||
|
|
||
|
commit := pb.vm.newCommitBlock(blockID)
|
||
|
abort := pb.vm.newAbortBlock(blockID)
|
||
|
|
||
|
if err := pb.vm.State.PutBlock(pb.vm.DB, commit); err != nil {
|
||
|
pb.vm.Ctx.Log.Warn(errDBPutBlock.Error())
|
||
|
}
|
||
|
if err := pb.vm.State.PutBlock(pb.vm.DB, abort); err != nil {
|
||
|
pb.vm.Ctx.Log.Warn(errDBPutBlock.Error())
|
||
|
}
|
||
|
pb.vm.DB.Commit()
|
||
|
|
||
|
if pb.Tx.InitiallyPrefersCommit() {
|
||
|
return [2]snowman.Block{commit, abort}
|
||
|
}
|
||
|
return [2]snowman.Block{abort, commit}
|
||
|
}
|
||
|
|
||
|
// newProposalBlock creates a new block that proposes to issue a transaction.
|
||
|
// The parent of this block has ID [parentID]. The parent must be a decision block.
|
||
|
// Returns nil if there's an error while creating this block
|
||
|
func (vm *VM) newProposalBlock(parentID ids.ID, tx ProposalTx) (*ProposalBlock, error) {
|
||
|
pb := &ProposalBlock{
|
||
|
CommonBlock: CommonBlock{
|
||
|
Block: core.NewBlock(parentID),
|
||
|
vm: vm,
|
||
|
},
|
||
|
Tx: tx,
|
||
|
}
|
||
|
|
||
|
// We marshal the block in this way (as a Block) so that we can unmarshal
|
||
|
// it into a Block (rather than a *ProposalBlock)
|
||
|
block := Block(pb)
|
||
|
bytes, err := Codec.Marshal(&block)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
pb.Initialize(bytes, vm.SnowmanVM)
|
||
|
return pb, nil
|
||
|
}
|