From d944f2fb8744f41f1f5693c9d94cb2b4bb5869c9 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Mon, 3 Feb 2025 18:24:53 +0400 Subject: [PATCH 1/2] Introducing ExtendedBatchGetLatestValuesGraceful --- pkg/contractreader/extended.go | 61 ++++++++++ pkg/contractreader/extended_unit_test.go | 146 +++++++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/pkg/contractreader/extended.go b/pkg/contractreader/extended.go index 0e9390a82..dc02f2940 100644 --- a/pkg/contractreader/extended.go +++ b/pkg/contractreader/extended.go @@ -84,6 +84,21 @@ type Extended interface { ctx context.Context, request ExtendedBatchGetLatestValuesRequest, ) (types.BatchGetLatestValuesResult, error) + + // ExtendedBatchGetLatestValuesGraceful performs the same operations as ExtendedBatchGetLatestValues + // but handles ErrNoBindings gracefully by: + // 1. Skipping contracts with no bindings instead of returning an error + // 2. Returning partial results for contracts that do have bindings + // 3. Including information about which contracts were skipped due to no bindings + ExtendedBatchGetLatestValuesGraceful( + ctx context.Context, + request ExtendedBatchGetLatestValuesRequest, + ) (BatchGetLatestValuesGracefulResult, error) +} + +type BatchGetLatestValuesGracefulResult struct { + Results types.BatchGetLatestValuesResult + SkippedNoBinds []string // List of contract names that were skipped due to no bindings } type ExtendedBatchGetLatestValuesRequest map[string]types.ContractBatch @@ -292,6 +307,52 @@ func (e *extendedContractReader) Bind(ctx context.Context, allBindings []types.B return nil } +func (e *extendedContractReader) ExtendedBatchGetLatestValuesGraceful( + ctx context.Context, + request ExtendedBatchGetLatestValuesRequest, +) (BatchGetLatestValuesGracefulResult, error) { + // Convert the request from contract names to BoundContracts + convertedRequest := make(types.BatchGetLatestValuesRequest) + var skippedContracts []string + + for contractName, batch := range request { + // Get the binding for this contract name + binding, err := e.getOneBinding(contractName) + if err != nil { + if errors.Is(err, ErrNoBindings) { + // Track skipped contracts but continue processing + skippedContracts = append(skippedContracts, contractName) + continue + } + // Return error for any other binding issues + return BatchGetLatestValuesGracefulResult{}, fmt.Errorf( + "BatchGetLatestValuesGraceful: failed to get binding for contract %s: %w", contractName, err) + } + + // Use the resolved binding for the request + convertedRequest[binding.Binding] = batch + } + + // If we have no valid bindings after filtering, return empty result + if len(convertedRequest) == 0 { + return BatchGetLatestValuesGracefulResult{ + Results: make(types.BatchGetLatestValuesResult), + SkippedNoBinds: skippedContracts, + }, nil + } + + // Call the underlying BatchGetLatestValues with the converted request + results, err := e.BatchGetLatestValues(ctx, convertedRequest) + if err != nil { + return BatchGetLatestValuesGracefulResult{}, err + } + + return BatchGetLatestValuesGracefulResult{ + Results: results, + SkippedNoBinds: skippedContracts, + }, nil +} + func (e *extendedContractReader) GetBindings(contractName string) []ExtendedBoundContract { e.mu.RLock() defer e.mu.RUnlock() diff --git a/pkg/contractreader/extended_unit_test.go b/pkg/contractreader/extended_unit_test.go index a30014260..2db501024 100644 --- a/pkg/contractreader/extended_unit_test.go +++ b/pkg/contractreader/extended_unit_test.go @@ -199,6 +199,152 @@ func TestExtendedBatchGetLatestValues(t *testing.T) { } } +func TestExtendedBatchGetLatestValuesGraceful(t *testing.T) { + tests := []struct { + name string + bindings map[string][]ExtendedBoundContract + request ExtendedBatchGetLatestValuesRequest + mockResponse types.BatchGetLatestValuesResult + expectedError error + expectedResult BatchGetLatestValuesGracefulResult + }{ + { + name: "all contracts valid", + bindings: map[string][]ExtendedBoundContract{ + "contract1": { + {Binding: types.BoundContract{Name: "contract1", Address: "0x123"}}, + }, + "contract2": { + {Binding: types.BoundContract{Name: "contract2", Address: "0x456"}}, + }, + }, + request: ExtendedBatchGetLatestValuesRequest{ + "contract1": { + {ReadName: "read1", Params: "params1", ReturnVal: "return1"}, + }, + "contract2": { + {ReadName: "read2", Params: "params2", ReturnVal: "return2"}, + }, + }, + mockResponse: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: "contract1", Address: "0x123"}: { + {ReadName: "read1"}, + }, + types.BoundContract{Name: "contract2", Address: "0x456"}: { + {ReadName: "read2"}, + }, + }, + expectedError: nil, + expectedResult: BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: "contract1", Address: "0x123"}: { + {ReadName: "read1"}, + }, + types.BoundContract{Name: "contract2", Address: "0x456"}: { + {ReadName: "read2"}, + }, + }, + SkippedNoBinds: nil, + }, + }, + { + name: "some contracts skipped", + bindings: map[string][]ExtendedBoundContract{ + "contract1": { + {Binding: types.BoundContract{Name: "contract1", Address: "0x123"}}, + }, + }, + request: ExtendedBatchGetLatestValuesRequest{ + "contract1": { + {ReadName: "read1", Params: "params1", ReturnVal: "return1"}, + }, + "nonexistent": { + {ReadName: "read2", Params: "params2", ReturnVal: "return2"}, + }, + }, + mockResponse: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: "contract1", Address: "0x123"}: { + {ReadName: "read1"}, + }, + }, + expectedError: nil, + expectedResult: BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: "contract1", Address: "0x123"}: { + {ReadName: "read1"}, + }, + }, + SkippedNoBinds: []string{"nonexistent"}, + }, + }, + { + name: "all contracts skipped", + bindings: map[string][]ExtendedBoundContract{ + "contract1": { + {Binding: types.BoundContract{Name: "contract1", Address: "0x123"}}, + }, + }, + request: ExtendedBatchGetLatestValuesRequest{ + "nonexistent1": { + {ReadName: "read1", Params: "params1", ReturnVal: "return1"}, + }, + "nonexistent2": { + {ReadName: "read2", Params: "params2", ReturnVal: "return2"}, + }, + }, + mockResponse: nil, + expectedError: nil, + expectedResult: BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{}, + SkippedNoBinds: []string{"nonexistent1", "nonexistent2"}, + }, + }, + { + name: "error on too many bindings", + bindings: map[string][]ExtendedBoundContract{ + "contract1": { + {Binding: types.BoundContract{Name: "contract1", Address: "0x123"}}, + {Binding: types.BoundContract{Name: "contract1", Address: "0x456"}}, + }, + }, + request: ExtendedBatchGetLatestValuesRequest{ + "contract1": { + {ReadName: "read1", Params: "params1", ReturnVal: "return1"}, + }, + }, + mockResponse: nil, + expectedError: ErrTooManyBindings, + expectedResult: BatchGetLatestValuesGracefulResult{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockReader := &mockContractReader{ + BatchGetLatestValuesResponse: tt.mockResponse, + } + + extendedReader := &extendedContractReader{ + reader: mockReader, + contractBindingsByName: tt.bindings, + mu: &sync.RWMutex{}, + } + + result, err := extendedReader.ExtendedBatchGetLatestValuesGraceful(context.Background(), tt.request) + + if tt.expectedError != nil { + assert.ErrorIs(t, err, tt.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedResult.Results, result.Results) + assert.ElementsMatch(t, tt.expectedResult.SkippedNoBinds, result.SkippedNoBinds) + } + }) + } +} + // mockContractReader implements ContractReaderFacade for testing type mockContractReader struct { ContractReaderFacade From 71e29dbd226f547ba38289a2043246b60e65f81e Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Mon, 3 Feb 2025 18:28:10 +0400 Subject: [PATCH 2/2] lint --- mocks/pkg/contractreader/extended.go | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/mocks/pkg/contractreader/extended.go b/mocks/pkg/contractreader/extended.go index 2b748854c..8db3ec991 100644 --- a/mocks/pkg/contractreader/extended.go +++ b/mocks/pkg/contractreader/extended.go @@ -193,6 +193,63 @@ func (_c *MockExtended_ExtendedBatchGetLatestValues_Call) RunAndReturn(run func( return _c } +// ExtendedBatchGetLatestValuesGraceful provides a mock function with given fields: ctx, request +func (_m *MockExtended) ExtendedBatchGetLatestValuesGraceful(ctx context.Context, request contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for ExtendedBatchGetLatestValuesGraceful") + } + + var r0 contractreader.BatchGetLatestValuesGracefulResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) contractreader.BatchGetLatestValuesGracefulResult); ok { + r0 = rf(ctx, request) + } else { + r0 = ret.Get(0).(contractreader.BatchGetLatestValuesGracefulResult) + } + + if rf, ok := ret.Get(1).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockExtended_ExtendedBatchGetLatestValuesGraceful_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExtendedBatchGetLatestValuesGraceful' +type MockExtended_ExtendedBatchGetLatestValuesGraceful_Call struct { + *mock.Call +} + +// ExtendedBatchGetLatestValuesGraceful is a helper method to define mock.On call +// - ctx context.Context +// - request contractreader.ExtendedBatchGetLatestValuesRequest +func (_e *MockExtended_Expecter) ExtendedBatchGetLatestValuesGraceful(ctx interface{}, request interface{}) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + return &MockExtended_ExtendedBatchGetLatestValuesGraceful_Call{Call: _e.mock.On("ExtendedBatchGetLatestValuesGraceful", ctx, request)} +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) Run(run func(ctx context.Context, request contractreader.ExtendedBatchGetLatestValuesRequest)) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(contractreader.ExtendedBatchGetLatestValuesRequest)) + }) + return _c +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) Return(_a0 contractreader.BatchGetLatestValuesGracefulResult, _a1 error) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) RunAndReturn(run func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error)) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Return(run) + return _c +} + // ExtendedGetLatestValue provides a mock function with given fields: ctx, contractName, methodName, confidenceLevel, params, returnVal func (_m *MockExtended) ExtendedGetLatestValue(ctx context.Context, contractName string, methodName string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { ret := _m.Called(ctx, contractName, methodName, confidenceLevel, params, returnVal)