diff --git a/offchainreporting2/reportingplugin/median/median.go b/offchainreporting2/reportingplugin/median/median.go index 473f00a..670da43 100644 --- a/offchainreporting2/reportingplugin/median/median.go +++ b/offchainreporting2/reportingplugin/median/median.go @@ -123,6 +123,13 @@ type OffchainConfig struct { // NumericalMedianFactory has the same effect; if either flag is true, // the condition applies. AcceptAfterFullTransmissionScheduleElapsed bool + // If BuildReportDespiteContractReadError is true and MedianContract reads + // return an error, Report will return that a report should be built. "If + // in doubt, report!" + // + // Be careful setting this. It can cause increased transaction load and + // costs. + BuildReportDespiteContractReadError bool } func DecodeOffchainConfig(b []byte) (OffchainConfig, error) { @@ -144,6 +151,7 @@ func DecodeOffchainConfig(b []byte) (OffchainConfig, error) { time.Duration(configProto.GetDeltaCNanoseconds()), configProto.GetTransmitDespiteContractReadError(), configProto.GetAcceptAfterFullTransmissionScheduleElapsed(), + configProto.GetBuildReportDespiteContractReadError(), }, nil } @@ -161,6 +169,7 @@ func (c OffchainConfig) Encode() []byte { uint64(c.DeltaC), c.TransmitDespiteContractReadError, c.AcceptAfterFullTransmissionScheduleElapsed, + c.BuildReportDespiteContractReadError, } result, err := proto.Marshal(&configProto) if err != nil { @@ -611,11 +620,22 @@ func (nm *numericalMedian) shouldReport(ctx context.Context, repts types.ReportT }) subs.Wait() + buildDespiteContractReadError := false if err := errors.Join(resultTransmissionDetails.err, resultRoundRequested.err); err != nil { - return false, fmt.Errorf("error during LatestTransmissionDetails/LatestRoundRequested: %w", err) + if nm.offchainConfig.BuildReportDespiteContractReadError { + buildDespiteContractReadError = true + nm.logger.Error("error during LatestTransmissionDetails/LatestRoundRequested", commontypes.LogFields{ + "error": err, + "timestamp": repts, + }) + // We intentionally do not return an error here. + } else { + return false, fmt.Errorf("error during LatestTransmissionDetails/LatestRoundRequested: %w", err) + } } - if resultTransmissionDetails.latestAnswer == nil { + // On a contract read error, latestAnswer is legitimately nil. + if !buildDespiteContractReadError && resultTransmissionDetails.latestAnswer == nil { return false, fmt.Errorf("nil latestAnswer was returned by LatestTransmissionDetails. This should never happen") } @@ -636,6 +656,14 @@ func (nm *numericalMedian) shouldReport(ctx context.Context, repts types.ReportT return false, nil } + // The branches below depend on the contract read, which errored. + if buildDespiteContractReadError { + nm.logger.Info("shouldReport: yes, because of BuildReportDespiteContractReadError", commontypes.LogFields{ + "result": true, + }) + return true, nil + } + initialRound := // Is this the first round for this configuration? resultTransmissionDetails.configDigest == repts.ConfigDigest && resultTransmissionDetails.epoch == 0 && diff --git a/offchainreporting2/reportingplugin/median/offchainreporting2_median_config.pb.go b/offchainreporting2/reportingplugin/median/offchainreporting2_median_config.pb.go index a4025fa..c6a3b35 100644 --- a/offchainreporting2/reportingplugin/median/offchainreporting2_median_config.pb.go +++ b/offchainreporting2/reportingplugin/median/offchainreporting2_median_config.pb.go @@ -32,6 +32,7 @@ type NumericalMedianConfigProto struct { DeltaCNanoseconds uint64 `protobuf:"varint,5,opt,name=delta_c_nanoseconds,json=deltaCNanoseconds,proto3" json:"delta_c_nanoseconds,omitempty"` TransmitDespiteContractReadError bool `protobuf:"varint,6,opt,name=transmit_despite_contract_read_error,json=transmitDespiteContractReadError,proto3" json:"transmit_despite_contract_read_error,omitempty"` AcceptAfterFullTransmissionScheduleElapsed bool `protobuf:"varint,7,opt,name=accept_after_full_transmission_schedule_elapsed,json=acceptAfterFullTransmissionScheduleElapsed,proto3" json:"accept_after_full_transmission_schedule_elapsed,omitempty"` + BuildReportDespiteContractReadError bool `protobuf:"varint,8,opt,name=build_report_despite_contract_read_error,json=buildReportDespiteContractReadError,proto3" json:"build_report_despite_contract_read_error,omitempty"` } func (x *NumericalMedianConfigProto) Reset() { @@ -115,13 +116,20 @@ func (x *NumericalMedianConfigProto) GetAcceptAfterFullTransmissionScheduleElaps return false } +func (x *NumericalMedianConfigProto) GetBuildReportDespiteContractReadError() bool { + if x != nil { + return x.BuildReportDespiteContractReadError + } + return false +} + var File_offchainreporting2_median_config_proto protoreflect.FileDescriptor var file_offchainreporting2_median_config_proto_rawDesc = []byte{ 0x0a, 0x26, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x32, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x32, 0x22, 0xbd, 0x03, 0x0a, + 0x69, 0x6e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x32, 0x22, 0x94, 0x04, 0x0a, 0x1a, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x69, @@ -149,8 +157,14 @@ var file_offchainreporting2_median_config_proto_rawDesc = []byte{ 0x6c, 0x65, 0x5f, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, - 0x65, 0x64, 0x75, 0x6c, 0x65, 0x45, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x42, 0x0a, 0x5a, 0x08, - 0x2e, 0x3b, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x45, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x28, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, + 0x70, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x72, 0x65, + 0x61, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x65, 0x73, 0x70, 0x69, + 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x65, 0x61, 0x64, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/offchainreporting2plus/confighelper/confighelper.go b/offchainreporting2plus/confighelper/confighelper.go index b422430..a57ee71 100644 --- a/offchainreporting2plus/confighelper/confighelper.go +++ b/offchainreporting2plus/confighelper/confighelper.go @@ -144,6 +144,7 @@ func ContractSetConfigArgsForEthereumIntegrationTest( 0, false, false, + false, }.Encode(), util.PointerTo(50 * time.Millisecond), 50 * time.Millisecond,