Skip to content

Commit

Permalink
Hare consensus view (#1153)
Browse files Browse the repository at this point in the history
* Impl IsActiveForConsensusView, provided by the oracle
* Impl ContextualBlocks getter for tortoise
* Fixed memdb
  • Loading branch information
gavraz committed Jul 24, 2019
1 parent 2885dd6 commit d871880
Show file tree
Hide file tree
Showing 24 changed files with 672 additions and 123 deletions.
4 changes: 4 additions & 0 deletions activation/activationdb_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package activation

import (
"errors"
"fmt"
"github.com/google/uuid"
"github.com/spacemeshos/go-spacemesh/address"
Expand Down Expand Up @@ -40,6 +41,9 @@ func (m *MeshValidatorMock) HandleIncomingLayer(layer *types.Layer) (types.Layer
func (m *MeshValidatorMock) HandleLateBlock(bl *types.Block) {}
func (m *MeshValidatorMock) RegisterLayerCallback(func(id types.LayerID)) {}
func (m *MeshValidatorMock) ContextualValidity(id types.BlockID) bool { return true }
func (m *MeshValidatorMock) GetGoodPatternBlocks(layer types.LayerID) (map[types.BlockID]struct{}, error) {
return nil, errors.New("not implemented")
}

type MockState struct{}

Expand Down
4 changes: 2 additions & 2 deletions cmd/hare/hare.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ func (mip *mockIdProvider) GetIdentity(edId string) (types.NodeId, error) {
type mockStateQuerier struct {
}

func (msq mockStateQuerier) IsIdentityActive(edId string, layer types.LayerID) (*types.NodeId, bool, types.AtxId, error) {
return nil, true, *types.EmptyAtxId, nil
func (msq mockStateQuerier) IsIdentityActiveOnConsensusView(edId string, layer types.LayerID) (bool, error) {
return true, nil
}

func validateBlocks(blocks []types.BlockID) bool {
Expand Down
6 changes: 3 additions & 3 deletions cmd/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ func (app *SpacemeshApp) initServices(nodeID types.NodeId, swarm service.Service
if isFixedOracle { // fixed rolacle, take the provided rolacle
hOracle = rolacle
} else { // regular oracle, build and use it
beacon := eligibility.NewBeacon(trtl)
hOracle = eligibility.New(beacon, atxdb, BLS381.Verify2, vrfSigner, uint16(app.Config.LayersPerEpoch), lg.WithName("hareOracle"))
beacon := eligibility.NewBeacon(trtl, app.Config.HareEligibility.ConfidenceParam)
hOracle = eligibility.New(beacon, msh.ActiveSetForLayerConsensusView, BLS381.Verify2, vrfSigner, uint16(app.Config.LayersPerEpoch), app.Config.HareEligibility, lg.WithName("hareOracle"))
}

// a function to validate we know the blocks
Expand All @@ -385,7 +385,7 @@ func (app *SpacemeshApp) initServices(nodeID types.NodeId, swarm service.Service

return true
}
ha := hare.New(app.Config.HARE, swarm, sgn, nodeID, validationFunc, syncer.IsSynced, msh, hOracle, uint16(app.Config.LayersPerEpoch), idStore, atxdb, clock.Subscribe(), lg.WithName("hare"))
ha := hare.New(app.Config.HARE, swarm, sgn, nodeID, validationFunc, syncer.IsSynced, msh, hOracle, uint16(app.Config.LayersPerEpoch), idStore, hOracle, clock.Subscribe(), lg.WithName("hare"))

blockProducer := miner.NewBlockBuilder(nodeID, sgn, swarm, clock.Subscribe(), app.Config.Hdist, txpool, atxpool, coinToss, msh, ha, blockOracle, processor, atxdb, syncer, lg.WithName("blockBuilder"))
blockListener := sync.NewBlockListener(swarm, syncer, 4, lg.WithName("blockListener"))
Expand Down
12 changes: 12 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ func AddCommands(cmd *cobra.Command) {
cmd.PersistentFlags().IntVar(&config.HARE.ExpectedLeaders, "hare-exp-leaders",
config.HARE.ExpectedLeaders, "The expected number of leaders in the hare protocol")

/**======================== Hare Eligibility Oracle Flags ========================== **/

// N determines the size of the hare committee
cmd.PersistentFlags().Uint64Var(&config.HareEligibility.ConfidenceParam, "eligibility-confidence-param",
config.HareEligibility.ConfidenceParam, "The relative layer (with respect to the current layer) we are confident to have consensus about")
// F determines the max number of adversaries in the Hare committee
cmd.PersistentFlags().IntVar(&config.HareEligibility.GenesisActiveSet, "eligibility-genesis-active-size",
config.HareEligibility.GenesisActiveSet, "The active set size for the genesis flow")
// RoundDuration determines the duration of a round in the Hare protocol
cmd.PersistentFlags().IntVar(&config.HareEligibility.EpochOffset, "eligibility-epoch-offset",
config.HareEligibility.EpochOffset, "The constant layer (within an epoch) for which we traverse its view for the purpose of counting consensus active set")

/**======================== PoST Flags ========================== **/

cmd.PersistentFlags().StringVar(&config.POST.DataDir, "post-datadir",
Expand Down
35 changes: 19 additions & 16 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
apiConfig "github.com/spacemeshos/go-spacemesh/api/config"
"github.com/spacemeshos/go-spacemesh/filesystem"
hareConfig "github.com/spacemeshos/go-spacemesh/hare/config"
eligConfig "github.com/spacemeshos/go-spacemesh/hare/eligibility/config"
"github.com/spacemeshos/go-spacemesh/log"
"github.com/spacemeshos/go-spacemesh/mesh"
"github.com/spacemeshos/go-spacemesh/nipst"
Expand Down Expand Up @@ -38,14 +39,15 @@ var (

// Config defines the top level configuration for a spacemesh node
type Config struct {
BaseConfig `mapstructure:"main"`
P2P p2pConfig.Config `mapstructure:"p2p"`
API apiConfig.Config `mapstructure:"api"`
HARE hareConfig.Config `mapstructure:"hare"`
TIME timeConfig.TimeConfig `mapstructure:"time"`
GAS state.GasConfig `mapstructure:"gas"`
REWARD mesh.Config `mapstructure:"reward"`
POST postConfig.Config `mapstructure:"post"`
BaseConfig `mapstructure:"main"`
P2P p2pConfig.Config `mapstructure:"p2p"`
API apiConfig.Config `mapstructure:"api"`
HARE hareConfig.Config `mapstructure:"hare"`
HareEligibility eligConfig.Config `mapstructure:"hare-eligibility"`
TIME timeConfig.TimeConfig `mapstructure:"time"`
GAS state.GasConfig `mapstructure:"gas"`
REWARD mesh.Config `mapstructure:"reward"`
POST postConfig.Config `mapstructure:"post"`
}

// BaseConfig defines the default configuration options for spacemesh app
Expand Down Expand Up @@ -90,14 +92,15 @@ type BaseConfig struct {
// DefaultConfig returns the default configuration for a spacemesh node
func DefaultConfig() Config {
return Config{
BaseConfig: defaultBaseConfig(),
P2P: p2pConfig.DefaultConfig(),
API: apiConfig.DefaultConfig(),
HARE: hareConfig.DefaultConfig(),
TIME: timeConfig.DefaultConfig(),
GAS: state.DefaultConfig(),
REWARD: mesh.DefaultMeshConfig(),
POST: nipst.DefaultConfig(),
BaseConfig: defaultBaseConfig(),
P2P: p2pConfig.DefaultConfig(),
API: apiConfig.DefaultConfig(),
HARE: hareConfig.DefaultConfig(),
HareEligibility: eligConfig.DefaultConfig(),
TIME: timeConfig.DefaultConfig(),
GAS: state.DefaultConfig(),
REWARD: mesh.DefaultMeshConfig(),
POST: nipst.DefaultConfig(),
}
}

Expand Down
4 changes: 4 additions & 0 deletions eligibility/fixedoracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func New() *FixedRolacle {
return rolacle
}

func (fo *FixedRolacle) IsIdentityActiveOnConsensusView(edId string, layer types.LayerID) (bool, error) {
return true, nil
}

func (fo *FixedRolacle) Export(id uint32, committeeSize int) map[string]struct{} {
fo.mapRW.RLock()
total := len(fo.honest) + len(fo.faulty)
Expand Down
11 changes: 5 additions & 6 deletions hare/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const protoName = "HARE_PROTOCOL"
type Rolacle interface {
Eligible(layer types.LayerID, round int32, committeeSize int, id types.NodeId, sig []byte) (bool, error)
Proof(id types.NodeId, layer types.LayerID, round int32) ([]byte, error)
IsIdentityActiveOnConsensusView(edId string, layer types.LayerID) (bool, error)
}

type NetworkService interface {
Expand Down Expand Up @@ -56,7 +57,7 @@ type State struct {
}

type StateQuerier interface {
IsIdentityActive(edId string, layer types.LayerID) (*types.NodeId, bool, types.AtxId, error)
IsIdentityActiveOnConsensusView(edId string, layer types.LayerID) (bool, error)
}

type Msg struct {
Expand Down Expand Up @@ -87,7 +88,7 @@ func newMsg(hareMsg *Message, querier StateQuerier, layersPerEpoch uint16) (*Msg
}
// query if identity is active
pub := signing.NewPublicKey(pubKey)
_, res, _, err := querier.IsIdentityActive(pub.String(), types.LayerID(hareMsg.InnerMsg.InstanceId))
res, err := querier.IsIdentityActiveOnConsensusView(pub.String(), types.LayerID(hareMsg.InnerMsg.InstanceId))
if err != nil {
log.Error("error while checking if identity is active for %v err=%v", pub.String(), err)
return nil, errors.New("is identity active query failed")
Expand All @@ -114,7 +115,6 @@ type ConsensusProcess struct {
isStarted bool
inbox chan *Msg
terminationReport chan TerminationOutput
stateQuerier StateQuerier
validator messageValidator
preRoundTracker *PreRoundTracker
statusesTracker *StatusTracker
Expand All @@ -137,7 +137,6 @@ func NewConsensusProcess(cfg config.Config, instanceId InstanceId, s *Set, oracl
proc.signing = signing
proc.nid = nid
proc.network = p2p
proc.stateQuerier = stateQuerier
proc.validator = newSyntaxContextValidator(signing, cfg.F+1, proc.statusValidator(), stateQuerier, layersPerEpoch, logger)
proc.preRoundTracker = NewPreRoundTracker(cfg.F+1, cfg.N)
proc.notifyTracker = NewNotifyTracker(cfg.N)
Expand Down Expand Up @@ -198,7 +197,7 @@ func (proc *ConsensusProcess) SetInbox(inbox chan *Msg) {
func (proc *ConsensusProcess) eventLoop() {
proc.With().Info("Consensus Process Started",
log.Int("Hare-N", proc.cfg.N), log.Int("f", proc.cfg.F), log.String("duration", (time.Duration(proc.cfg.RoundDuration)*time.Second).String()),
log.LayerId(uint64(proc.instanceId)), log.Int("exp_leaders", proc.cfg.ExpectedLeaders), log.String("set_values", proc.s.String()))
log.Uint64("layer_id", uint64(proc.instanceId)), log.Int("exp_leaders", proc.cfg.ExpectedLeaders), log.String("set_values", proc.s.String()))

// set pre-round InnerMsg and send
builder, err := proc.initDefaultBuilder(proc.s)
Expand Down Expand Up @@ -611,7 +610,7 @@ func (proc *ConsensusProcess) endOfRound3() {

func (proc *ConsensusProcess) shouldParticipate() bool {
// query if identity is active
_, res, _, err := proc.stateQuerier.IsIdentityActive(proc.signing.PublicKey().String(), types.LayerID(proc.instanceId))
res, err := proc.oracle.IsIdentityActiveOnConsensusView(proc.signing.PublicKey().String(), types.LayerID(proc.instanceId))
if err != nil {
proc.With().Error("Error checking our identity for activeness", log.String("err", err.Error()),
log.Uint64("layer_id", uint64(proc.instanceId)))
Expand Down
19 changes: 10 additions & 9 deletions hare/algorithm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func (mmv *mockMessageValidator) ContextuallyValidateMessage(m *Msg, expectedK i
type mockRolacle struct {
isEligible bool
err error
MockStateQuerier
}

func (mr *mockRolacle) Eligible(layer types.LayerID, round int32, committeeSize int, id types.NodeId, sig []byte) (bool, error) {
Expand Down Expand Up @@ -181,7 +182,7 @@ func TestConsensusProcess_eventLoop(t *testing.T) {
broker.Start()
proc := generateConsensusProcess(t)
proc.network = net
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}
oracle.isEligible = true
proc.oracle = oracle
proc.inbox, _ = broker.Register(proc.Id())
Expand Down Expand Up @@ -289,23 +290,23 @@ func TestConsensusProcess_InitDefaultBuilder(t *testing.T) {

func TestConsensusProcess_isEligible(t *testing.T) {
proc := generateConsensusProcess(t)
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}
proc.oracle = oracle
oracle.isEligible = false
assert.False(t, proc.shouldParticipate())
oracle.isEligible = true
assert.True(t, proc.shouldParticipate())
proc.stateQuerier = MockStateQuerier{false, errors.New("some err")}
oracle.MockStateQuerier = MockStateQuerier{false, errors.New("some err")}
assert.False(t, proc.shouldParticipate())
proc.stateQuerier = MockStateQuerier{false, nil}
oracle.MockStateQuerier = MockStateQuerier{false, nil}
assert.False(t, proc.shouldParticipate())
proc.stateQuerier = MockStateQuerier{true, nil}
oracle.MockStateQuerier = MockStateQuerier{true, nil}
assert.True(t, proc.shouldParticipate())
}

func TestConsensusProcess_sendMessage(t *testing.T) {
net := &mockP2p{}
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}

proc := generateConsensusProcess(t)
proc.oracle = oracle
Expand Down Expand Up @@ -458,7 +459,7 @@ func TestConsensusProcess_beginRound1(t *testing.T) {
proc.onRoundBegin()
network := &mockP2p{}
proc.network = network
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}
proc.oracle = oracle
s := NewSmallEmptySet()
m := BuildPreRoundMsg(generateSigning(t), s)
Expand All @@ -476,7 +477,7 @@ func TestConsensusProcess_beginRound2(t *testing.T) {
proc.advanceToNextRound()
network := &mockP2p{}
proc.network = network
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}
proc.oracle = oracle
oracle.isEligible = true

Expand All @@ -498,7 +499,7 @@ func TestConsensusProcess_beginRound3(t *testing.T) {
proc := generateConsensusProcess(t)
network := &mockP2p{}
proc.network = network
oracle := &mockRolacle{}
oracle := &mockRolacle{MockStateQuerier: MockStateQuerier{true, nil}}
proc.oracle = oracle
mpt := &mockProposalTracker{}
proc.proposalTracker = mpt
Expand Down
4 changes: 2 additions & 2 deletions hare/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func NewMockStateQuerier() MockStateQuerier {
return MockStateQuerier{true, nil}
}

func (msq MockStateQuerier) IsIdentityActive(edId string, layer types.LayerID) (*types.NodeId, bool, types.AtxId, error) {
return nil, msq.res, *types.EmptyAtxId, msq.err
func (msq MockStateQuerier) IsIdentityActiveOnConsensusView(edId string, layer types.LayerID) (bool, error) {
return msq.res, msq.err
}

func createMessage(t *testing.T, instanceId InstanceId) []byte {
Expand Down
6 changes: 4 additions & 2 deletions hare/eligibility/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ type patternProvider interface {
type beacon struct {
// provides a value that is unpredictable and agreed (w.h.p.) by all honest
patternProvider patternProvider
confidenceParam uint64
}

func NewBeacon(patternProvider patternProvider) *beacon {
func NewBeacon(patternProvider patternProvider, confidenceParam uint64) *beacon {
return &beacon{
patternProvider: patternProvider,
confidenceParam: confidenceParam,
}
}

// Value returns the unpredictable and agreed value for the given Layer
func (b *beacon) Value(layer types.LayerID) (uint32, error) {
v, err := b.patternProvider.GetGoodPattern(layer)
v, err := b.patternProvider.GetGoodPattern(safeLayer(layer, types.LayerID(b.confidenceParam)))
if err != nil {
log.Error("Could not get pattern Id: %v", err)
return nilVal, err
Expand Down
21 changes: 15 additions & 6 deletions hare/eligibility/beacon_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
package eligibility

import (
"github.com/spacemeshos/go-spacemesh/config"
"github.com/spacemeshos/go-spacemesh/types"
"github.com/stretchr/testify/assert"
"testing"
)

type mockPatternProvider struct {
val uint32
err error
val uint32
valGenesis uint32
err error
}

func (mpp *mockPatternProvider) GetGoodPattern(layer types.LayerID) (uint32, error) {
if layer == config.Genesis {
return mpp.valGenesis, mpp.err
}
return mpp.val, mpp.err
}

func TestBeacon_Value(t *testing.T) {
b := beacon{}
b.patternProvider = &mockPatternProvider{1, someErr}
_, err := b.Value(5)
b.patternProvider = &mockPatternProvider{1, 5, someErr}
b.confidenceParam = cfg.ConfidenceParam
_, err := b.Value(100)
assert.NotNil(t, err)
b.patternProvider = &mockPatternProvider{3, nil}
val, err := b.Value(5)
b.patternProvider = &mockPatternProvider{3, 5, nil}
val, err := b.Value(100)
assert.Nil(t, err)
assert.Equal(t, uint32(3), val)
val, err = b.Value(1)
assert.Nil(t, err)
assert.Equal(t, uint32(5), val)
}
11 changes: 11 additions & 0 deletions hare/eligibility/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package config

type Config struct {
ConfidenceParam uint64 `mapstructure:"eligibility-confidence-param"` // the confidence interval
GenesisActiveSet int `mapstructure:"eligibility-genesis-active-size"` // the active set size for genesis
EpochOffset int `mapstructure:"eligibility-epoch-offset"` // the offset from the beginning of the epoch
}

func DefaultConfig() Config {
return Config{25, 5, 30}
}
Loading

0 comments on commit d871880

Please sign in to comment.