mirror of https://github.com/poanetwork/gecko.git
159 lines
5.2 KiB
Go
159 lines
5.2 KiB
Go
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||
|
// See the file LICENSE for licensing terms.
|
||
|
|
||
|
package platformvm
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/ava-labs/gecko/database"
|
||
|
"github.com/ava-labs/gecko/database/versiondb"
|
||
|
)
|
||
|
|
||
|
// advanceTimeTx is a transaction to increase the chain's timestamp.
|
||
|
// When the chain's timestamp is updated (a AdvanceTimeTx is accepted and
|
||
|
// followed by a commit block) the staker set is also updated accordingly.
|
||
|
// It must be that:
|
||
|
// * proposed timestamp > [current chain time]
|
||
|
// * proposed timestamp <= [time for next staker to be removed]
|
||
|
type advanceTimeTx struct {
|
||
|
// Unix time this block proposes increasing the timestamp to
|
||
|
Time uint64 `serialize:"true"`
|
||
|
|
||
|
vm *VM
|
||
|
}
|
||
|
|
||
|
func (tx *advanceTimeTx) initialize(vm *VM) error {
|
||
|
tx.vm = vm
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Timestamp returns the time this block is proposing the chain should be set to
|
||
|
func (tx *advanceTimeTx) Timestamp() time.Time { return time.Unix(int64(tx.Time), 0) }
|
||
|
|
||
|
// SyntacticVerify that this transaction is well formed
|
||
|
func (tx *advanceTimeTx) SyntacticVerify() error {
|
||
|
switch {
|
||
|
case tx == nil:
|
||
|
return errNilTx
|
||
|
case tx.vm.clock.Time().Add(Delta).Before(tx.Timestamp()):
|
||
|
return errTimeTooAdvanced
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SemanticVerify this transaction is valid.
|
||
|
func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
|
||
|
if err := tx.SyntacticVerify(); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
currentTimestamp, err := tx.vm.getTimestamp(db)
|
||
|
if err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
if tx.Time <= uint64(currentTimestamp.Unix()) {
|
||
|
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %s not after current timestamp %s",
|
||
|
tx.Timestamp(),
|
||
|
currentTimestamp)
|
||
|
}
|
||
|
|
||
|
// Only allow timestamp to move forward as far as the next validator's end time
|
||
|
nextValidatorEndTime := tx.vm.nextValidatorChangeTime(db, false)
|
||
|
if tx.Time > uint64(nextValidatorEndTime.Unix()) {
|
||
|
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %v later than next validator end time %s",
|
||
|
tx.Time,
|
||
|
nextValidatorEndTime)
|
||
|
}
|
||
|
|
||
|
// Only allow timestamp to move forward as far as the next pending validator's start time
|
||
|
nextValidatorStartTime := tx.vm.nextValidatorChangeTime(db, true)
|
||
|
if tx.Time > uint64(nextValidatorStartTime.Unix()) {
|
||
|
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %v later than next validator start time %s",
|
||
|
tx.Time,
|
||
|
nextValidatorStartTime)
|
||
|
}
|
||
|
|
||
|
// Calculate what the validator sets will be given new timestamp
|
||
|
// Move validators from pending to current if their start time is <= new timestamp.
|
||
|
// Remove validators from current if their end time <= proposed timestamp
|
||
|
|
||
|
// Specify what the state of the chain will be if this proposal is committed
|
||
|
onCommitDB := versiondb.New(db)
|
||
|
if err := tx.vm.putTimestamp(onCommitDB, tx.Timestamp()); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), DefaultSubnetID)
|
||
|
if err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
if err := tx.vm.putCurrentValidators(onCommitDB, current, DefaultSubnetID); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
if err := tx.vm.putPendingValidators(onCommitDB, pending, DefaultSubnetID); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
// For each subnet, calculate what current and pending validator sets should be
|
||
|
// given new timestamp
|
||
|
subnets, err := tx.vm.getSubnets(db)
|
||
|
if err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
for _, subnet := range subnets {
|
||
|
current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.ID)
|
||
|
if err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.ID); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.ID); err != nil {
|
||
|
return nil, nil, nil, nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If this block is committed, update the validator sets
|
||
|
// onAbortDB or onCommitDB should commit (flush to vm.DB) before this is called
|
||
|
updateValidators := func() {
|
||
|
subnets, err := tx.vm.getSubnets(tx.vm.DB)
|
||
|
if err != nil {
|
||
|
tx.vm.Ctx.Log.Error("failed to get subnets: %s", err)
|
||
|
return
|
||
|
}
|
||
|
for _, subnet := range subnets {
|
||
|
if err := tx.vm.updateValidators(subnet.ID); err != nil {
|
||
|
tx.vm.Ctx.Log.Debug("failed to update validators on the default subnet: %s", err)
|
||
|
}
|
||
|
}
|
||
|
if err := tx.vm.updateValidators(DefaultSubnetID); err != nil {
|
||
|
tx.vm.Ctx.Log.Fatal("failed to update validators on the default subnet: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Specify what the state of the chain will be if this proposal is aborted
|
||
|
onAbortDB := versiondb.New(db) // state doesn't change
|
||
|
|
||
|
return onCommitDB, onAbortDB, updateValidators, nil, nil
|
||
|
}
|
||
|
|
||
|
// InitiallyPrefersCommit returns true if the proposed time isn't after the
|
||
|
// current wall clock time.
|
||
|
func (tx *advanceTimeTx) InitiallyPrefersCommit() bool {
|
||
|
return !tx.Timestamp().After(tx.vm.clock.Time())
|
||
|
}
|
||
|
|
||
|
// newAdvanceTimeTx creates a new tx that, if it is accepted and followed by a
|
||
|
// Commit block, will set the chain's timestamp to [timestamp].
|
||
|
func (vm *VM) newAdvanceTimeTx(timestamp time.Time) (*advanceTimeTx, error) {
|
||
|
tx := &advanceTimeTx{
|
||
|
Time: uint64(timestamp.Unix()),
|
||
|
}
|
||
|
return tx, tx.initialize(vm)
|
||
|
}
|