Skip to content
Snippets Groups Projects
Unverified Commit db0d8ac1 authored by Angelo Capossele's avatar Angelo Capossele Committed by GitHub
Browse files

Add multiple dRNG committee (#779)

* :sparkles: Add support for multiple drng committees

* :bug: Fix compilation issues

* :white_check_mark: Fix dRNG test

* :white_check_mark: Fix integration tests

* :lipstick: Update dRNG dashboard section

* :wrench: Add default Pollen dRNG committee members

* :wrench: Add default X-Team dRNG committee members

* :white_check_mark: Fix integration test

* :beers: Fix error shadow declaration

* :beers: Address review comments

* :wrench: Make Pollen and XTeam dRNG instanceID constant
parent 1450d444
Branches
No related tags found
No related merge requests found
Showing
with 308 additions and 89 deletions
......@@ -38,10 +38,38 @@
"inMemory": false
},
"drng": {
"instanceId": 1,
"threshold": 3,
"distributedPubKey": "",
"committeeMembers": []
"pollen": {
"instanceId": 1,
"threshold": 3,
"distributedPubKey": "",
"committeeMembers": [
"AheLpbhRs1XZsRF8t8VBwuyQh9mqPHXQvthV5rsHytDG",
"FZ28bSTidszUBn8TTCAT9X1nVMwFNnoYBmZ1xfafez2z",
"GT3UxryW4rA9RN9ojnMGmZgE2wP7psagQxgVdA4B9L1P",
"4pB5boPvvk2o5MbMySDhqsmC2CtUdXyotPPEpb7YQPD7",
"64wCsTZpmKjRVHtBKXiFojw7uw3GszumfvC4kHdWsHga"
]
},
"xteam": {
"instanceId": 1339,
"threshold": 4,
"distributedPubKey": "",
"committeeMembers": [
"GUdTwLDb6t6vZ7X5XzEnjFNDEVPteU7tVQ9nzKLfPjdo",
"68vNzBFE9HpmWLb2x4599AUUQNuimuhwn3XahTZZYUHt",
"Dc9n3JxYecaX3gpxVnWb4jS3KVz1K1SgSK1KpV1dzqT1",
"75g6r4tqGZhrgpDYZyZxVje1Qo54ezFYkCw94ELTLhPs",
"CN1XLXLHT9hv7fy3qNhpgNMD6uoHFkHtaNNKyNVCKybf",
"7SmttyqrKMkLo5NPYaiFoHs8LE6s7oCoWCQaZhui8m16",
"CypSmrHpTe3WQmCw54KP91F5gTmrQEL7EmTX38YStFXx"
]
},
"custom": {
"instanceId": 9999,
"threshold": 3,
"distributedPubKey": "",
"committeeMembers": []
}
},
"fpc": {
"bindAddress": "0.0.0.0:10895"
......
......@@ -68,7 +68,7 @@ func VerifyCollectiveBeacon(state *State, cb *CollectiveBeaconEvent) error {
return err
}
if !bytes.Equal(cb.Dpk, state.Committee().DistributedPK) {
if len(state.Committee().DistributedPK) != 0 && !bytes.Equal(cb.Dpk, state.Committee().DistributedPK) {
return ErrDistributedPubKeyMismatch
}
......
......@@ -3,6 +3,7 @@ package drng
const (
// SignatureSize defines the BLS Signature size in bytes.
SignatureSize = 96
// PublicKeySize defines the BLS Public Key size in bytes.
PublicKeySize = 48
)
......@@ -31,12 +31,15 @@ func (d *DRNG) Dispatch(issuer ed25519.PublicKey, timestamp time.Time, payload *
d.Events.CollectiveBeacon.Trigger(cbEvent)
// process collectiveBeacon
if err := ProcessBeacon(d.State, cbEvent); err != nil {
if _, ok := d.State[cbEvent.InstanceID]; !ok {
return ErrInstanceIDMismatch
}
if err := ProcessBeacon(d.State[cbEvent.InstanceID], cbEvent); err != nil {
return err
}
// trigger RandomnessEvent
d.Events.Randomness.Trigger(d.State.Randomness())
d.Events.Randomness.Trigger(d.State[cbEvent.InstanceID])
return nil
......
......@@ -7,6 +7,7 @@ import (
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
......@@ -57,9 +58,24 @@ func TestDispatcher(t *testing.T) {
marshalUtil := marshalutil.New(testPayload().Bytes())
parsedPayload, err := PayloadFromMarshalUtil(marshalUtil)
require.NoError(t, err)
config := make(map[uint32][]Option)
config[1] = []Option{SetCommittee(committeeTest)}
drng := New(SetCommittee(committeeTest))
drng := New(config)
err = drng.Dispatch(issuerPK, timestampTest, parsedPayload)
require.NoError(t, err)
require.Equal(t, *randomnessTest, drng.State.Randomness())
require.Equal(t, *randomnessTest, drng.State[1].Randomness())
}
func TestEmptyState(t *testing.T) {
marshalUtil := marshalutil.New(testPayload().Bytes())
parsedPayload, err := PayloadFromMarshalUtil(marshalUtil)
require.NoError(t, err)
config := make(map[uint32][]Option)
drng := New(config)
err = drng.Dispatch(issuerPK, timestampTest, parsedPayload)
if assert.Error(t, err) {
assert.Equal(t, ErrInstanceIDMismatch, err)
}
}
......@@ -10,16 +10,22 @@ import (
// DRNG holds the state and events of a drng instance.
type DRNG struct {
State *State // The state of the DRNG.
Events *Event // The events fired on the DRNG.
State map[uint32]*State // The state of the DRNG.
Events *Event // The events fired on the DRNG.
}
// New creates a new DRNG instance.
func New(setters ...Option) *DRNG {
return &DRNG{
State: NewState(setters...),
func New(config map[uint32][]Option) *DRNG {
drng := &DRNG{
State: make(map[uint32]*State),
Events: newEvent(),
}
for id, setters := range config {
drng.State[id] = NewState(setters...)
}
return drng
}
// Options define state options of a DRNG.
......
......@@ -46,5 +46,5 @@ func newEvent() *Event {
}
func randomnessReceived(handler interface{}, params ...interface{}) {
handler.(func(Randomness))(params[0].(Randomness))
handler.(func(*State))(params[0].(*State))
}
......@@ -18,6 +18,7 @@ var drngLiveFeedWorkerPool *workerpool.WorkerPool
type drngMsg struct {
Instance uint32 `json:"instance"`
Name string `json:"name"`
DistributedPK string `json:"dpk"`
Round uint64 `json:"round"`
Randomness string `json:"randomness"`
......@@ -26,14 +27,26 @@ type drngMsg struct {
func configureDrngLiveFeed() {
drngLiveFeedWorkerPool = workerpool.New(func(task workerpool.Task) {
newRandomness := task.Param(0).(drngpkg.Randomness)
newRandomness := task.Param(0).(*drngpkg.State)
// assign the name of the instance based on its instanceID
var name string
switch newRandomness.Committee().InstanceID {
case drng.Pollen:
name = "Pollen"
case drng.XTeam:
name = "X-Team"
default:
name = "Custom"
}
broadcastWsMessage(&wsmsg{MsgTypeDrng, &drngMsg{
Instance: drng.Instance().State.Committee().InstanceID,
DistributedPK: hex.EncodeToString(drng.Instance().State.Committee().DistributedPK),
Round: newRandomness.Round,
Randomness: hex.EncodeToString(newRandomness.Randomness[:32]),
Timestamp: newRandomness.Timestamp.Format("2 Jan 2006 15:04:05")}})
Instance: newRandomness.Committee().InstanceID,
Name: name,
DistributedPK: hex.EncodeToString(newRandomness.Committee().DistributedPK),
Round: newRandomness.Randomness().Round,
Randomness: hex.EncodeToString(newRandomness.Randomness().Randomness[:32]),
Timestamp: newRandomness.Randomness().Timestamp.Format("2 Jan 2006 15:04:05")}})
task.Return(nil)
}, workerpool.WorkerCount(drngLiveFeedWorkerCount), workerpool.QueueSize(drngLiveFeedWorkerQueueSize))
......@@ -44,7 +57,7 @@ func runDrngLiveFeed() {
newMsgRateLimiter := time.NewTicker(time.Second / 10)
defer newMsgRateLimiter.Stop()
notifyNewRandomness := events.NewClosure(func(message drngpkg.Randomness) {
notifyNewRandomness := events.NewClosure(func(message *drngpkg.State) {
select {
case <-newMsgRateLimiter.C:
drngLiveFeedWorkerPool.TrySubmit(message)
......
......@@ -30,7 +30,8 @@ export class DrngLiveFeed extends React.Component<Props, any> {
<Table>
<thead>
<tr>
<td>InstanceID</td>
<td>Instance Name</td>
<td>DPubKey</td>
<td>Round</td>
<td>Randomness</td>
<td>Timestamp</td>
......
......@@ -5,6 +5,7 @@ import {RouterStore} from "mobx-react-router";
export class DrngMessage {
instance: number;
name: string;
dpk: string;
round: number;
randomness: string;
......@@ -50,7 +51,10 @@ export class DrngStore {
feed.push(
<tr key={msg.round}>
<td>
{msg.instance}
{msg.name}
</td>
<td>
{msg.dpk.substring(0,4)+".."}
</td>
<td>
{msg.round}
......
This diff is collapsed.
......@@ -12,41 +12,103 @@ import (
"github.com/mr-tron/base58/base58"
)
const (
// Pollen defines the instance ID of the Pollen drng committee.
Pollen = 1
// XTeam defines the instance ID of the X-Team drng committee.
XTeam = 1339
)
var (
// ErrParsingCommitteeMember is returned for an invalid committee member
ErrParsingCommitteeMember = errors.New("cannot parse committee member")
)
func configureDRNG() *drng.DRNG {
c := make(map[uint32][]drng.Option)
log = logger.NewLogger(PluginName)
// Pollen dRNG configuration
// parse identities of the committee members
committeeMembers, err := parseCommitteeMembers()
committeeMembers, err := parseCommitteeMembers(config.Node().GetStringSlice(CfgDRNGCommitteeMembers))
if err != nil {
log.Warnf("Invalid %s: %s", CfgDRNGCommitteeMembers, err)
}
// parse distributed public key of the committee
var dpk []byte
if str := config.Node().GetString(CfgDRNGDistributedPubKey); str != "" {
bytes, err := hex.DecodeString(str)
if err != nil {
log.Warnf("Invalid %s: %s", CfgDRNGDistributedPubKey, err)
}
if l := len(bytes); l != drng.PublicKeySize {
log.Warnf("Invalid %s length: %d, need %d", CfgDRNGDistributedPubKey, l, drng.PublicKeySize)
}
dpk = append(dpk, bytes...)
dpk, err := parseDistributedPublicKey(CfgDRNGDistributedPubKey)
if err != nil {
log.Warn(err)
}
// configure committee
committeeConf := &drng.Committee{
InstanceID: config.Node().GetUint32(CfgDRNGInstanceID),
// configure pollen committee
pollenConf := &drng.Committee{
InstanceID: Pollen,
Threshold: uint8(config.Node().GetUint32(CfgDRNGThreshold)),
DistributedPK: dpk,
Identities: committeeMembers,
}
return drng.New(drng.SetCommittee(committeeConf))
if len(committeeMembers) > 0 {
c[pollenConf.InstanceID] = []drng.Option{drng.SetCommittee(pollenConf)}
}
// X-Team dRNG configuration
// parse identities of the x-team committee members
committeeMembers, err = parseCommitteeMembers(config.Node().GetStringSlice(CfgDRNGXTeamCommitteeMembers))
if err != nil {
log.Warnf("Invalid %s: %s", CfgDRNGXTeamCommitteeMembers, err)
}
// parse distributed public key of the committee
dpk, err = parseDistributedPublicKey(CfgDRNGXTeamDistributedPubKey)
if err != nil {
log.Warn(err)
}
// configure X-Team committee
xTeamConf := &drng.Committee{
InstanceID: XTeam,
Threshold: uint8(config.Node().GetUint32(CfgDRNGXTeamThreshold)),
DistributedPK: dpk,
Identities: committeeMembers,
}
if len(committeeMembers) > 0 {
c[xTeamConf.InstanceID] = []drng.Option{drng.SetCommittee(xTeamConf)}
}
// Custom dRNG configuration
// parse identities of the x-team committee members
committeeMembers, err = parseCommitteeMembers(config.Node().GetStringSlice(CfgDRNGCustomCommitteeMembers))
if err != nil {
log.Warnf("Invalid %s: %s", CfgDRNGCustomCommitteeMembers, err)
}
// parse distributed public key of the committee
dpk, err = parseDistributedPublicKey(CfgDRNGCustomDistributedPubKey)
if err != nil {
log.Warn(err)
}
// configure Custom committee
customConf := &drng.Committee{
InstanceID: config.Node().GetUint32(CfgDRNGCustomInstanceID),
Threshold: uint8(config.Node().GetUint32(CfgDRNGCustomThreshold)),
DistributedPK: dpk,
Identities: committeeMembers,
}
if len(committeeMembers) > 0 {
if customConf.InstanceID != Pollen && customConf.InstanceID != XTeam {
c[customConf.InstanceID] = []drng.Option{drng.SetCommittee(customConf)}
} else {
log.Warnf("Invalid Custom dRNG instanceID: %d, must be different than both Pollen and X-Team dRNG instance IDs (%d - %d)", customConf.InstanceID, pollenConf.InstanceID, xTeamConf.InstanceID)
}
}
return drng.New(c)
}
// Instance returns the DRNG instance.
......@@ -55,8 +117,8 @@ func Instance() *drng.DRNG {
return instance
}
func parseCommitteeMembers() (result []ed25519.PublicKey, err error) {
for _, committeeMember := range config.Node().GetStringSlice(CfgDRNGCommitteeMembers) {
func parseCommitteeMembers(committeeMembers []string) (result []ed25519.PublicKey, err error) {
for _, committeeMember := range committeeMembers {
if committeeMember == "" {
continue
}
......@@ -75,3 +137,16 @@ func parseCommitteeMembers() (result []ed25519.PublicKey, err error) {
return result, nil
}
func parseDistributedPublicKey(pubKey string) (dpk []byte, err error) {
if str := config.Node().GetString(pubKey); str != "" {
dpk, err = hex.DecodeString(str)
if err != nil {
return []byte{}, fmt.Errorf("Invalid %s: %s", pubKey, err)
}
if l := len(dpk); l != drng.PublicKeySize {
return []byte{}, fmt.Errorf("Invalid %s length: %d, need %d", pubKey, l, drng.PublicKeySize)
}
}
return
}
......@@ -5,19 +5,56 @@ import (
)
const (
// Configuration parameters of Pollen dRNG committee.
// CfgDRNGInstanceID defines the config flag of the DRNG instanceID.
CfgDRNGInstanceID = "drng.instanceId"
CfgDRNGInstanceID = "drng.pollen.instanceId"
// CfgDRNGThreshold defines the config flag of the DRNG threshold.
CfgDRNGThreshold = "drng.threshold"
CfgDRNGThreshold = "drng.pollen.threshold"
// CfgDRNGDistributedPubKey defines the config flag of the DRNG distributed Public Key.
CfgDRNGDistributedPubKey = "drng.distributedPubKey"
CfgDRNGDistributedPubKey = "drng.pollen.distributedPubKey"
// CfgDRNGCommitteeMembers defines the config flag of the DRNG committee members identities.
CfgDRNGCommitteeMembers = "drng.committeeMembers"
CfgDRNGCommitteeMembers = "drng.pollen.committeeMembers"
// Configuration parameters of X-Team dRNG committee.
// CfgDRNGXTeamInstanceID defines the config flag of the DRNG instanceID.
CfgDRNGXTeamInstanceID = "drng.xteam.instanceId"
// CfgDRNGXTeamThreshold defines the config flag of the DRNG threshold.
CfgDRNGXTeamThreshold = "drng.xteam.threshold"
// CfgDRNGXTeamDistributedPubKey defines the config flag of the DRNG distributed Public Key.
CfgDRNGXTeamDistributedPubKey = "drng.xteam.distributedPubKey"
// CfgDRNGXTeamCommitteeMembers defines the config flag of the DRNG committee members identities.
CfgDRNGXTeamCommitteeMembers = "drng.xteam.committeeMembers"
// Configuration parameters of Custom dRNG committee.
// CfgDRNGCustomInstanceID defines the config flag of the DRNG instanceID.
CfgDRNGCustomInstanceID = "drng.custom.instanceId"
// CfgDRNGCustomThreshold defines the config flag of the DRNG threshold.
CfgDRNGCustomThreshold = "drng.custom.threshold"
// CfgDRNGCustomDistributedPubKey defines the config flag of the DRNG distributed Public Key.
CfgDRNGCustomDistributedPubKey = "drng.custom.distributedPubKey"
// CfgDRNGCustomCommitteeMembers defines the config flag of the DRNG committee members identities.
CfgDRNGCustomCommitteeMembers = "drng.custom.committeeMembers"
)
func init() {
flag.Uint32(CfgDRNGInstanceID, 1, "instance ID of the drng instance")
flag.Uint32(CfgDRNGThreshold, 3, "BLS threshold of the drng")
flag.String(CfgDRNGDistributedPubKey, "", "distributed public key of the committee (hex encoded)")
flag.StringSlice(CfgDRNGCommitteeMembers, []string{}, "list of committee members of the drng")
// Default parameters of Pollen dRNG committee.
flag.Uint32(CfgDRNGInstanceID, Pollen, "instance ID of the pollen drng instance")
flag.Uint32(CfgDRNGThreshold, 3, "BLS threshold of the pollen drng")
flag.String(CfgDRNGDistributedPubKey, "", "distributed public key of the pollen committee (hex encoded)")
flag.StringSlice(CfgDRNGCommitteeMembers, []string{}, "list of committee members of the pollen drng")
// Default parameters of X-Team dRNG committee.
flag.Uint32(CfgDRNGXTeamInstanceID, XTeam, "instance ID of the x-team drng instance")
flag.Uint32(CfgDRNGXTeamThreshold, 3, "BLS threshold of the x-team drng")
flag.String(CfgDRNGXTeamDistributedPubKey, "", "distributed public key of the x-team committee (hex encoded)")
flag.StringSlice(CfgDRNGXTeamCommitteeMembers, []string{}, "list of committee members of the x-team drng")
// Default parameters of Custom dRNG committee.
flag.Uint32(CfgDRNGCustomInstanceID, 9999, "instance ID of the custom drng instance")
flag.Uint32(CfgDRNGCustomThreshold, 3, "BLS threshold of the custom drng")
flag.String(CfgDRNGCustomDistributedPubKey, "", "distributed public key of the custom committee (hex encoded)")
flag.StringSlice(CfgDRNGCustomCommitteeMembers, []string{}, "list of committee members of the custom drng")
}
......@@ -39,7 +39,11 @@ func configure(_ *node.Plugin) {
func run(*node.Plugin) {}
func configureEvents() {
instance := Instance()
// skip the event configuration if no committee has been configured.
if len(Instance().State) == 0 {
return
}
messagelayer.Tangle().Events.MessageSolid.Attach(events.NewClosure(func(cachedMsgEvent *tangle.CachedMessageEvent) {
cachedMsgEvent.MessageMetadata.Release()
......@@ -62,7 +66,7 @@ func configureEvents() {
log.Debug(err)
return
}
log.Debug("New randomness: ", instance.State.Randomness())
log.Debug("New randomness: ", instance.State[parsedPayload.InstanceID].Randomness())
})
}))
}
......@@ -5,30 +5,45 @@ import (
"net/http"
"github.com/iotaledger/goshimmer/plugins/drng"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/labstack/echo"
"github.com/mr-tron/base58"
)
// committeeHandler returns the current DRNG committee used.
func committeeHandler(c echo.Context) error {
committee := drng.Instance().State.Committee()
identities := []string{}
for _, pk := range committee.Identities {
identities = append(identities, base58.Encode(pk[:]))
committees := []Committee{}
for _, state := range drng.Instance().State {
committees = append(committees, Committee{
InstanceID: state.Committee().InstanceID,
Threshold: state.Committee().Threshold,
Identities: identitiesToString(state.Committee().Identities),
DistributedPK: hex.EncodeToString(state.Committee().DistributedPK),
})
}
return c.JSON(http.StatusOK, CommitteeResponse{
InstanceID: committee.InstanceID,
Threshold: committee.Threshold,
Identities: identities,
DistributedPK: hex.EncodeToString(committee.DistributedPK),
Committees: committees,
})
}
// CommitteeResponse is the HTTP message containing the DRNG committee.
type CommitteeResponse struct {
Committees []Committee `json:"committees,omitempty"`
Error string `json:"error,omitempty"`
}
// Committee defines the information about a committee.
type Committee struct {
InstanceID uint32 `json:"instanceID,omitempty"`
Threshold uint8 `json:"threshold,omitempty"`
Identities []string `json:"identities,omitempty"`
DistributedPK string `json:"distributedPK,omitempty"`
Error string `json:"error,omitempty"`
}
func identitiesToString(publicKeys []ed25519.PublicKey) []string {
identities := []string{}
for _, pk := range publicKeys {
identities = append(identities, base58.Encode(pk[:]))
}
return identities
}
......@@ -10,18 +10,32 @@ import (
// randomnessHandler returns the current DRNG randomness used.
func randomnessHandler(c echo.Context) error {
randomness := drng.Instance().State.Randomness()
randomness := []Randomness{}
for _, state := range drng.Instance().State {
randomness = append(randomness,
Randomness{
InstanceID: state.Committee().InstanceID,
Round: state.Randomness().Round,
Randomness: state.Randomness().Randomness,
Timestamp: state.Randomness().Timestamp,
})
}
return c.JSON(http.StatusOK, RandomnessResponse{
Round: randomness.Round,
Randomness: randomness.Randomness,
Timestamp: randomness.Timestamp,
Randomness: randomness,
})
}
// RandomnessResponse is the HTTP message containing the current DRNG randomness.
type RandomnessResponse struct {
Randomness []Randomness `json:"randomness,omitempty"`
Error string `json:"error,omitempty"`
}
// Randomness defines the content of new randomness.
type Randomness struct {
InstanceID uint32 `json:"instanceID,omitempty"`
Round uint64 `json:"round,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
Randomness []byte `json:"randomness,omitempty"`
Error string `json:"error,omitempty"`
}
......@@ -112,10 +112,12 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error {
"--webapi.bindAddress=0.0.0.0:8080",
fmt.Sprintf("--autopeering.seed=base58:%s", config.Seed),
fmt.Sprintf("--autopeering.entryNodes=%s@%s:14626", config.EntryNodePublicKey, config.EntryNodeHost),
fmt.Sprintf("--drng.instanceId=%d", config.DRNGInstance),
fmt.Sprintf("--drng.threshold=%d", config.DRNGThreshold),
fmt.Sprintf("--drng.committeeMembers=%s", config.DRNGCommittee),
fmt.Sprintf("--drng.distributedPubKey=%s", config.DRNGDistKey),
fmt.Sprintf("--drng.custom.instanceId=%d", config.DRNGInstance),
fmt.Sprintf("--drng.custom.threshold=%d", config.DRNGThreshold),
fmt.Sprintf("--drng.custom.committeeMembers=%s", config.DRNGCommittee),
fmt.Sprintf("--drng.custom.distributedPubKey=%s", config.DRNGDistKey),
fmt.Sprintf("--drng.xteam.committeeMembers="),
fmt.Sprintf("--drng.pollen.committeeMembers="),
fmt.Sprintf("--syncbeaconfollower.followNodes=%s", config.SyncBeaconFollowNodes),
fmt.Sprintf("--syncbeacon.broadcastInterval=%d", config.SyncBeaconBroadcastInterval),
"--syncbeacon.startSynced=true",
......
......@@ -77,7 +77,7 @@ func waitForRound(t *testing.T, peer *framework.Peer, round uint64, maxAttempts
resp, err := peer.GetRandomness()
require.NoError(t, err)
b, _ = json.MarshalIndent(resp, "", " ")
if resp.Round == round {
if resp.Randomness[0].Round == round {
return string(b), nil
}
time.Sleep(1 * time.Second)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment