-
Notifications
You must be signed in to change notification settings - Fork 214
/
signature_getter.go
117 lines (104 loc) · 3.91 KB
/
signature_getter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package aggregator
import (
"context"
"fmt"
"time"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/subnet-evm/plugin/evm/message"
)
const (
initialRetryFetchSignatureDelay = 100 * time.Millisecond
maxRetryFetchSignatureDelay = 5 * time.Second
retryBackoffFactor = 2
)
var _ SignatureGetter = (*NetworkSignatureGetter)(nil)
// SignatureGetter defines the minimum network interface to perform signature aggregation
type SignatureGetter interface {
// GetSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage]
GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error)
}
type NetworkClient interface {
SendAppRequest(ctx context.Context, nodeID ids.NodeID, message []byte) ([]byte, error)
}
// NetworkSignatureGetter fetches warp signatures on behalf of the
// aggregator using VM App-Specific Messaging
type NetworkSignatureGetter struct {
Client NetworkClient
}
func NewSignatureGetter(client NetworkClient) *NetworkSignatureGetter {
return &NetworkSignatureGetter{
Client: client,
}
}
// GetSignature attempts to fetch a BLS Signature of [unsignedWarpMessage] from [nodeID] until it succeeds or receives an invalid response
//
// Note: this function will continue attempting to fetch the signature from [nodeID] until it receives an invalid value or [ctx] is cancelled.
// The caller is responsible to cancel [ctx] if it no longer needs to fetch this signature.
func (s *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *avalancheWarp.UnsignedMessage) (*bls.Signature, error) {
var signatureReqBytes []byte
parsedPayload, err := payload.Parse(unsignedWarpMessage.Payload)
if err != nil {
return nil, fmt.Errorf("failed to parse unsigned message payload: %w", err)
}
switch p := parsedPayload.(type) {
case *payload.AddressedCall:
signatureReq := message.MessageSignatureRequest{
MessageID: unsignedWarpMessage.ID(),
}
signatureReqBytes, err = message.RequestToBytes(message.Codec, signatureReq)
if err != nil {
return nil, fmt.Errorf("failed to marshal signature request: %w", err)
}
case *payload.Hash:
signatureReq := message.BlockSignatureRequest{
BlockID: p.Hash,
}
signatureReqBytes, err = message.RequestToBytes(message.Codec, signatureReq)
if err != nil {
return nil, fmt.Errorf("failed to marshal signature request: %w", err)
}
}
delay := initialRetryFetchSignatureDelay
timer := time.NewTimer(delay)
defer timer.Stop()
for {
signatureRes, err := s.Client.SendAppRequest(ctx, nodeID, signatureReqBytes)
// If the client fails to retrieve a response perform an exponential backoff.
// Note: it is up to the caller to ensure that [ctx] is eventually cancelled
if err != nil {
// Wait until the retry delay has elapsed before retrying.
if !timer.Stop() {
<-timer.C
}
timer.Reset(delay)
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-timer.C:
}
// Exponential backoff.
delay *= retryBackoffFactor
if delay > maxRetryFetchSignatureDelay {
delay = maxRetryFetchSignatureDelay
}
continue
}
var response message.SignatureResponse
if _, err := message.Codec.Unmarshal(signatureRes, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal signature res: %w", err)
}
if response.Signature == [bls.SignatureLen]byte{} {
return nil, fmt.Errorf("received empty signature response")
}
blsSignature, err := bls.SignatureFromBytes(response.Signature[:])
if err != nil {
return nil, fmt.Errorf("failed to parse signature from res: %w", err)
}
return blsSignature, nil
}
}