From e702b7faca4b65500fe069976247c84359b389e5 Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko Date: Fri, 6 Dec 2024 17:22:30 +0100 Subject: [PATCH 1/3] define finality violation error Signed-off-by: Dmytro Haidashenko --- pkg/services/health.go | 11 +++++++++++ pkg/types/contract_reader.go | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/pkg/services/health.go b/pkg/services/health.go index 7bdfb5113..7108e53b6 100644 --- a/pkg/services/health.go +++ b/pkg/services/health.go @@ -257,3 +257,14 @@ func (c *HealthChecker) IsHealthy() (healthy bool, errors map[string]error) { return } + +// ContainsError - returns true if report contains targetErr +func ContainsError(report map[string]error, targetErr error) bool { + for _, err := range report { + if errors.Is(err, targetErr) { + return true + } + } + + return false +} diff --git a/pkg/types/contract_reader.go b/pkg/types/contract_reader.go index 9cf6b0555..f75cee1aa 100644 --- a/pkg/types/contract_reader.go +++ b/pkg/types/contract_reader.go @@ -15,6 +15,7 @@ const ( ErrContractReaderConfigMissing = UnimplementedError("ContractReader entry missing from RelayConfig") ErrInternal = InternalError("internal error") ErrNotFound = NotFoundError("not found") + ErrFinalityViolation = InternalError("finality violation") ) // ContractReader defines essential read operations a chain should implement for reading contract values and events. @@ -65,6 +66,13 @@ type ContractReader interface { // QueryKey provides fetching chain agnostic events (Sequence) with general querying capability. QueryKey(ctx context.Context, contract BoundContract, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]Sequence, error) + // HealthReport returns a full health report of the callee including its dependencies. + // Keys are based on Name(), with nil values when healthy or errors otherwise. + // Use CopyHealth to collect reports from sub-services. + // This should run very fast, so avoid doing computation and instead prefer reporting pre-calculated state. + // On finality violation report must contain at least one ErrFinalityViolation. + HealthReport() map[string]error + mustEmbedUnimplementedContractReader() } From b1aaf3dda38517bc6bb00b0b71c0059f7c2a7f09 Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko Date: Fri, 6 Dec 2024 17:41:31 +0100 Subject: [PATCH 2/3] rename finality violation Signed-off-by: Dmytro Haidashenko --- pkg/types/contract_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/contract_reader.go b/pkg/types/contract_reader.go index f75cee1aa..b2c1c1dfb 100644 --- a/pkg/types/contract_reader.go +++ b/pkg/types/contract_reader.go @@ -15,7 +15,7 @@ const ( ErrContractReaderConfigMissing = UnimplementedError("ContractReader entry missing from RelayConfig") ErrInternal = InternalError("internal error") ErrNotFound = NotFoundError("not found") - ErrFinalityViolation = InternalError("finality violation") + ErrFinalityViolated = InternalError("finality violated") ) // ContractReader defines essential read operations a chain should implement for reading contract values and events. From 29727b81ca6c0c957ae9ad679830452cc1d429ca Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko Date: Wed, 11 Dec 2024 15:51:22 +0100 Subject: [PATCH 3/3] Test ContainsError Signed-off-by: Dmytro Haidashenko --- pkg/services/health_test.go | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 pkg/services/health_test.go diff --git a/pkg/services/health_test.go b/pkg/services/health_test.go new file mode 100644 index 000000000..325d2cf20 --- /dev/null +++ b/pkg/services/health_test.go @@ -0,0 +1,58 @@ +package services + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContainsError(t *testing.T) { + anError := errors.New("an error") + anotherError := errors.New("another error") + testCases := []struct { + Name string + Report map[string]error + Target error + ExpectedResult bool + }{ + { + Name: "nil map", + Report: nil, + Target: anError, + ExpectedResult: false, + }, + { + Name: "report contains service, but it's healthy", + Report: map[string]error{"service": nil}, + Target: anError, + ExpectedResult: false, + }, + { + Name: "service is not healthy, but it's not caused by target error", + Report: map[string]error{"service": anotherError}, + Target: anError, + ExpectedResult: false, + }, + { + Name: "service is not healthy and contains wrapper target", + Report: map[string]error{"service": fmt.Errorf("wrapped error: %w", anError)}, + Target: anError, + ExpectedResult: true, + }, + { + Name: "service is not healthy due to multiple errors including target", + Report: map[string]error{"service": errors.Join(anError, anotherError)}, + Target: anError, + ExpectedResult: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + actualResult := ContainsError(tc.Report, tc.Target) + assert.Equal(t, tc.ExpectedResult, actualResult) + }) + } +}