From 3631e6a43edc378fba003cec6ed160ae6d505a1d Mon Sep 17 00:00:00 2001 From: kimmy lin <30611210+countvonzero@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:29:05 -0700 Subject: [PATCH] fallback on first applied block for new miners --- hare/eligibility/oracle.go | 2 +- miner/util.go | 18 ++++++- miner/util_test.go | 103 +++++++++++++++++++++++++------------ sql/layers/layers.go | 24 +++++++++ sql/layers/layers_test.go | 50 ++++++++++++++++++ 5 files changed, 161 insertions(+), 36 deletions(-) diff --git a/hare/eligibility/oracle.go b/hare/eligibility/oracle.go index 88ea9a0ba7..f1ca48918c 100644 --- a/hare/eligibility/oracle.go +++ b/hare/eligibility/oracle.go @@ -421,7 +421,7 @@ func (o *Oracle) computeActiveSet(ctx context.Context, targetEpoch types.EpochID return activeSet, nil } - activeSet, err := miner.ActiveSetFromEpochFirstBlock(o.cdb, targetEpoch) + activeSet, err := miner.ActiveSetFromEpochFirstCertifiedBlock(o.cdb, targetEpoch) if err != nil && !errors.Is(err, sql.ErrNotFound) { return nil, err } diff --git a/miner/util.go b/miner/util.go index 59df91ffd1..1bad561859 100644 --- a/miner/util.go +++ b/miner/util.go @@ -10,14 +10,30 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/sql/blocks" "github.com/spacemeshos/go-spacemesh/sql/certificates" + "github.com/spacemeshos/go-spacemesh/sql/layers" ) -func ActiveSetFromEpochFirstBlock(db sql.Executor, epoch types.EpochID) ([]types.ATXID, error) { +func ActiveSetFromEpochFirstCertifiedBlock(db sql.Executor, epoch types.EpochID) ([]types.ATXID, error) { bid, err := certificates.FirstInEpoch(db, epoch) if err != nil { return nil, err } + return activeSetFromBlock(db, bid) +} + +func ActiveSetFromEpochFirstBlock(db sql.Executor, epoch types.EpochID) ([]types.ATXID, error) { + result, err := ActiveSetFromEpochFirstCertifiedBlock(db, epoch) + if err == nil { + return result, nil + } + bid, err := layers.FirstAppliedInEpoch(db, epoch) + if err != nil { + return nil, err + } + return activeSetFromBlock(db, bid) +} +func activeSetFromBlock(db sql.Executor, bid types.BlockID) ([]types.ATXID, error) { block, err := blocks.Get(db, bid) if err != nil { return nil, fmt.Errorf("actives get block: %w", err) diff --git a/miner/util_test.go b/miner/util_test.go index 53a0a4ef27..d93ca38ee6 100644 --- a/miner/util_test.go +++ b/miner/util_test.go @@ -13,44 +13,79 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/blocks" "github.com/spacemeshos/go-spacemesh/sql/certificates" + "github.com/spacemeshos/go-spacemesh/sql/layers" ) func TestActiveSetFromEpochFirstBlock(t *testing.T) { - epoch := types.EpochID(3) - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) + for _, tc := range []struct { + desc string + certified, applied bool + err error + }{ + { + desc: "with certified", + certified: true, + }, + { + desc: "w/o certified", + applied: true, + }, + { + desc: "nothing", + err: sql.ErrNotFound, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + epoch := types.EpochID(3) + cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) - got, err := ActiveSetFromEpochFirstBlock(cdb, epoch) - require.ErrorIs(t, err, sql.ErrNotFound) - require.Nil(t, got) + got, err := ActiveSetFromEpochFirstCertifiedBlock(cdb, epoch) + require.ErrorIs(t, err, sql.ErrNotFound) + require.Nil(t, got) - var expected []types.ATXID - for i := uint32(0); i < layersPerEpoch; i++ { - lid := epoch.FirstLayer() + types.LayerID(i) - all := types.RandomActiveSet(10) - blts := createBallots(t, cdb, lid, 5, all) - block := &types.Block{ - InnerBlock: types.InnerBlock{ - LayerIndex: lid, - }, - } - for _, b := range blts { - block.Rewards = append(block.Rewards, types.AnyReward{AtxID: b.AtxID}) - all = append(all, b.AtxID) - } - block.Initialize() - require.NoError(t, blocks.Add(cdb, block)) - require.NoError(t, certificates.Add(cdb, lid, &types.Certificate{BlockID: block.ID()})) - if i == 0 { - expected = all - } - for _, id := range all { - signer, err := signing.NewEdSigner() - require.NoError(t, err) - genMinerATX(t, cdb, id, (epoch - 1).FirstLayer(), signer, time.Now()) - } - } + var expected []types.ATXID + for i := uint32(0); i < layersPerEpoch; i++ { + lid := epoch.FirstLayer() + types.LayerID(i) + all := types.RandomActiveSet(10) + blts := createBallots(t, cdb, lid, 5, all) + block := &types.Block{ + InnerBlock: types.InnerBlock{ + LayerIndex: lid, + }, + } + for _, b := range blts { + block.Rewards = append(block.Rewards, types.AnyReward{AtxID: b.AtxID}) + all = append(all, b.AtxID) + } + block.Initialize() + require.NoError(t, blocks.Add(cdb, block)) + if tc.certified { + require.NoError(t, certificates.Add(cdb, lid, &types.Certificate{BlockID: block.ID()})) + } else if tc.applied { + require.NoError(t, layers.SetApplied(cdb, lid, block.ID())) + } + if i == 0 { + expected = all + } + for _, id := range all { + signer, err := signing.NewEdSigner() + require.NoError(t, err) + genMinerATX(t, cdb, id, (epoch - 1).FirstLayer(), signer, time.Now()) + } + } - got, err = ActiveSetFromEpochFirstBlock(cdb, epoch) - require.NoError(t, err) - require.ElementsMatch(t, expected, got) + got, err = ActiveSetFromEpochFirstBlock(cdb, epoch) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + } else { + require.NoError(t, err) + require.ElementsMatch(t, expected, got) + } + if tc.certified { + got, err = ActiveSetFromEpochFirstCertifiedBlock(cdb, epoch) + require.NoError(t, err) + require.ElementsMatch(t, expected, got) + } + }) + } } diff --git a/sql/layers/layers.go b/sql/layers/layers.go index 13093aca4b..e9d854898c 100644 --- a/sql/layers/layers.go +++ b/sql/layers/layers.go @@ -140,6 +140,30 @@ func GetApplied(db sql.Executor, lid types.LayerID) (rst types.BlockID, err erro return rst, err } +func FirstAppliedInEpoch(db sql.Executor, epoch types.EpochID) (types.BlockID, error) { + var ( + result types.BlockID + err error + rows int + ) + if rows, err = db.Exec(` + select applied_block from layers + where id between ?1 and ?2 and applied_block is not null and applied_block != ?3 + order by id asc limit 1;`, func(stmt *sql.Statement) { + stmt.BindInt64(1, int64(epoch.FirstLayer())) + stmt.BindInt64(2, int64((epoch+1).FirstLayer()-1)) + stmt.BindBytes(3, types.EmptyBlockID[:]) + }, func(stmt *sql.Statement) bool { + stmt.ColumnBytes(0, result[:]) + return true + }); err != nil { + return types.EmptyBlockID, fmt.Errorf("FirstAppliedInEpoch %s: %w", epoch, err) + } else if rows == 0 { + return types.EmptyBlockID, fmt.Errorf("FirstAppliedInEpoch %s: %w", epoch, sql.ErrNotFound) + } + return result, nil +} + // GetLastApplied for the applied block for layer. func GetLastApplied(db sql.Executor) (types.LayerID, error) { var lid types.LayerID diff --git a/sql/layers/layers_test.go b/sql/layers/layers_test.go index 9bd3abbc7b..a5cf6d58e1 100644 --- a/sql/layers/layers_test.go +++ b/sql/layers/layers_test.go @@ -1,6 +1,7 @@ package layers import ( + "os" "testing" "github.com/stretchr/testify/require" @@ -9,6 +10,15 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" ) +const layersPerEpoch = 4 + +func TestMain(m *testing.M) { + types.SetLayersPerEpoch(layersPerEpoch) + + res := m.Run() + os.Exit(res) +} + func TestWeakCoin(t *testing.T) { db := sql.InMemory() lid := types.LayerID(10) @@ -56,6 +66,46 @@ func TestAppliedBlock(t *testing.T) { require.ErrorIs(t, err, sql.ErrNotFound) } +func TestFirstAppliedInEpoch(t *testing.T) { + db := sql.InMemory() + blks := map[types.LayerID]types.BlockID{ + types.EpochID(1).FirstLayer(): {1}, + types.EpochID(2).FirstLayer(): types.EmptyBlockID, + types.EpochID(2).FirstLayer() + 1: {2}, + types.EpochID(3).FirstLayer() + 1: {3}, + } + for _, epoch := range []types.EpochID{0, 1, 2, 3} { + // cause layer to be inserted + for j := 0; j < layersPerEpoch; j++ { + lid := epoch.FirstLayer() + types.LayerID(j) + require.NoError(t, SetWeakCoin(db, lid, false)) + } + } + for lid, bid := range blks { + require.NoError(t, SetApplied(db, lid, bid)) + } + + got, err := FirstAppliedInEpoch(db, types.EpochID(0)) + require.ErrorIs(t, err, sql.ErrNotFound) + require.Equal(t, types.EmptyBlockID, got) + + got, err = FirstAppliedInEpoch(db, types.EpochID(1)) + require.NoError(t, err) + require.Equal(t, types.BlockID{1}, got) + + got, err = FirstAppliedInEpoch(db, types.EpochID(2)) + require.NoError(t, err) + require.Equal(t, types.BlockID{2}, got) + + got, err = FirstAppliedInEpoch(db, types.EpochID(3)) + require.NoError(t, err) + require.Equal(t, types.BlockID{3}, got) + + got, err = FirstAppliedInEpoch(db, types.EpochID(4)) + require.ErrorIs(t, err, sql.ErrNotFound) + require.Equal(t, types.EmptyBlockID, got) +} + func TestUnsetAppliedFrom(t *testing.T) { db := sql.InMemory() lid := types.LayerID(10)