Added handling for if bootstrapping partially accepted a block pair

This commit is contained in:
StephenButtolph 2020-04-28 15:53:36 -04:00
parent 9580a3bf0f
commit 83c5bf39ae
6 changed files with 213 additions and 9 deletions

View File

@ -45,10 +45,24 @@ func (t *Transitive) Initialize(config Config) {
}
func (t *Transitive) finishBootstrapping() {
tail := t.Config.VM.LastAccepted()
t.Config.VM.SetPreference(tail)
t.Consensus.Initialize(t.Config.Context, t.Params, tail)
t.bootstrapped = true
tailID := t.Config.VM.LastAccepted()
t.Consensus.Initialize(t.Config.Context, t.Params, tailID)
tail, err := t.Config.VM.GetBlock(tailID)
if err != nil {
t.Config.Context.Log.Error("Failed to get last accepted block due to: %s", err)
return
}
switch blk := tail.(type) {
case OracleBlock:
for _, blk := range blk.Options() {
t.deliver(blk)
}
default:
t.Config.VM.SetPreference(tailID)
}
}
// Shutdown implements the Engine interface

View File

@ -56,8 +56,17 @@ func setup(t *testing.T) (validators.Validator, validators.Set, *common.SenderTe
te := &Transitive{}
te.Initialize(config)
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
if !blkID.Equals(gBlk.ID()) {
t.Fatalf("Wrong block requested")
}
return gBlk, nil
}
te.finishBootstrapping()
vm.GetBlockF = nil
vm.LastAcceptedF = nil
sender.CantGetAcceptedFrontier = true
@ -369,8 +378,17 @@ func TestEngineMultipleQuery(t *testing.T) {
te := &Transitive{}
te.Initialize(config)
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
if !blkID.Equals(gBlk.ID()) {
t.Fatalf("Wrong block requested")
}
return gBlk, nil
}
te.finishBootstrapping()
vm.GetBlockF = nil
vm.LastAcceptedF = nil
sender.CantGetAcceptedFrontier = true

View File

@ -57,7 +57,7 @@ func (b *Block) Accept() {
b.SetStatus(choices.Accepted) // Change state of this block
b.VM.State.PutStatus(b.VM.DB, b.ID(), choices.Accepted) // Persist data
b.VM.State.PutLastAccepted(b.VM.DB, b.ID())
b.VM.lastAccepted = b.ID() // Change state of VM
b.VM.LastAcceptedID = b.ID() // Change state of VM
}
// Reject sets this block's status to Rejected and saves the status in state

View File

@ -45,7 +45,7 @@ type SnowmanVM struct {
preferred ids.ID
// ID of the last accepted block
lastAccepted ids.ID
LastAcceptedID ids.ID
// unmarshals bytes to a block
unmarshalBlockFunc func([]byte) (snowman.Block, error)
@ -61,7 +61,7 @@ func (svm *SnowmanVM) SetPreference(ID ids.ID) { svm.preferred = ID }
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 }
func (svm *SnowmanVM) LastAccepted() ids.ID { return svm.LastAcceptedID }
// ParseBlock parses [bytes] to a block
func (svm *SnowmanVM) ParseBlock(bytes []byte) (snowman.Block, error) {
@ -161,10 +161,10 @@ func (svm *SnowmanVM) Initialize(
}
if svm.DBInitialized() {
if svm.lastAccepted, err = svm.State.GetLastAccepted(svm.DB); err != nil {
if svm.LastAcceptedID, err = svm.State.GetLastAccepted(svm.DB); err != nil {
return err
}
svm.preferred = svm.lastAccepted
svm.preferred = svm.LastAcceptedID
}
return nil

View File

@ -43,7 +43,10 @@ type ProposalBlock struct {
}
// Accept implements the snowman.Block interface
func (pb *ProposalBlock) Accept() { pb.SetStatus(choices.Accepted) }
func (pb *ProposalBlock) Accept() {
pb.SetStatus(choices.Accepted)
pb.VM.LastAcceptedID = pb.ID()
}
// Initialize this block.
// Sets [pb.vm] to [vm] and populates non-serialized fields

View File

@ -10,13 +10,22 @@ import (
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/gecko/chains"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/database/prefixdb"
"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/snowball"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/snow/engine/common/queue"
"github.com/ava-labs/gecko/snow/networking/handler"
"github.com/ava-labs/gecko/snow/networking/router"
"github.com/ava-labs/gecko/snow/networking/sender"
"github.com/ava-labs/gecko/snow/networking/timeout"
"github.com/ava-labs/gecko/snow/validators"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
@ -25,6 +34,9 @@ import (
"github.com/ava-labs/gecko/vms/components/core"
"github.com/ava-labs/gecko/vms/secp256k1fx"
"github.com/ava-labs/gecko/vms/timestampvm"
smcon "github.com/ava-labs/gecko/snow/consensus/snowman"
smeng "github.com/ava-labs/gecko/snow/engine/snowman"
)
var (
@ -1427,3 +1439,160 @@ func TestRestartFullyAccepted(t *testing.T) {
t.Fatalf("Should have changed the genesis")
}
}
// test bootstrapping the node
func TestBootstrapPartiallyAccepted(t *testing.T) {
genesisAccounts := GenesisAccounts()
genesisValidators := GenesisCurrentValidators()
genesisChains := make([]*CreateChainTx, 0)
genesisState := Genesis{
Accounts: genesisAccounts,
Validators: genesisValidators,
Chains: genesisChains,
Timestamp: uint64(defaultGenesisTime.Unix()),
}
genesisBytes, err := Codec.Marshal(genesisState)
if err != nil {
t.Fatal(err)
}
db := memdb.New()
vmDB := prefixdb.New([]byte("vm"), db)
bootstrappingDB := prefixdb.New([]byte("bootstrapping"), db)
blocked, err := queue.New(bootstrappingDB)
if err != nil {
t.Fatal(err)
}
vm := &VM{
SnowmanVM: &core.SnowmanVM{},
chainManager: chains.MockManager{},
}
defer vm.Shutdown()
defaultSubnet := validators.NewSet()
vm.validators = validators.NewManager()
vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet)
vm.clock.Set(defaultGenesisTime)
ctx := defaultContext()
msgChan := make(chan common.Message, 1)
ctx.Lock.Lock()
if err := vm.Initialize(ctx, vmDB, genesisBytes, msgChan, nil); err != nil {
t.Fatal(err)
}
genesisID := vm.Preferred()
advanceTimeTx, err := vm.newAdvanceTimeTx(defaultGenesisTime.Add(time.Second))
if err != nil {
t.Fatal(err)
}
advanceTimeBlk, err := vm.newProposalBlock(vm.Preferred(), advanceTimeTx)
if err != nil {
t.Fatal(err)
}
advanceTimeBlkID := advanceTimeBlk.ID()
advanceTimeBlkBytes := advanceTimeBlk.Bytes()
advanceTimePreference := advanceTimeBlk.Options()[0]
vdrs := validators.NewSet()
vdrs.Add(validators.NewValidator(ctx.NodeID, 1))
beacons := vdrs
timeoutManager := timeout.Manager{}
timeoutManager.Initialize(2 * time.Second)
go timeoutManager.Dispatch()
router := &router.ChainRouter{}
router.Initialize(logging.NoLog{}, &timeoutManager)
externalSender := &sender.ExternalSenderTest{T: t}
externalSender.Default(true)
// Passes messages from the consensus engine to the network
sender := sender.Sender{}
sender.Initialize(ctx, externalSender, router, &timeoutManager)
// The engine handles consensus
engine := smeng.Transitive{}
engine.Initialize(smeng.Config{
BootstrapConfig: smeng.BootstrapConfig{
Config: common.Config{
Context: ctx,
Validators: vdrs,
Beacons: beacons,
Alpha: uint64(beacons.Len()/2 + 1),
Sender: &sender,
},
Blocked: blocked,
VM: vm,
},
Params: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 20,
BetaRogue: 20,
ConcurrentRepolls: 1,
},
Consensus: &smcon.Topological{},
})
// Asynchronously passes messages from the network to the consensus engine
handler := &handler.Handler{}
handler.Initialize(&engine, msgChan, 1000)
// Allow incoming messages to be routed to the new chain
router.AddChain(handler)
go ctx.Log.RecoverAndPanic(handler.Dispatch)
reqID := new(uint32)
externalSender.GetAcceptedFrontierF = func(_ ids.ShortSet, _ ids.ID, requestID uint32) {
*reqID = requestID
}
engine.Startup()
externalSender.GetAcceptedFrontierF = nil
externalSender.GetAcceptedF = func(_ ids.ShortSet, _ ids.ID, requestID uint32, _ ids.Set) {
*reqID = requestID
}
frontier := ids.Set{}
frontier.Add(advanceTimeBlkID)
engine.AcceptedFrontier(ctx.NodeID, *reqID, frontier)
externalSender.GetAcceptedF = nil
externalSender.GetF = func(_ ids.ShortID, _ ids.ID, requestID uint32, containerID ids.ID) {
*reqID = requestID
if !containerID.Equals(advanceTimeBlkID) {
t.Fatalf("wrong block requested")
}
}
engine.Accepted(ctx.NodeID, *reqID, frontier)
externalSender.GetF = nil
externalSender.CantPushQuery = false
engine.Put(ctx.NodeID, *reqID, advanceTimeBlkID, advanceTimeBlkBytes)
externalSender.CantPushQuery = true
if pref := vm.Preferred(); !pref.Equals(advanceTimePreference.ID()) {
t.Fatalf("wrong preference reported after bootstrapping to proposal block\nPreferred: %s\nExpected: %s\nGenesis: %s",
pref,
advanceTimePreference.ID(),
genesisID)
}
ctx.Lock.Unlock()
router.Shutdown()
}