mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
1173 lines
30 KiB
Go
1173 lines
30 KiB
Go
package global
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
pcrypto "github.com/libp2p/go-libp2p/core/crypto"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/multiformats/go-multiaddr"
|
|
mn "github.com/multiformats/go-multiaddr/net"
|
|
"github.com/pkg/errors"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/shopspring/decimal"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/grpc"
|
|
"source.quilibrium.com/quilibrium/monorepo/config"
|
|
"source.quilibrium.com/quilibrium/monorepo/lifecycle"
|
|
"source.quilibrium.com/quilibrium/monorepo/node/consensus/provers"
|
|
consensustime "source.quilibrium.com/quilibrium/monorepo/node/consensus/time"
|
|
globalintrinsics "source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/global"
|
|
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/global/compat"
|
|
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token"
|
|
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
|
|
"source.quilibrium.com/quilibrium/monorepo/protobufs"
|
|
"source.quilibrium.com/quilibrium/monorepo/types/channel"
|
|
typesconsensus "source.quilibrium.com/quilibrium/monorepo/types/consensus"
|
|
"source.quilibrium.com/quilibrium/monorepo/types/crypto"
|
|
"source.quilibrium.com/quilibrium/monorepo/types/schema"
|
|
"source.quilibrium.com/quilibrium/monorepo/types/store"
|
|
)
|
|
|
|
func (e *GlobalConsensusEngine) eventDistributorLoop(
|
|
ctx lifecycle.SignalerContext,
|
|
) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
e.logger.Error("fatal error encountered", zap.Any("panic", r))
|
|
ctx.Throw(errors.Errorf("fatal unhandled error encountered: %v", r))
|
|
}
|
|
}()
|
|
|
|
e.logger.Debug("starting event distributor")
|
|
|
|
// Subscribe to events from the event distributor
|
|
eventCh := e.eventDistributor.Subscribe("global")
|
|
defer e.eventDistributor.Unsubscribe("global")
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-e.quit:
|
|
return
|
|
case event, ok := <-eventCh:
|
|
if !ok {
|
|
e.logger.Error("event channel closed unexpectedly")
|
|
return
|
|
}
|
|
|
|
e.logger.Debug("received event", zap.Int("event_type", int(event.Type)))
|
|
|
|
// Handle the event based on its type
|
|
switch event.Type {
|
|
case typesconsensus.ControlEventGlobalNewHead:
|
|
timer := prometheus.NewTimer(globalCoordinationDuration)
|
|
|
|
// New global frame has been selected as the head by the time reel
|
|
if data, ok := event.Data.(*consensustime.GlobalEvent); ok &&
|
|
data.Frame != nil {
|
|
e.lastObservedFrame.Store(data.Frame.Header.FrameNumber)
|
|
e.logger.Info(
|
|
"received new global head event",
|
|
zap.Uint64("frame_number", data.Frame.Header.FrameNumber),
|
|
)
|
|
|
|
e.flushDeferredGlobalMessages(data.Frame.GetRank() + 1)
|
|
|
|
// Check shard coverage
|
|
if err := e.checkShardCoverage(
|
|
data.Frame.Header.FrameNumber,
|
|
); err != nil {
|
|
e.logger.Error("failed to check shard coverage", zap.Error(err))
|
|
}
|
|
|
|
// Update global coordination metrics
|
|
globalCoordinationTotal.Inc()
|
|
timer.ObserveDuration()
|
|
|
|
_, err := e.signerRegistry.GetIdentityKey(e.GetPeerInfo().PeerId)
|
|
if err != nil && !e.hasSentKeyBundle {
|
|
e.hasSentKeyBundle = true
|
|
e.publishKeyRegistry()
|
|
}
|
|
|
|
if e.proposer != nil && !e.config.Engine.ArchiveMode {
|
|
workers, err := e.workerManager.RangeWorkers()
|
|
if err != nil {
|
|
e.logger.Error("could not retrieve workers", zap.Error(err))
|
|
} else {
|
|
if len(workers) == 0 {
|
|
e.logger.Error("no workers detected for allocation")
|
|
}
|
|
allAllocated := true
|
|
needsProposals := false
|
|
for _, w := range workers {
|
|
allAllocated = allAllocated && w.Allocated
|
|
if len(w.Filter) == 0 {
|
|
needsProposals = true
|
|
}
|
|
}
|
|
if needsProposals || !allAllocated {
|
|
e.evaluateForProposals(ctx, data, needsProposals)
|
|
} else {
|
|
self, effectiveSeniority := e.allocationContext()
|
|
e.checkExcessPendingJoins(self, data.Frame.Header.FrameNumber)
|
|
e.logAllocationStatusOnly(ctx, data, self, effectiveSeniority)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case typesconsensus.ControlEventGlobalEquivocation:
|
|
// Handle equivocation by constructing and publishing a ProverKick
|
|
// message
|
|
if data, ok := event.Data.(*consensustime.GlobalEvent); ok &&
|
|
data.Frame != nil && data.OldHead != nil {
|
|
e.logger.Warn(
|
|
"received equivocating frame",
|
|
zap.Uint64("frame_number", data.Frame.Header.FrameNumber),
|
|
)
|
|
|
|
// The equivocating prover is the one who signed the new frame
|
|
if data.Frame.Header != nil &&
|
|
data.Frame.Header.PublicKeySignatureBls48581 != nil &&
|
|
data.Frame.Header.PublicKeySignatureBls48581.PublicKey != nil {
|
|
|
|
kickedProverPublicKey :=
|
|
data.Frame.Header.PublicKeySignatureBls48581.PublicKey.KeyValue
|
|
|
|
// Serialize both conflicting frame headers
|
|
conflictingFrame1, err := data.OldHead.Header.ToCanonicalBytes()
|
|
if err != nil {
|
|
e.logger.Error(
|
|
"failed to marshal old frame header",
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
conflictingFrame2, err := data.Frame.Header.ToCanonicalBytes()
|
|
if err != nil {
|
|
e.logger.Error(
|
|
"failed to marshal new frame header",
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Create the ProverKick message using the intrinsic struct
|
|
proverKick, err := globalintrinsics.NewProverKick(
|
|
data.Frame.Header.FrameNumber,
|
|
kickedProverPublicKey,
|
|
conflictingFrame1,
|
|
conflictingFrame2,
|
|
e.blsConstructor,
|
|
e.frameProver,
|
|
e.hypergraph,
|
|
schema.NewRDFMultiprover(
|
|
&schema.TurtleRDFParser{},
|
|
e.inclusionProver,
|
|
),
|
|
e.proverRegistry,
|
|
e.clockStore,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error(
|
|
"failed to construct prover kick",
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
err = proverKick.Prove(data.Frame.Header.FrameNumber)
|
|
if err != nil {
|
|
e.logger.Error(
|
|
"failed to prove prover kick",
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Serialize the ProverKick to the request form
|
|
kickBytes, err := proverKick.ToRequestBytes()
|
|
if err != nil {
|
|
e.logger.Error(
|
|
"failed to serialize prover kick",
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Publish the kick message
|
|
if err := e.pubsub.PublishToBitmask(
|
|
GLOBAL_PROVER_BITMASK,
|
|
kickBytes,
|
|
); err != nil {
|
|
e.logger.Error("failed to publish prover kick", zap.Error(err))
|
|
} else {
|
|
e.logger.Info(
|
|
"published prover kick for equivocation",
|
|
zap.Uint64("frame_number", data.Frame.Header.FrameNumber),
|
|
zap.String(
|
|
"kicked_prover",
|
|
hex.EncodeToString(kickedProverPublicKey),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
case typesconsensus.ControlEventCoverageHalt:
|
|
data, ok := event.Data.(*typesconsensus.CoverageEventData)
|
|
if ok && data.Message != "" {
|
|
e.logger.Error(data.Message)
|
|
e.halt()
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(10 * time.Second):
|
|
e.logger.Error(
|
|
"full halt detected, leaving system in halted state until recovery",
|
|
)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
case typesconsensus.ControlEventHalt:
|
|
data, ok := event.Data.(*typesconsensus.ErrorEventData)
|
|
if ok && data.Error != nil {
|
|
e.logger.Error(
|
|
"full halt detected, leaving system in halted state",
|
|
zap.Error(data.Error),
|
|
)
|
|
e.halt()
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(10 * time.Second):
|
|
e.logger.Error(
|
|
"full halt detected, leaving system in halted state",
|
|
zap.Error(data.Error),
|
|
)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
default:
|
|
e.logger.Debug(
|
|
"received unhandled event type",
|
|
zap.Int("event_type", int(event.Type)),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const pendingFilterGraceFrames = 720
|
|
|
|
func (e *GlobalConsensusEngine) emitCoverageEvent(
|
|
eventType typesconsensus.ControlEventType,
|
|
data *typesconsensus.CoverageEventData,
|
|
) {
|
|
event := typesconsensus.ControlEvent{
|
|
Type: eventType,
|
|
Data: data,
|
|
}
|
|
|
|
go e.eventDistributor.Publish(event)
|
|
|
|
e.logger.Info(
|
|
"emitted coverage event",
|
|
zap.String("type", fmt.Sprintf("%d", eventType)),
|
|
zap.String("shard_address", hex.EncodeToString(data.ShardAddress)),
|
|
zap.Int("prover_count", data.ProverCount),
|
|
zap.String("message", data.Message),
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) emitMergeEvent(
|
|
data *typesconsensus.ShardMergeEventData,
|
|
) {
|
|
event := typesconsensus.ControlEvent{
|
|
Type: typesconsensus.ControlEventShardMergeEligible,
|
|
Data: data,
|
|
}
|
|
|
|
go e.eventDistributor.Publish(event)
|
|
|
|
e.logger.Info(
|
|
"emitted merge eligible event",
|
|
zap.Int("shard_count", len(data.ShardAddresses)),
|
|
zap.Int("total_provers", data.TotalProvers),
|
|
zap.Uint64("attested_storage", data.AttestedStorage),
|
|
zap.Uint64("required_storage", data.RequiredStorage),
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) emitSplitEvent(
|
|
data *typesconsensus.ShardSplitEventData,
|
|
) {
|
|
event := typesconsensus.ControlEvent{
|
|
Type: typesconsensus.ControlEventShardSplitEligible,
|
|
Data: data,
|
|
}
|
|
|
|
go e.eventDistributor.Publish(event)
|
|
|
|
e.logger.Info(
|
|
"emitted split eligible event",
|
|
zap.String("shard_address", hex.EncodeToString(data.ShardAddress)),
|
|
zap.Int("proposed_shard_count", len(data.ProposedShards)),
|
|
zap.Int("prover_count", data.ProverCount),
|
|
zap.Uint64("attested_storage", data.AttestedStorage),
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) emitAlertEvent(alertMessage string) {
|
|
event := typesconsensus.ControlEvent{
|
|
Type: typesconsensus.ControlEventHalt,
|
|
Data: &typesconsensus.ErrorEventData{
|
|
Error: errors.New(alertMessage),
|
|
},
|
|
}
|
|
|
|
go e.eventDistributor.Publish(event)
|
|
|
|
e.logger.Info("emitted alert message")
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) estimateSeniorityFromConfig() uint64 {
|
|
peerIds := []string{}
|
|
peerIds = append(peerIds, peer.ID(e.pubsub.GetPeerID()).String())
|
|
if len(e.config.Engine.MultisigProverEnrollmentPaths) != 0 {
|
|
for _, conf := range e.config.Engine.MultisigProverEnrollmentPaths {
|
|
extraConf, err := config.LoadConfig(conf, "", false)
|
|
if err != nil {
|
|
e.logger.Error("could not load config", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
peerPrivKey, err := hex.DecodeString(extraConf.P2P.PeerPrivKey)
|
|
if err != nil {
|
|
e.logger.Error("could not decode peer key", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
privKey, err := pcrypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
|
if err != nil {
|
|
e.logger.Error("could not unmarshal peer key", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
pub := privKey.GetPublic()
|
|
id, err := peer.IDFromPublicKey(pub)
|
|
if err != nil {
|
|
e.logger.Error("could not unmarshal peerid", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
peerIds = append(peerIds, id.String())
|
|
}
|
|
}
|
|
seniorityBI := compat.GetAggregatedSeniority(peerIds)
|
|
return seniorityBI.Uint64()
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) evaluateForProposals(
|
|
ctx context.Context,
|
|
data *consensustime.GlobalEvent,
|
|
allowProposals bool,
|
|
) {
|
|
self, effectiveSeniority := e.allocationContext()
|
|
e.reconcileWorkerAllocations(data.Frame.Header.FrameNumber, self)
|
|
e.checkExcessPendingJoins(self, data.Frame.Header.FrameNumber)
|
|
canPropose, skipReason := e.joinProposalReady(data.Frame.Header.FrameNumber)
|
|
|
|
snapshot, ok := e.collectAllocationSnapshot(
|
|
ctx,
|
|
data,
|
|
self,
|
|
effectiveSeniority,
|
|
)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
e.logAllocationStatus(snapshot)
|
|
pendingFilters := snapshot.pendingFilters
|
|
proposalDescriptors := snapshot.proposalDescriptors
|
|
decideDescriptors := snapshot.decideDescriptors
|
|
worldBytes := snapshot.worldBytes
|
|
|
|
if len(proposalDescriptors) != 0 && allowProposals {
|
|
if canPropose {
|
|
proposals, err := e.proposer.PlanAndAllocate(
|
|
uint64(data.Frame.Header.Difficulty),
|
|
proposalDescriptors,
|
|
100,
|
|
worldBytes,
|
|
data.Frame.Header.FrameNumber,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not plan shard allocations", zap.Error(err))
|
|
} else {
|
|
if len(proposals) > 0 {
|
|
e.lastJoinAttemptFrame.Store(data.Frame.Header.FrameNumber)
|
|
}
|
|
expectedRewardSum := big.NewInt(0)
|
|
for _, p := range proposals {
|
|
expectedRewardSum.Add(expectedRewardSum, p.ExpectedReward)
|
|
}
|
|
raw := decimal.NewFromBigInt(expectedRewardSum, 0)
|
|
rewardInQuilPerInterval := raw.Div(decimal.NewFromInt(8000000000))
|
|
rewardInQuilPerDay := rewardInQuilPerInterval.Mul(
|
|
decimal.NewFromInt(24 * 60 * 6),
|
|
)
|
|
e.logger.Info(
|
|
"proposed joins",
|
|
zap.Int("shard_proposals", len(proposals)),
|
|
zap.String(
|
|
"estimated_reward_per_interval",
|
|
rewardInQuilPerInterval.String(),
|
|
),
|
|
zap.String(
|
|
"estimated_reward_per_day",
|
|
rewardInQuilPerDay.String(),
|
|
),
|
|
)
|
|
}
|
|
} else {
|
|
e.logger.Info(
|
|
"skipping join proposals",
|
|
zap.String("reason", skipReason),
|
|
zap.Uint64("frame_number", data.Frame.Header.FrameNumber),
|
|
)
|
|
}
|
|
} else if len(proposalDescriptors) != 0 && !allowProposals {
|
|
e.logger.Info(
|
|
"skipping join proposals",
|
|
zap.String("reason", "all workers already assigned filters"),
|
|
zap.Uint64("frame_number", data.Frame.Header.FrameNumber),
|
|
)
|
|
}
|
|
if len(pendingFilters) != 0 {
|
|
if err := e.proposer.DecideJoins(
|
|
uint64(data.Frame.Header.Difficulty),
|
|
decideDescriptors,
|
|
pendingFilters,
|
|
worldBytes,
|
|
); err != nil {
|
|
e.logger.Error("could not decide shard allocations", zap.Error(err))
|
|
} else {
|
|
e.logger.Info(
|
|
"decided on joins",
|
|
zap.Int("joins", len(pendingFilters)),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
type allocationSnapshot struct {
|
|
shardsPending int
|
|
awaitingFrames []string
|
|
shardsLeaving int
|
|
shardsActive int
|
|
shardsPaused int
|
|
shardDivisions int
|
|
logicalShards int
|
|
pendingFilters [][]byte
|
|
proposalDescriptors []provers.ShardDescriptor
|
|
decideDescriptors []provers.ShardDescriptor
|
|
worldBytes *big.Int
|
|
}
|
|
|
|
func (s *allocationSnapshot) statusFields() []zap.Field {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
return []zap.Field{
|
|
zap.Int("pending_joins", s.shardsPending),
|
|
zap.String("pending_join_frames", strings.Join(s.awaitingFrames, ", ")),
|
|
zap.Int("pending_leaves", s.shardsLeaving),
|
|
zap.Int("active", s.shardsActive),
|
|
zap.Int("paused", s.shardsPaused),
|
|
zap.Int("network_shards", s.shardDivisions),
|
|
zap.Int("network_logical_shards", s.logicalShards),
|
|
}
|
|
}
|
|
|
|
func (s *allocationSnapshot) proposalSnapshotFields() []zap.Field {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
return []zap.Field{
|
|
zap.Int("proposal_candidates", len(s.proposalDescriptors)),
|
|
zap.Int("pending_confirmations", len(s.pendingFilters)),
|
|
zap.Int("decide_descriptors", len(s.decideDescriptors)),
|
|
}
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) reconcileWorkerAllocations(
|
|
frameNumber uint64,
|
|
self *typesconsensus.ProverInfo,
|
|
) {
|
|
if e.workerManager == nil {
|
|
return
|
|
}
|
|
|
|
workers, err := e.workerManager.RangeWorkers()
|
|
if err != nil {
|
|
e.logger.Warn("could not load workers for reconciliation", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
filtersToWorkers := make(map[string]*store.WorkerInfo, len(workers))
|
|
freeWorkers := make([]*store.WorkerInfo, 0, len(workers))
|
|
for _, worker := range workers {
|
|
if worker == nil {
|
|
continue
|
|
}
|
|
if len(worker.Filter) == 0 {
|
|
freeWorkers = append(freeWorkers, worker)
|
|
continue
|
|
}
|
|
filtersToWorkers[string(worker.Filter)] = worker
|
|
}
|
|
|
|
seenFilters := make(map[string]struct{})
|
|
if self != nil {
|
|
for _, alloc := range self.Allocations {
|
|
if len(alloc.ConfirmationFilter) == 0 {
|
|
continue
|
|
}
|
|
|
|
key := string(alloc.ConfirmationFilter)
|
|
worker, ok := filtersToWorkers[key]
|
|
if !ok {
|
|
if len(freeWorkers) == 0 {
|
|
e.logger.Warn(
|
|
"no free worker available for registry allocation",
|
|
zap.String("filter", hex.EncodeToString(alloc.ConfirmationFilter)),
|
|
)
|
|
continue
|
|
}
|
|
worker = freeWorkers[0]
|
|
freeWorkers = freeWorkers[1:]
|
|
worker.Filter = slices.Clone(alloc.ConfirmationFilter)
|
|
}
|
|
|
|
seenFilters[key] = struct{}{}
|
|
|
|
desiredAllocated := alloc.Status == typesconsensus.ProverStatusActive ||
|
|
alloc.Status == typesconsensus.ProverStatusPaused
|
|
|
|
pendingFrame := alloc.JoinFrameNumber
|
|
if desiredAllocated {
|
|
pendingFrame = 0
|
|
}
|
|
|
|
if worker.Allocated != desiredAllocated ||
|
|
worker.PendingFilterFrame != pendingFrame {
|
|
worker.Allocated = desiredAllocated
|
|
worker.PendingFilterFrame = pendingFrame
|
|
if err := e.workerManager.RegisterWorker(worker); err != nil {
|
|
e.logger.Warn(
|
|
"failed to update worker allocation state",
|
|
zap.Uint("core_id", worker.CoreId),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, worker := range workers {
|
|
if worker == nil || len(worker.Filter) == 0 {
|
|
continue
|
|
}
|
|
if _, ok := seenFilters[string(worker.Filter)]; ok {
|
|
continue
|
|
}
|
|
|
|
if worker.PendingFilterFrame != 0 {
|
|
if frameNumber <= worker.PendingFilterFrame {
|
|
continue
|
|
}
|
|
if frameNumber-worker.PendingFilterFrame < pendingFilterGraceFrames {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if worker.PendingFilterFrame == 0 && self == nil {
|
|
continue
|
|
}
|
|
|
|
worker.Filter = nil
|
|
worker.Allocated = false
|
|
worker.PendingFilterFrame = 0
|
|
if err := e.workerManager.RegisterWorker(worker); err != nil {
|
|
e.logger.Warn(
|
|
"failed to clear stale worker filter",
|
|
zap.Uint("core_id", worker.CoreId),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) collectAllocationSnapshot(
|
|
ctx context.Context,
|
|
data *consensustime.GlobalEvent,
|
|
self *typesconsensus.ProverInfo,
|
|
effectiveSeniority uint64,
|
|
) (*allocationSnapshot, bool) {
|
|
appShards, err := e.shardsStore.RangeAppShards()
|
|
if err != nil {
|
|
e.logger.Error("could not obtain app shard info", zap.Error(err))
|
|
return nil, false
|
|
}
|
|
|
|
// consolidate into high level L2 shards:
|
|
shardMap := map[string]store.ShardInfo{}
|
|
for _, s := range appShards {
|
|
shardMap[string(s.L2)] = s
|
|
}
|
|
|
|
shards := []store.ShardInfo{}
|
|
for _, s := range shardMap {
|
|
shards = append(shards, store.ShardInfo{
|
|
L1: s.L1,
|
|
L2: s.L2,
|
|
})
|
|
}
|
|
|
|
registry, err := e.keyStore.GetKeyRegistryByProver(data.Frame.Header.Prover)
|
|
if err != nil {
|
|
e.logger.Info(
|
|
"awaiting key registry info for prover",
|
|
zap.String(
|
|
"prover_address",
|
|
hex.EncodeToString(data.Frame.Header.Prover),
|
|
),
|
|
)
|
|
return nil, false
|
|
}
|
|
|
|
if registry.IdentityKey == nil || registry.IdentityKey.KeyValue == nil {
|
|
e.logger.Info("key registry info missing identity of prover")
|
|
return nil, false
|
|
}
|
|
|
|
pub, err := pcrypto.UnmarshalEd448PublicKey(registry.IdentityKey.KeyValue)
|
|
if err != nil {
|
|
e.logger.Warn("error unmarshaling identity key", zap.Error(err))
|
|
return nil, false
|
|
}
|
|
|
|
peerId, err := peer.IDFromPublicKey(pub)
|
|
if err != nil {
|
|
e.logger.Warn("error deriving peer id", zap.Error(err))
|
|
return nil, false
|
|
}
|
|
|
|
info := e.peerInfoManager.GetPeerInfo([]byte(peerId))
|
|
if info == nil {
|
|
e.logger.Info(
|
|
"no peer info known yet",
|
|
zap.String("peer", peer.ID(peerId).String()),
|
|
)
|
|
return nil, false
|
|
}
|
|
|
|
if len(info.Reachability) == 0 {
|
|
e.logger.Info(
|
|
"no reachability info known yet",
|
|
zap.String("peer", peer.ID(peerId).String()),
|
|
)
|
|
return nil, false
|
|
}
|
|
|
|
var client protobufs.GlobalServiceClient = nil
|
|
if len(info.Reachability[0].StreamMultiaddrs) > 0 {
|
|
s := info.Reachability[0].StreamMultiaddrs[0]
|
|
creds, err := p2p.NewPeerAuthenticator(
|
|
e.logger,
|
|
e.config.P2P,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
[][]byte{[]byte(peerId)},
|
|
map[string]channel.AllowedPeerPolicyType{},
|
|
map[string]channel.AllowedPeerPolicyType{},
|
|
).CreateClientTLSCredentials([]byte(peerId))
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
ma, err := multiaddr.StringCast(s)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
mga, err := mn.ToNetAddr(ma)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
cc, err := grpc.NewClient(
|
|
mga.String(),
|
|
grpc.WithTransportCredentials(creds),
|
|
)
|
|
if err != nil {
|
|
e.logger.Debug(
|
|
"could not establish direct channel, trying next multiaddr",
|
|
zap.String("peer", peer.ID(peerId).String()),
|
|
zap.String("multiaddr", ma.String()),
|
|
zap.Error(err),
|
|
)
|
|
return nil, false
|
|
}
|
|
defer func() {
|
|
if err := cc.Close(); err != nil {
|
|
e.logger.Error("error while closing connection", zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
client = protobufs.NewGlobalServiceClient(cc)
|
|
}
|
|
|
|
if client == nil {
|
|
e.logger.Debug("could not get app shards from prover")
|
|
return nil, false
|
|
}
|
|
|
|
worldBytes := big.NewInt(0)
|
|
shardsPending := 0
|
|
shardsActive := 0
|
|
shardsLeaving := 0
|
|
shardsPaused := 0
|
|
logicalShards := 0
|
|
shardDivisions := 0
|
|
awaitingFrame := map[uint64]struct{}{}
|
|
pendingFilters := [][]byte{}
|
|
proposalDescriptors := []provers.ShardDescriptor{}
|
|
decideDescriptors := []provers.ShardDescriptor{}
|
|
|
|
for _, shardInfo := range shards {
|
|
resp, err := e.getAppShardsFromProver(
|
|
client,
|
|
slices.Concat(shardInfo.L1, shardInfo.L2),
|
|
)
|
|
if err != nil {
|
|
e.logger.Debug("could not get app shards from prover", zap.Error(err))
|
|
return nil, false
|
|
}
|
|
|
|
for _, shard := range resp.Info {
|
|
shardDivisions++
|
|
worldBytes = worldBytes.Add(worldBytes, new(big.Int).SetBytes(shard.Size))
|
|
bp := slices.Clone(shardInfo.L2)
|
|
for _, p := range shard.Prefix {
|
|
bp = append(bp, byte(p))
|
|
}
|
|
|
|
prs, err := e.proverRegistry.GetProvers(bp)
|
|
if err != nil {
|
|
e.logger.Error("failed to get provers", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
allocated := false
|
|
pending := false
|
|
if self != nil {
|
|
for _, allocation := range self.Allocations {
|
|
if bytes.Equal(allocation.ConfirmationFilter, bp) {
|
|
allocated = allocation.Status != 4
|
|
if allocation.Status == typesconsensus.ProverStatusJoining {
|
|
shardsPending++
|
|
awaitingFrame[allocation.JoinFrameNumber+360] = struct{}{}
|
|
}
|
|
if allocation.Status == typesconsensus.ProverStatusActive {
|
|
shardsActive++
|
|
}
|
|
if allocation.Status == typesconsensus.ProverStatusLeaving {
|
|
shardsLeaving++
|
|
}
|
|
if allocation.Status == typesconsensus.ProverStatusPaused {
|
|
shardsPaused++
|
|
}
|
|
if e.config.P2P.Network != 0 ||
|
|
data.Frame.Header.FrameNumber > token.FRAME_2_1_EXTENDED_ENROLL_END {
|
|
pending = allocation.Status ==
|
|
typesconsensus.ProverStatusJoining &&
|
|
allocation.JoinFrameNumber+360 <= data.Frame.Header.FrameNumber
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
size := new(big.Int).SetBytes(shard.Size)
|
|
if size.Cmp(big.NewInt(0)) == 0 {
|
|
continue
|
|
}
|
|
|
|
logicalShards += int(shard.DataShards)
|
|
|
|
above := []*typesconsensus.ProverInfo{}
|
|
for _, i := range prs {
|
|
for _, a := range i.Allocations {
|
|
if !bytes.Equal(a.ConfirmationFilter, bp) {
|
|
continue
|
|
}
|
|
if a.Status == typesconsensus.ProverStatusActive ||
|
|
a.Status == typesconsensus.ProverStatusJoining {
|
|
if i.Seniority >= effectiveSeniority {
|
|
above = append(above, i)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if allocated && pending {
|
|
pendingFilters = append(pendingFilters, bp)
|
|
}
|
|
if !allocated {
|
|
proposalDescriptors = append(
|
|
proposalDescriptors,
|
|
provers.ShardDescriptor{
|
|
Filter: bp,
|
|
Size: size.Uint64(),
|
|
Ring: uint8(len(above) / 8),
|
|
Shards: shard.DataShards,
|
|
},
|
|
)
|
|
}
|
|
decideDescriptors = append(
|
|
decideDescriptors,
|
|
provers.ShardDescriptor{
|
|
Filter: bp,
|
|
Size: size.Uint64(),
|
|
Ring: uint8(len(above) / 8),
|
|
Shards: shard.DataShards,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
awaitingFrames := []string{}
|
|
for frame := range awaitingFrame {
|
|
awaitingFrames = append(awaitingFrames, fmt.Sprintf("%d", frame))
|
|
}
|
|
|
|
return &allocationSnapshot{
|
|
shardsPending: shardsPending,
|
|
awaitingFrames: awaitingFrames,
|
|
shardsLeaving: shardsLeaving,
|
|
shardsActive: shardsActive,
|
|
shardsPaused: shardsPaused,
|
|
shardDivisions: shardDivisions,
|
|
logicalShards: logicalShards,
|
|
pendingFilters: pendingFilters,
|
|
proposalDescriptors: proposalDescriptors,
|
|
decideDescriptors: decideDescriptors,
|
|
worldBytes: worldBytes,
|
|
}, true
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) logAllocationStatus(
|
|
snapshot *allocationSnapshot,
|
|
) {
|
|
if snapshot == nil {
|
|
return
|
|
}
|
|
|
|
e.logger.Info(
|
|
"status for allocations",
|
|
snapshot.statusFields()...,
|
|
)
|
|
|
|
e.logger.Debug(
|
|
"proposal evaluation snapshot",
|
|
snapshot.proposalSnapshotFields()...,
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) logAllocationStatusOnly(
|
|
ctx context.Context,
|
|
data *consensustime.GlobalEvent,
|
|
self *typesconsensus.ProverInfo,
|
|
effectiveSeniority uint64,
|
|
) {
|
|
snapshot, ok := e.collectAllocationSnapshot(
|
|
ctx,
|
|
data,
|
|
self,
|
|
effectiveSeniority,
|
|
)
|
|
if !ok || snapshot == nil {
|
|
e.logger.Info(
|
|
"all workers already allocated or pending; skipping proposal cycle",
|
|
)
|
|
return
|
|
}
|
|
|
|
e.logger.Info(
|
|
"all workers already allocated or pending; skipping proposal cycle",
|
|
snapshot.statusFields()...,
|
|
)
|
|
e.logAllocationStatus(snapshot)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) allocationContext() (
|
|
*typesconsensus.ProverInfo,
|
|
uint64,
|
|
) {
|
|
self, err := e.proverRegistry.GetProverInfo(e.getProverAddress())
|
|
if err != nil || self == nil {
|
|
return nil, e.estimateSeniorityFromConfig()
|
|
}
|
|
return self, self.Seniority
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) checkExcessPendingJoins(
|
|
self *typesconsensus.ProverInfo,
|
|
frameNumber uint64,
|
|
) {
|
|
excessFilters := e.selectExcessPendingFilters(self)
|
|
if len(excessFilters) != 0 {
|
|
e.logger.Debug(
|
|
"identified excess pending joins",
|
|
zap.Int("excess_count", len(excessFilters)),
|
|
zap.Uint64("frame_number", frameNumber),
|
|
)
|
|
e.rejectExcessPending(excessFilters, frameNumber)
|
|
return
|
|
}
|
|
|
|
e.logger.Debug(
|
|
"no excess pending joins detected",
|
|
zap.Uint64("frame_number", frameNumber),
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) publishKeyRegistry() {
|
|
vk, err := e.keyManager.GetAgreementKey("q-view-key")
|
|
if err != nil {
|
|
vk, err = e.keyManager.CreateAgreementKey(
|
|
"q-view-key",
|
|
crypto.KeyTypeDecaf448,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
sk, err := e.keyManager.GetAgreementKey("q-spend-key")
|
|
if err != nil {
|
|
sk, err = e.keyManager.CreateAgreementKey(
|
|
"q-spend-key",
|
|
crypto.KeyTypeDecaf448,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
pk, err := e.keyManager.GetSigningKey("q-prover-key")
|
|
if err != nil {
|
|
pk, _, err = e.keyManager.CreateSigningKey(
|
|
"q-prover-key",
|
|
crypto.KeyTypeBLS48581G1,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
onion, err := e.keyManager.GetAgreementKey("q-onion-key")
|
|
if err != nil {
|
|
onion, err = e.keyManager.CreateAgreementKey(
|
|
"q-onion-key",
|
|
crypto.KeyTypeX448,
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
sig, err := e.pubsub.SignMessage(
|
|
slices.Concat(
|
|
[]byte("KEY_REGISTRY"),
|
|
pk.Public().([]byte),
|
|
),
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
sigp, err := pk.SignWithDomain(
|
|
e.pubsub.GetPublicKey(),
|
|
[]byte("KEY_REGISTRY"),
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
sigvk, err := e.pubsub.SignMessage(
|
|
slices.Concat(
|
|
[]byte("KEY_REGISTRY"),
|
|
vk.Public(),
|
|
),
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
sigsk, err := e.pubsub.SignMessage(
|
|
slices.Concat(
|
|
[]byte("KEY_REGISTRY"),
|
|
sk.Public(),
|
|
),
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
sigonion, err := e.pubsub.SignMessage(
|
|
slices.Concat(
|
|
[]byte("KEY_REGISTRY"),
|
|
onion.Public(),
|
|
),
|
|
)
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
registry := &protobufs.KeyRegistry{
|
|
LastUpdated: uint64(time.Now().UnixMilli()),
|
|
IdentityKey: &protobufs.Ed448PublicKey{
|
|
KeyValue: e.pubsub.GetPublicKey(),
|
|
},
|
|
ProverKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: pk.Public().([]byte),
|
|
},
|
|
IdentityToProver: &protobufs.Ed448Signature{
|
|
Signature: sig,
|
|
},
|
|
ProverToIdentity: &protobufs.BLS48581Signature{
|
|
Signature: sigp,
|
|
},
|
|
KeysByPurpose: map[string]*protobufs.KeyCollection{
|
|
"ONION_ROUTING": &protobufs.KeyCollection{
|
|
KeyPurpose: "ONION_ROUTING",
|
|
X448Keys: []*protobufs.SignedX448Key{{
|
|
Key: &protobufs.X448PublicKey{
|
|
KeyValue: onion.Public(),
|
|
},
|
|
ParentKeyAddress: e.pubsub.GetPeerID(),
|
|
Signature: &protobufs.SignedX448Key_Ed448Signature{
|
|
Ed448Signature: &protobufs.Ed448Signature{
|
|
Signature: sigonion,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
"view": &protobufs.KeyCollection{
|
|
KeyPurpose: "view",
|
|
Decaf448Keys: []*protobufs.SignedDecaf448Key{{
|
|
Key: &protobufs.Decaf448PublicKey{
|
|
KeyValue: vk.Public(),
|
|
},
|
|
ParentKeyAddress: e.pubsub.GetPeerID(),
|
|
Signature: &protobufs.SignedDecaf448Key_Ed448Signature{
|
|
Ed448Signature: &protobufs.Ed448Signature{
|
|
Signature: sigvk,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
"spend": &protobufs.KeyCollection{
|
|
KeyPurpose: "spend",
|
|
Decaf448Keys: []*protobufs.SignedDecaf448Key{{
|
|
Key: &protobufs.Decaf448PublicKey{
|
|
KeyValue: sk.Public(),
|
|
},
|
|
ParentKeyAddress: e.pubsub.GetPeerID(),
|
|
Signature: &protobufs.SignedDecaf448Key_Ed448Signature{
|
|
Ed448Signature: &protobufs.Ed448Signature{
|
|
Signature: sigsk,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
kr, err := registry.ToCanonicalBytes()
|
|
if err != nil {
|
|
e.logger.Error("could not publish key registry", zap.Error(err))
|
|
return
|
|
}
|
|
e.pubsub.PublishToBitmask(
|
|
GLOBAL_PEER_INFO_BITMASK,
|
|
kr,
|
|
)
|
|
}
|
|
|
|
func (e *GlobalConsensusEngine) getAppShardsFromProver(
|
|
client protobufs.GlobalServiceClient,
|
|
shardKey []byte,
|
|
) (
|
|
*protobufs.GetAppShardsResponse,
|
|
error,
|
|
) {
|
|
getCtx, cancelGet := context.WithTimeout(
|
|
context.Background(),
|
|
e.config.Engine.SyncTimeout,
|
|
)
|
|
response, err := client.GetAppShards(
|
|
getCtx,
|
|
&protobufs.GetAppShardsRequest{
|
|
ShardKey: shardKey, // buildutils:allow-slice-alias slice is static
|
|
},
|
|
// The message size limits are swapped because the server is the one
|
|
// sending the data.
|
|
grpc.MaxCallRecvMsgSize(
|
|
e.config.Engine.SyncMessageLimits.MaxSendMsgSize,
|
|
),
|
|
grpc.MaxCallSendMsgSize(
|
|
e.config.Engine.SyncMessageLimits.MaxRecvMsgSize,
|
|
),
|
|
)
|
|
cancelGet()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response == nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|