mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
163 lines
4.4 KiB
Go
163 lines
4.4 KiB
Go
// // Copyright (C) 2025, Lux Industries Inc. All rights reserved
|
|
// See the file LICENSE for licensing terms.
|
|
package interchain
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/luxfi/node/v2/v2/utils/logging"
|
|
"github.com/luxfi/warp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
SignatureAggregatorRequestTimeout = 30 * time.Second
|
|
DefaultQuorumPercentage = 67
|
|
MaxRetries = 3
|
|
InitialBackoff = 1 * time.Second
|
|
)
|
|
|
|
// AggregateSignatureRequest defines the request structure for signature aggregation
|
|
type AggregateSignatureRequest struct {
|
|
Message string
|
|
SigningSubnetID string
|
|
QuorumPercentage uint64
|
|
Justification string
|
|
}
|
|
|
|
// AggregateSignatureResponse defines the response structure for signature aggregation
|
|
type AggregateSignatureResponse struct {
|
|
SignedMessage string
|
|
}
|
|
|
|
// SignMessage sends a request to the signature aggregator to sign a message.
|
|
// It returns the signed warp message or an error if the operation fails.
|
|
func SignMessage(logger logging.Logger, signatureAggregatorEndpoint string, message, justification, signingSubnetID string, quorumPercentage uint64) (*warp.Message, error) {
|
|
if quorumPercentage == 0 {
|
|
quorumPercentage = DefaultQuorumPercentage
|
|
} else if quorumPercentage > 100 {
|
|
return nil, fmt.Errorf("quorum percentage cannot be greater than 100")
|
|
}
|
|
request := AggregateSignatureRequest{
|
|
Message: message,
|
|
SigningSubnetID: signingSubnetID,
|
|
QuorumPercentage: quorumPercentage,
|
|
Justification: justification,
|
|
}
|
|
|
|
requestBody, err := json.Marshal(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
logger.Info("Calling signature aggregator",
|
|
zap.String("request", string(requestBody)),
|
|
)
|
|
|
|
client := &http.Client{
|
|
Timeout: SignatureAggregatorRequestTimeout,
|
|
}
|
|
|
|
var lastErr error
|
|
backoff := InitialBackoff
|
|
|
|
for attempt := 0; attempt < MaxRetries; attempt++ {
|
|
if attempt > 0 {
|
|
logger.Info("Retrying signature aggregator request",
|
|
zap.Int("attempt", attempt+1),
|
|
zap.Duration("backoff", backoff),
|
|
)
|
|
time.Sleep(backoff)
|
|
backoff *= 2 // Exponential backoff
|
|
}
|
|
|
|
resp, err := client.Post(
|
|
signatureAggregatorEndpoint,
|
|
"application/json",
|
|
bytes.NewBuffer(requestBody),
|
|
)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("failed to make request: %w", err)
|
|
logger.Error("Error making request to signature aggregator",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Read response body
|
|
body, err := io.ReadAll(resp.Body)
|
|
if closeErr := resp.Body.Close(); closeErr != nil {
|
|
logger.Warn("Failed to close response body",
|
|
zap.Error(closeErr),
|
|
)
|
|
}
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("failed to read response body: %w", err)
|
|
logger.Error("Error reading response body",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
logger.Info("Received response from signature aggregator",
|
|
zap.Int("status_code", resp.StatusCode),
|
|
zap.String("response", string(body)),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
lastErr = fmt.Errorf("signature aggregator returned non-200 status code: %d, body: %s", resp.StatusCode, string(body))
|
|
logger.Error("Received non-200 status code",
|
|
zap.Int("status_code", resp.StatusCode),
|
|
zap.String("body", string(body)),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
var response AggregateSignatureResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
lastErr = fmt.Errorf("failed to parse response: %w", err)
|
|
logger.Error("Error parsing response",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Decode the hex string
|
|
signedMessageBytes, err := hex.DecodeString(response.SignedMessage)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("error decoding hex: %w", err)
|
|
logger.Error("Error decoding hex",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Parse the signed message
|
|
signedMessage, err := warp.ParseMessage(signedMessageBytes)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("error parsing signed message: %w", err)
|
|
logger.Error("Error parsing signed message",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
return signedMessage, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed after %d attempts, last error: %w", MaxRetries, lastErr)
|
|
}
|