Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Author a sibling block in case best block's slot is same as current slot #2726

Merged
merged 14 commits into from
Sep 9, 2022
Merged
1 change: 1 addition & 0 deletions dot/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func (nb nodeBuilder) createBABEService(cfg *Config, st *state.Service, ks keyst
cs *core.Service, telemetryMailer telemetry.Client) (babe.ServiceIFace, error) {
return nb.createBABEServiceWithBuilder(cfg, st, ks, cs, telemetryMailer, babe.Builder{})
}

func (nodeBuilder) createBABEServiceWithBuilder(cfg *Config, st *state.Service, ks keystore.Keystore,
cs *core.Service, telemetryMailer telemetry.Client, newBabeService ServiceBuilder) (babe.
ServiceIFace, error) {
Expand Down
4 changes: 3 additions & 1 deletion dot/types/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (

// Authority struct to hold authority data
type Authority struct {
Key crypto.PublicKey
Key crypto.PublicKey
// Weight exists for potential improvements in the protocol and could
// have a use-case in the future. In polkadot all authorities have the weight = 1.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Weight uint64
}

Expand Down
24 changes: 24 additions & 0 deletions lib/babe/babe.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,30 @@ func (b *Service) handleSlot(epoch, slotNum uint64,
return errNilParentHeader
}
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved

atGenesisBlock := b.blockState.GenesisHash().Equal(parentHeader.Hash())
if !atGenesisBlock {
bestBlockSlotNum, err := b.blockState.GetSlotForBlock(parentHeader.Hash())
if err != nil {
return fmt.Errorf("could not get slot for block %s: %w", parentHeader.Hash(), err)
}

if bestBlockSlotNum > slotNum {
return errLaggingSlot
}

if bestBlockSlotNum == slotNum {
// pick parent of best block instead to handle slot
newParentHeader, err := b.blockState.GetHeader(parentHeader.ParentHash)
if err != nil {
return fmt.Errorf("could not get header for hash %s: %w", parentHeader.ParentHash, err)
}
if newParentHeader == nil {
return errNilParentHeader
}
parentHeader = newParentHeader
}
}

// there is a chance that the best block header may change in the course of building the block,
// so let's copy it first.
parent, err := parentHeader.DeepCopy()
Expand Down
171 changes: 171 additions & 0 deletions lib/babe/babe_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/internal/log"
"github.com/ChainSafe/gossamer/lib/babe/mocks"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/crypto/sr25519"
"github.com/ChainSafe/gossamer/lib/genesis"
"github.com/ChainSafe/gossamer/lib/runtime"
Expand Down Expand Up @@ -301,3 +302,173 @@ func TestService_PauseAndResume(t *testing.T) {
err = bs.Stop()
require.NoError(t, err)
}

func TestService_HandleSlotWithLaggingSlot(t *testing.T) {
cfg := &ServiceConfig{
Authority: true,
Lead: true,
}
babeService := createTestService(t, cfg)

err := babeService.Start()
require.NoError(t, err)
defer func() {
_ = babeService.Stop()
}()

// add a block
parentHash := babeService.blockState.GenesisHash()
rt, err := babeService.blockState.GetRuntime(nil)
require.NoError(t, err)

epochData, err := babeService.initiateEpoch(testEpochIndex)
require.NoError(t, err)

ext := runtime.NewTestExtrinsic(t, rt, parentHash, parentHash, 0, "System.remark", []byte{0xab, 0xcd})
block := createTestBlock(t, babeService, emptyHeader, [][]byte{common.MustHexToBytes(ext)},
1, testEpochIndex, epochData)

babeService.blockState.AddBlock(block)
time.Sleep(babeService.constants.slotDuration * 1)

header, err := babeService.blockState.BestBlockHeader()
require.NoError(t, err)

bestBlockSlotNum, err := babeService.blockState.GetSlotForBlock(header.Hash())
require.NoError(t, err)

slotnum := uint64(1)
slot := Slot{
start: time.Now(),
duration: 1 * time.Second,
number: slotnum,
}
testVRFOutputAndProof := &VrfOutputAndProof{}
preRuntimeDigest, err := types.NewBabePrimaryPreDigest(
0, slot.number,
testVRFOutputAndProof.output,
testVRFOutputAndProof.proof,
).ToPreRuntimeDigest()

require.NoError(t, err)

err = babeService.handleSlot(
babeService.epochHandler.epochNumber,
bestBlockSlotNum-1,
babeService.epochHandler.epochData.authorityIndex,
preRuntimeDigest)

require.ErrorIs(t, err, errLaggingSlot)
}

func TestService_HandleSlotWithSameSlot(t *testing.T) {
alice := keyring.Alice().(*sr25519.Keypair)
bob := keyring.Bob().(*sr25519.Keypair)

// Create babe service for alice
cfgAlice := &ServiceConfig{
Authority: true,
Lead: true,
Keypair: alice,
}
cfgAlice.AuthData = []types.Authority{
{
Key: alice.Public().(*sr25519.PublicKey),
Weight: 1,
},
{
Key: bob.Public().(*sr25519.PublicKey),
Weight: 1,
},
}

// Create babe service for bob
cfgBob := &ServiceConfig{
Authority: true,
Lead: true,
Keypair: bob,
}
cfgBob.AuthData = []types.Authority{
{
Key: alice.Public().(*sr25519.PublicKey),
Weight: 1,
},
{
Key: bob.Public().(*sr25519.PublicKey),
Weight: 1,
},
}

babeServiceBob := createTestService(t, cfgBob)

err := babeServiceBob.Start()
require.NoError(t, err)
defer func() {
_ = babeServiceBob.Stop()
}()

time.Sleep(babeServiceBob.constants.slotDuration * 5)

// create a block using bob
parentHash := babeServiceBob.blockState.GenesisHash()
rt, err := babeServiceBob.blockState.GetRuntime(nil)
require.NoError(t, err)

epochData, err := babeServiceBob.initiateEpoch(testEpochIndex)
require.NoError(t, err)

ext := runtime.NewTestExtrinsic(t, rt, parentHash, parentHash, 0, "System.remark", []byte{0xab, 0xcd})
block := createTestBlock(t, babeServiceBob, emptyHeader, [][]byte{common.MustHexToBytes(ext)},
1, testEpochIndex, epochData)

err = babeServiceBob.Stop()
require.NoError(t, err)

babeServiceAlice := createTestService(t, cfgAlice)

err = babeServiceAlice.Start()
require.NoError(t, err)
defer func() {
_ = babeServiceAlice.Stop()
}()
time.Sleep(babeServiceAlice.constants.slotDuration * 1)

// Add block created by Bob to Alice
babeServiceAlice.blockState.AddBlock(block)

time.Sleep(babeServiceAlice.constants.slotDuration * 1)

bestBlockHeader, err := babeServiceAlice.blockState.BestBlockHeader()
require.NoError(t, err)

require.Equal(t, block.Header.Hash().String(), bestBlockHeader.Hash().String())

// If the slot we are claiming is same as slot in best header, test that we don't
// through any error and can claim slot.
bestBlockSlotNum, err := babeServiceAlice.blockState.GetSlotForBlock(bestBlockHeader.Hash())
require.NoError(t, err)

err = babeServiceAlice.Stop()
require.NoError(t, err)

slot := Slot{
start: time.Now(),
duration: 1 * time.Second,
number: bestBlockSlotNum,
}
testVRFOutputAndProof := &VrfOutputAndProof{}
preRuntimeDigest, err := types.NewBabePrimaryPreDigest(
0, slot.number,
testVRFOutputAndProof.output,
testVRFOutputAndProof.proof,
).ToPreRuntimeDigest()
require.NoError(t, err)

err = babeServiceAlice.handleSlot(
babeServiceAlice.epochHandler.epochNumber,
bestBlockSlotNum,
babeServiceAlice.epochHandler.epochData.authorityIndex,
preRuntimeDigest)
require.NoError(t, err)

}
1 change: 1 addition & 0 deletions lib/babe/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
errInvalidSlotTechnique = errors.New("invalid slot claiming technique")
errNoBABEAuthorityKeyProvided = errors.New("cannot create BABE service as authority; no keypair provided")
errLastDigestItemNotSeal = errors.New("last digest item is not seal")
errLaggingSlot = errors.New("cannot claim slot, current slot is smaller than slot of best block")

other Other
invalidCustom InvalidCustom
Expand Down
17 changes: 16 additions & 1 deletion lib/genesis/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/ChainSafe/gossamer/lib/utils"
"github.com/ChainSafe/gossamer/pkg/scale"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -100,8 +101,22 @@ func newGenesisTrieAndHeader(t *testing.T, gen *Genesis) (*trie.Trie, *types.Hea
genTrie, err := NewTrieFromGenesis(gen)
require.NoError(t, err)

babeDigest := types.NewBabeDigest()
err = babeDigest.Set(types.BabePrimaryPreDigest{AuthorityIndex: 0})
require.NoError(t, err)

bdEnc, err := scale.Marshal(babeDigest)
require.NoError(t, err)

digest := types.NewDigest()
err = digest.Add(types.PreRuntimeDigest{
ConsensusEngineID: types.BabeEngineID,
Data: bdEnc,
})
require.NoError(t, err)

genesisHeader, err := types.NewHeader(common.NewHash([]byte{0}),
genTrie.MustHash(), trie.EmptyHash, 0, types.NewDigest())
genTrie.MustHash(), trie.EmptyHash, 0, digest)
require.NoError(t, err)

return genTrie, genesisHeader
Expand Down