mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
148 lines
3.3 KiB
Go
148 lines
3.3 KiB
Go
package thresholdd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/luxfi/log"
|
|
"github.com/luxfi/metric"
|
|
"github.com/luxfi/threshold/internal/test"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/protocol"
|
|
)
|
|
|
|
// runMultiparty drives a synchronous in-process MPC round trip for every
|
|
// listed party and returns the final per-party result map.
|
|
//
|
|
// This is the production path the daemon uses for the protocols that
|
|
// actually need a round-based exchange (cmp / frost / doerner). Pulsar,
|
|
// corona, and bls have direct single-process keygen functions and skip
|
|
// this runner.
|
|
func runMultiparty(
|
|
partyIDs []party.ID,
|
|
sessionID []byte,
|
|
start func(party.ID) protocol.StartFunc,
|
|
timeout time.Duration,
|
|
) (map[party.ID]any, error) {
|
|
if timeout <= 0 {
|
|
timeout = 2 * time.Minute
|
|
}
|
|
|
|
cfg := &protocol.Config{
|
|
Workers: 4,
|
|
PriorityWorkers: 4,
|
|
BufferSize: 10000,
|
|
PriorityBuffer: 1000,
|
|
MessageTimeout: 30 * time.Second,
|
|
RoundTimeout: 60 * time.Second,
|
|
ProtocolTimeout: timeout,
|
|
}
|
|
// Use a writer-backed logger at FatalLevel so NewHandler accepts it
|
|
// (NoOpLogger.IsZero() == true and is rejected). Discarding output
|
|
// keeps the daemon silent under load.
|
|
logger := log.NewWriter(io.Discard).Level(log.FatalLevel)
|
|
|
|
network := test.NewNetwork(partyIDs)
|
|
handlers := make(map[party.ID]*protocol.Handler, len(partyIDs))
|
|
|
|
for _, id := range partyIDs {
|
|
h, err := protocol.NewHandler(
|
|
context.Background(),
|
|
logger,
|
|
metric.NewRegistry(),
|
|
start(id),
|
|
sessionID,
|
|
cfg,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create handler %s: %w", id, err)
|
|
}
|
|
handlers[id] = h
|
|
}
|
|
|
|
results := make(map[party.ID]any, len(partyIDs))
|
|
errs := make(map[party.ID]error, len(partyIDs))
|
|
var mu sync.Mutex
|
|
var wg sync.WaitGroup
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
for id, h := range handlers {
|
|
wg.Add(1)
|
|
go func(id party.ID, h *protocol.Handler) {
|
|
defer wg.Done()
|
|
// per-party done channel scopes the router goroutines so
|
|
// fast finishers don't race the global ctx cancel.
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
case msg := <-network.Next(id):
|
|
if msg != nil {
|
|
h.Accept(msg)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
case msg, ok := <-h.Listen():
|
|
if !ok {
|
|
return
|
|
}
|
|
if msg != nil {
|
|
network.Send(msg)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
res, err := h.WaitForResult()
|
|
mu.Lock()
|
|
results[id] = res
|
|
errs[id] = err
|
|
mu.Unlock()
|
|
}(id, h)
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() { wg.Wait(); close(done) }()
|
|
select {
|
|
case <-done:
|
|
case <-ctx.Done():
|
|
return nil, fmt.Errorf("protocol timeout after %s", timeout)
|
|
}
|
|
|
|
for id, err := range errs {
|
|
if err != nil {
|
|
return results, fmt.Errorf("party %s: %w", id, err)
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// partyIDs returns deterministic sorted party.IDs for n parties.
|
|
func partyIDs(n int) []party.ID {
|
|
out := make([]party.ID, n)
|
|
base := ""
|
|
for i := 0; i < n; i++ {
|
|
if i%26 == 0 && i > 0 {
|
|
base += "a"
|
|
}
|
|
out[i] = party.ID(base + string('a'+rune(i%26)))
|
|
}
|
|
return out
|
|
}
|