@@ -10,10 +10,12 @@ import (
1010 "sync"
1111 "time"
1212
13+ ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
1314 "google.golang.org/protobuf/proto"
1415
1516 ragep2ptypes "github.com/smartcontractkit/libocr/ragep2p/types"
1617
18+ "github.com/smartcontractkit/chainlink-common/keystore/corekeys/ocr2key"
1719 "github.com/smartcontractkit/chainlink-common/pkg/beholder"
1820 commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities"
1921 "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb"
@@ -33,16 +35,19 @@ type clientResponse struct {
3335}
3436
3537type ClientRequest struct {
36- id string
37- cancelFn context.CancelFunc
38- responseCh chan clientResponse
39- createdAt time.Time
40- responseIDCount map [[32 ]byte ]int
41- meteringResponses map [[32 ]byte ][]commoncap.MeteringNodeDetail
42- errorCount map [string ]int
43- totalErrorCount int
44- responseReceived map [p2ptypes.PeerID ]bool
45- lggr logger.Logger
38+ id string
39+ cancelFn context.CancelFunc
40+ responseCh chan clientResponse
41+ createdAt time.Time
42+ responseIDCount map [[32 ]byte ]int
43+ meteringResponses map [[32 ]byte ][]commoncap.MeteringNodeDetail
44+ errorCount map [string ]int
45+ totalErrorCount int
46+ responseReceived map [p2ptypes.PeerID ]bool
47+ lggr logger.Logger
48+ ocr3Configs map [string ]ocrtypes.ContractConfig
49+ workflowExecutionID string
50+ referenceID string
4651
4752 requiredIdenticalResponses int
4853 remoteNodeCount int
@@ -58,6 +63,7 @@ type ClientRequest struct {
5863func NewClientExecuteRequest (ctx context.Context , lggr logger.Logger , req commoncap.CapabilityRequest ,
5964 remoteCapabilityInfo commoncap.CapabilityInfo , localDonInfo commoncap.DON , dispatcher types.Dispatcher ,
6065 requestTimeout time.Duration , transmissionConfig * transmission.TransmissionConfig , capMethodName string ,
66+ ocr3Configs map [string ]ocrtypes.ContractConfig ,
6167) (* ClientRequest , error ) {
6268 rawRequest , err := proto.MarshalOptions {Deterministic : true }.Marshal (pb .CapabilityRequestToProto (req ))
6369 if err != nil {
@@ -87,14 +93,15 @@ func NewClientExecuteRequest(ctx context.Context, lggr logger.Logger, req common
8793 }
8894
8995 lggr = logger .With (lggr , "requestId" , requestID ) // cap ID and method name included in the parent logger
90- return newClientRequest (ctx , lggr , requestID , remoteCapabilityInfo , localDonInfo , dispatcher , requestTimeout , tc , types .MethodExecute , rawRequest , workflowExecutionID , req .Metadata .ReferenceID , capMethodName )
96+ return newClientRequest (ctx , lggr , requestID , remoteCapabilityInfo , localDonInfo , dispatcher , requestTimeout , tc , types .MethodExecute , rawRequest , workflowExecutionID , req .Metadata .ReferenceID , capMethodName , ocr3Configs )
9197}
9298
9399var defaultDelayMargin = 10 * time .Second
94100
95101func newClientRequest (ctx context.Context , lggr logger.Logger , requestID string , remoteCapabilityInfo commoncap.CapabilityInfo ,
96102 localDonInfo commoncap.DON , dispatcher types.Dispatcher , requestTimeout time.Duration ,
97103 tc transmission.TransmissionConfig , methodType string , rawRequest []byte , workflowExecutionID string , stepRef string , capMethodName string ,
104+ ocr3Configs map [string ]ocrtypes.ContractConfig ,
98105) (* ClientRequest , error ) {
99106 remoteCapabilityDonInfo := remoteCapabilityInfo .DON
100107 if remoteCapabilityDonInfo == nil {
@@ -200,6 +207,9 @@ func newClientRequest(ctx context.Context, lggr logger.Logger, requestID string,
200207 responseCh : make (chan clientResponse , 1 ),
201208 wg : & wg ,
202209 lggr : lggr ,
210+ ocr3Configs : ocr3Configs ,
211+ workflowExecutionID : workflowExecutionID ,
212+ referenceID : stepRef ,
203213 }, nil
204214}
205215
@@ -301,6 +311,34 @@ func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) err
301311 c .responseReceived [sender ] = true
302312
303313 if msg .Error == types .Error_OK {
314+ resp , err := pb .UnmarshalCapabilityResponse (msg .Payload )
315+ if err != nil {
316+ return fmt .Errorf ("failed to unmarshal capability response: %w" , err )
317+ }
318+
319+ if resp .Metadata .OCRAttestation != nil {
320+ rpt , err := extractMeteringFromMetadata (sender , resp .Metadata )
321+ if err != nil {
322+ return fmt .Errorf ("failed to extract metering detail from metadata: %w" , err )
323+ }
324+ // Since signatures are provided switch to OCR based validation. It's enough to get 1 response with F+1 signatures
325+ // to be confident that the response is honest.
326+ err = c .verifyAttestation (resp , rpt )
327+ if err != nil {
328+ c .lggr .Errorw ("failed to verify capability response OCR attestation" , "peer" , sender , "err" , err , "requestID" , c .id , "msgPayload" , hex .EncodeToString (msg .Payload ))
329+ return fmt .Errorf ("failed to verify capability response OCR attestation: %w" , err )
330+ }
331+
332+ var payload []byte
333+ payload , err = c .encodePayloadWithMetadata (msg , commoncap.ResponseMetadata {Metering : []commoncap.MeteringNodeDetail {rpt }})
334+ if err != nil {
335+ return fmt .Errorf ("failed to encode payload with metadata: %w" , err )
336+ }
337+
338+ c .sendResponse (clientResponse {Result : payload })
339+ return nil
340+ }
341+
304342 // metering reports per node are aggregated into a single array of values. for any single node message, the
305343 // metering values are extracted from the CapabilityResponse, added to an array, and the CapabilityResponse
306344 // is marshalled without the metering value to get the hash. each node could have a different metering value
@@ -317,13 +355,11 @@ func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) err
317355 nodeReports = make ([]commoncap.MeteringNodeDetail , 0 )
318356 }
319357
320- if len (metadata .Metering ) == 1 {
321- rpt := metadata .Metering [0 ]
322- rpt .Peer2PeerID = sender .String ()
323-
324- nodeReports = append (nodeReports , rpt )
358+ rpt , err := extractMeteringFromMetadata (sender , metadata )
359+ if err != nil {
360+ lggr .Warnw ("invalid metering detail" , "err" , err )
325361 } else {
326- lggr . Warnw ( "node metering detail did not contain exactly 1 record" , "records" , len ( metadata . Metering ) )
362+ nodeReports = append ( nodeReports , rpt )
327363 }
328364
329365 c .responseIDCount [responseID ]++
@@ -359,6 +395,53 @@ func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) err
359395 return nil
360396}
361397
398+ func extractMeteringFromMetadata (sender p2ptypes.PeerID , metadata commoncap.ResponseMetadata ) (commoncap.MeteringNodeDetail , error ) {
399+ if len (metadata .Metering ) != 1 {
400+ return commoncap.MeteringNodeDetail {}, fmt .Errorf ("unexpected number of metering records received from pperi %s: got %d, want 1" , sender , len (metadata .Metering ))
401+ }
402+
403+ rpt := metadata .Metering [0 ]
404+ rpt .Peer2PeerID = sender .String ()
405+ return rpt , nil
406+ }
407+
408+ func (c * ClientRequest ) verifyAttestation (resp commoncap.CapabilityResponse , metering commoncap.MeteringNodeDetail ) error {
409+ if c .ocr3Configs == nil {
410+ return errors .New ("OCR3 configs not provided, cannot verify signatures" )
411+ }
412+
413+ cfg , ok := c .ocr3Configs [pb .OCR3ConfigDefaultKey ]
414+ if ! ok {
415+ return fmt .Errorf ("OCR3 config with key %s not found" , pb .OCR3ConfigDefaultKey )
416+ }
417+
418+ attestation := resp .Metadata .OCRAttestation
419+ if len (attestation .Sigs ) < int (cfg .F )+ 1 {
420+ return fmt .Errorf ("not enough signatures: got %d, need at least %d" , len (attestation .Sigs ), cfg .F + 1 )
421+ }
422+
423+ reportData := commoncap .ResponseToReportData (c .workflowExecutionID , c .referenceID , resp .Payload .Value , metering .SpendUnit , metering .SpendValue )
424+ sigData := ocr2key .ReportToSigData3 (attestation .ConfigDigest , attestation .SequenceNumber , reportData )
425+ signed := make ([]bool , len (cfg .Signers ))
426+ for _ , sig := range attestation .Sigs {
427+ if int (sig .Signer ) > len (cfg .Signers ) {
428+ return fmt .Errorf ("invalid signer index: %d" , sig .Signer )
429+ }
430+
431+ if signed [sig .Signer ] {
432+ return fmt .Errorf ("duplicate signature from signer index: %d" , sig .Signer )
433+ }
434+
435+ if ! ocr2key .EvmVerifyBlob (cfg .Signers [sig .Signer ], sigData , sig .Signature ) {
436+ return fmt .Errorf ("invalid signature from signer index: %d" , sig .Signer )
437+ }
438+
439+ signed [sig .Signer ] = true
440+ }
441+
442+ return nil
443+ }
444+
362445func (c * ClientRequest ) sendResponse (response clientResponse ) {
363446 c .responseCh <- response
364447 close (c .responseCh )
0 commit comments