From d962cfba49692e79d42a5e0834ecb1702340891c Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Wed, 7 Aug 2024 16:05:25 +0300 Subject: [PATCH 1/6] Fixed bug with option --- engine/access/rest/middleware/common_query_params.go | 6 +++--- .../rest/middleware/common_query_params_test.go | 2 +- engine/access/rest/util/select_filter.go | 12 ++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/engine/access/rest/middleware/common_query_params.go b/engine/access/rest/middleware/common_query_params.go index b81c828d91a..cfd9bf1bfde 100644 --- a/engine/access/rest/middleware/common_query_params.go +++ b/engine/access/rest/middleware/common_query_params.go @@ -8,7 +8,7 @@ import ( ) const ExpandQueryParam = "expand" -const selectQueryParam = "select" +const SelectQueryParam = "select" // commonQueryParamMiddleware generates a Middleware function that extracts the given query parameter from the request // and adds it to the request context as a key value pair with the key as the query param name. @@ -37,7 +37,7 @@ func QueryExpandable() mux.MiddlewareFunc { // QuerySelect middleware extracts out the 'select' query param field if present in the request func QuerySelect() mux.MiddlewareFunc { - return commonQueryParamMiddleware(selectQueryParam) + return commonQueryParamMiddleware(SelectQueryParam) } func getField(req *http.Request, key string) ([]string, bool) { @@ -54,5 +54,5 @@ func GetFieldsToExpand(req *http.Request) ([]string, bool) { } func GetFieldsToSelect(req *http.Request) ([]string, bool) { - return getField(req, selectQueryParam) + return getField(req, SelectQueryParam) } diff --git a/engine/access/rest/middleware/common_query_params_test.go b/engine/access/rest/middleware/common_query_params_test.go index c40a70e0783..a04882f7613 100644 --- a/engine/access/rest/middleware/common_query_params_test.go +++ b/engine/access/rest/middleware/common_query_params_test.go @@ -35,7 +35,7 @@ func TestCommonQueryParamMiddlewares(t *testing.T) { query.Add(ExpandQueryParam, strings.Join(expandList, ",")) } if len(selectList) > 0 { - query.Add(selectQueryParam, strings.Join(selectList, ",")) + query.Add(SelectQueryParam, strings.Join(selectList, ",")) } req.URL.RawQuery = query.Encode() diff --git a/engine/access/rest/util/select_filter.go b/engine/access/rest/util/select_filter.go index f63e5fa6814..4f7172a7ff5 100644 --- a/engine/access/rest/util/select_filter.go +++ b/engine/access/rest/util/select_filter.go @@ -25,10 +25,10 @@ func SelectFilter(object interface{}, selectKeys []string) (interface{}, error) } filter := sliceToMap(selectKeys) - switch itemAsType := (*outputMap).(type) { case []interface{}: - filterSlice(itemAsType, "", filter) + filteredSlice, _ := filterSlice(itemAsType, "", filter) + *outputMap = filteredSlice case map[string]interface{}: filterObject(itemAsType, "", filter) } @@ -40,6 +40,10 @@ func SelectFilter(object interface{}, selectKeys []string) (interface{}, error) func filterObject(jsonStruct map[string]interface{}, prefix string, filterMap map[string]bool) { for key, item := range jsonStruct { newPrefix := jsonPath(prefix, key) + // if the leaf object is the key, go to others + if filterMap[newPrefix] { + continue + } switch itemAsType := item.(type) { case []interface{}: // if the value of a key is a list, call filterSlice @@ -87,7 +91,7 @@ func filterSlice(jsonSlice []interface{}, prefix string, filterMap map[string]bo if len(itemAsType) == 0 { // since all elements of the slice are the same, if one sub-slice has been filtered out, we can safely // remove all sub-slices and return (instead of iterating all slice elements) - return nil, sliceType + return make([]interface{}, 0), sliceType } case map[string]interface{}: // if the slice has structs as elements, call filterObject @@ -96,7 +100,7 @@ func filterSlice(jsonSlice []interface{}, prefix string, filterMap map[string]bo if len(itemAsType) == 0 { // since all elements of the slice are the same, if one struct element has been filtered out, we can safely // remove all struct elements and return (instead of iterating all slice elements) - return nil, false + return make([]interface{}, 0), false } default: // if the elements are neither a slice nor a struct, then return the slice and true to indicate the slice has From 320071df02b6bdc8dfce0b1d9aa90806089f28f6 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Wed, 7 Aug 2024 16:06:09 +0300 Subject: [PATCH 2/6] Added more tests, refactored blocks tests --- engine/access/rest/routes/blocks_test.go | 178 ++++++++++++------ engine/access/rest/util/select_filter_test.go | 12 ++ 2 files changed, 137 insertions(+), 53 deletions(-) diff --git a/engine/access/rest/routes/blocks_test.go b/engine/access/rest/routes/blocks_test.go index 7facf06d423..94962e87bb6 100644 --- a/engine/access/rest/routes/blocks_test.go +++ b/engine/access/rest/routes/blocks_test.go @@ -44,6 +44,10 @@ func prepareTestVectors(t *testing.T, singleBlockCondensedResponse := expectedBlockResponsesExpanded(blocks[:1], executionResults[:1], false, flow.BlockStatusUnknown) multipleBlockCondensedResponse := expectedBlockResponsesExpanded(blocks, executionResults, false, flow.BlockStatusUnknown) + multipleBlockHeaderWithHeaderSelectedResponse := expectedBlockResponsesSelected(blocks, executionResults, flow.BlockStatusUnknown, []string{"header"}) + multipleBlockHeaderWithHeaderAndStatusSelectedResponse := expectedBlockResponsesSelected(blocks, executionResults, flow.BlockStatusUnknown, []string{"header", "block_status"}) + multipleBlockHeaderWithUnknownSelectedResponse := expectedBlockResponsesSelected(blocks, executionResults, flow.BlockStatusUnknown, []string{"unknown"}) + invalidID := unittest.IdentifierFixture().String() invalidHeight := fmt.Sprintf("%d", blkCnt+1) @@ -112,7 +116,7 @@ func prepareTestVectors(t *testing.T, }, { description: "Get block by both heights and start and end height", - request: requestURL(t, nil, heights[len(heights)-1], heights[0], true, heights...), + request: requestURL(t, nil, heights[len(heights)-1], heights[0], true, []string{}, heights...), expectedStatus: http.StatusBadRequest, expectedResponse: `{"code":400, "message": "can only provide either heights or start and end height range"}`, }, @@ -134,6 +138,24 @@ func prepareTestVectors(t *testing.T, expectedStatus: http.StatusBadRequest, expectedResponse: fmt.Sprintf(`{"code":400, "message": "at most %d IDs can be requested at a time"}`, request.MaxBlockRequestHeightRange), }, + { + description: "Get multiple blocks by IDs with header selected", + request: getByIDsCondensedWithSelectURL(t, blockIDs, []string{"header"}), + expectedStatus: http.StatusOK, + expectedResponse: multipleBlockHeaderWithHeaderSelectedResponse, + }, + { + description: "Get multiple blocks by IDs with header and block_status selected", + request: getByIDsCondensedWithSelectURL(t, blockIDs, []string{"header", "block_status"}), + expectedStatus: http.StatusOK, + expectedResponse: multipleBlockHeaderWithHeaderAndStatusSelectedResponse, + }, + { + description: "Get multiple blocks by IDs with unknown select object", + request: getByIDsCondensedWithSelectURL(t, blockIDs, []string{"unknown"}), + expectedStatus: http.StatusOK, + expectedResponse: multipleBlockHeaderWithUnknownSelectedResponse, + }, } return testVectors } @@ -154,7 +176,7 @@ func TestAccessGetBlocks(t *testing.T) { } } -func requestURL(t *testing.T, ids []string, start string, end string, expandResponse bool, heights ...string) *http.Request { +func requestURL(t *testing.T, ids []string, start string, end string, expandResponse bool, selectedFields []string, heights ...string) *http.Request { u, _ := url.Parse("/v1/blocks") q := u.Query() @@ -172,6 +194,11 @@ func requestURL(t *testing.T, ids []string, start string, end string, expandResp q.Add(heightQueryParam, heightsStr) } + if len(selectedFields) > 0 { + selectedStr := strings.Join(selectedFields, ",") + q.Add(middleware.SelectQueryParam, selectedStr) + } + if expandResponse { var expands []string expands = append(expands, ExpandableFieldPayload) @@ -188,19 +215,23 @@ func requestURL(t *testing.T, ids []string, start string, end string, expandResp } func getByIDsExpandedURL(t *testing.T, ids []string) *http.Request { - return requestURL(t, ids, "", "", true) + return requestURL(t, ids, "", "", true, []string{}) } func getByHeightsExpandedURL(t *testing.T, heights ...string) *http.Request { - return requestURL(t, nil, "", "", true, heights...) + return requestURL(t, nil, "", "", true, []string{}, heights...) } func getByStartEndHeightExpandedURL(t *testing.T, start, end string) *http.Request { - return requestURL(t, nil, start, end, true) + return requestURL(t, nil, start, end, true, []string{}) } func getByIDsCondensedURL(t *testing.T, ids []string) *http.Request { - return requestURL(t, ids, "", "", false) + return requestURL(t, ids, "", "", false, []string{}) +} + +func getByIDsCondensedWithSelectURL(t *testing.T, ids []string, selectedFields []string) *http.Request { + return requestURL(t, ids, "", "", false, selectedFields) } func generateMocks(backend *mock.API, count int) ([]string, []string, []*flow.Block, []*flow.ExecutionResult) { @@ -232,65 +263,106 @@ func generateMocks(backend *mock.API, count int) ([]string, []string, []*flow.Bl return blockIDs, heights, blocks, executionResults } -func expectedBlockResponsesExpanded(blocks []*flow.Block, execResult []*flow.ExecutionResult, expanded bool, status flow.BlockStatus) string { - blockResponses := make([]string, len(blocks)) +func expectedBlockResponsesExpanded( + blocks []*flow.Block, + execResult []*flow.ExecutionResult, + expanded bool, + status flow.BlockStatus, + selectedFields ...string, +) string { + blockResponses := make([]string, 0) for i, b := range blocks { - blockResponses[i] = expectedBlockResponse(b, execResult[i], expanded, status) + response := expectedBlockResponse(b, execResult[i], expanded, status, selectedFields...) + if response != "" { + blockResponses = append(blockResponses, response) + } } return fmt.Sprintf("[%s]", strings.Join(blockResponses, ",")) } -func expectedBlockResponse(block *flow.Block, execResult *flow.ExecutionResult, expanded bool, status flow.BlockStatus) string { +func expectedBlockResponsesSelected( + blocks []*flow.Block, + execResult []*flow.ExecutionResult, + status flow.BlockStatus, + selectedFields []string, +) string { + return expectedBlockResponsesExpanded(blocks, execResult, false, status, selectedFields...) +} + +func expectedBlockResponse( + block *flow.Block, + execResult *flow.ExecutionResult, + expanded bool, + status flow.BlockStatus, + selectedFields ...string, +) string { id := block.ID().String() execResultID := execResult.ID().String() - execLink := fmt.Sprintf("/v1/execution_results/%s", execResultID) blockLink := fmt.Sprintf("/v1/blocks/%s", id) payloadLink := fmt.Sprintf("/v1/blocks/%s/payload", id) - blockStatus := status.String() - + execLink := fmt.Sprintf("/v1/execution_results/%s", execResultID) timestamp := block.Header.Timestamp.Format(time.RFC3339Nano) + header := fmt.Sprintf(`"header": { + "id": "%s", + "parent_id": "%s", + "height": "%d", + "timestamp": "%s", + "parent_voter_signature": "%s" + }`, id, block.Header.ParentID.String(), block.Header.Height, timestamp, util.ToBase64(block.Header.ParentVoterSigData)) + + links := fmt.Sprintf(`"_links": { + "_self": "%s" + }`, blockLink) + + expandable := fmt.Sprintf(`"_expandable": { + "payload": "%s", + "execution_result": "%s" + }`, payloadLink, execLink) + + blockStatus := fmt.Sprintf(`"block_status": "%s"`, status.String()) + payload := `"payload": {"collection_guarantees": [],"block_seals": []}` + executionResult := fmt.Sprintf(`"execution_result": %s`, executionResultExpectedStr(execResult)) + + partsSet := make(map[string]string) + if expanded { - return fmt.Sprintf(` - { - "header": { - "id": "%s", - "parent_id": "%s", - "height": "%d", - "timestamp": "%s", - "parent_voter_signature": "%s" - }, - "payload": { - "collection_guarantees": [], - "block_seals": [] - }, - "execution_result": %s, - "_expandable": {}, - "_links": { - "_self": "%s" - }, - "block_status": "%s" - }`, id, block.Header.ParentID.String(), block.Header.Height, timestamp, - util.ToBase64(block.Header.ParentVoterSigData), executionResultExpectedStr(execResult), blockLink, blockStatus) + partsSet["header"] = header + partsSet["payload"] = payload + partsSet["executionResult"] = executionResult + partsSet["_expandable"] = `"_expandable": {}` + partsSet["_links"] = links + partsSet["block_status"] = blockStatus + } else { + partsSet["header"] = header + partsSet["_expandable"] = expandable + partsSet["_links"] = links + partsSet["block_status"] = blockStatus } - return fmt.Sprintf(` - { - "header": { - "id": "%s", - "parent_id": "%s", - "height": "%d", - "timestamp": "%s", - "parent_voter_signature": "%s" - }, - "_expandable": { - "payload": "%s", - "execution_result": "%s" - }, - "_links": { - "_self": "%s" - }, - "block_status": "%s" - }`, id, block.Header.ParentID.String(), block.Header.Height, timestamp, - util.ToBase64(block.Header.ParentVoterSigData), payloadLink, execLink, blockLink, blockStatus) + if len(selectedFields) > 0 { + // filter a json struct + // elements whose keys are not found in the filter map will be removed + selectedFieldSet := make(map[string]struct{}, len(selectedFields)) + for _, field := range selectedFields { + selectedFieldSet[field] = struct{}{} + } + + for key := range partsSet { + if _, found := selectedFieldSet[key]; !found { + delete(partsSet, key) + } + } + } + + // Iterate over the map and append the values to the slice + var values []string + for _, value := range partsSet { + values = append(values, value) + } + if len(values) == 0 { + return "" + } + + return fmt.Sprintf("{%s}", strings.Join(values, ",")) } diff --git a/engine/access/rest/util/select_filter_test.go b/engine/access/rest/util/select_filter_test.go index e810affece8..4e2e769bce4 100644 --- a/engine/access/rest/util/select_filter_test.go +++ b/engine/access/rest/util/select_filter_test.go @@ -52,6 +52,18 @@ func TestSelectFilter(t *testing.T) { keys: []string{"b.c"}, description: "single object with arrays as values", }, + { + input: `{ "a": 1, "b": {"c":2, "d":3}}`, + output: `{ "b": {"c":2, "d":3}}`, + keys: []string{"b"}, + description: "full single object with nested fields", + }, + { + input: `{ "a": 1, "b": {"c":2, "d":3}}`, + output: `{}`, + keys: []string{"e"}, + description: "unknown object", + }, } for _, tv := range testVectors { From 142633b8756b1f321e7e859709ec3865b8ef2aaf Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 8 Aug 2024 22:50:03 -0700 Subject: [PATCH 3/6] Add reproducer --- .../cadence_values_migration_test.go | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 4a9aba89541..62e88f04d30 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -2182,7 +2182,9 @@ func TestCapabilityMigration(t *testing.T) { interpreter.PrimitiveStaticTypeAnyStruct, ) - capabilityValue := &interpreter.PathCapabilityValue{ + // Store a capability with storage path + + capabilityFoo := &interpreter.PathCapabilityValue{ BorrowType: borrowType, Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo"), @@ -2194,7 +2196,23 @@ func TestCapabilityMigration(t *testing.T) { storageMap.WriteValue( runtime.Interpreter, storageMapKey, - capabilityValue, + capabilityFoo, + ) + + // Store another capability with storage path, but without a borrow type. + + capabilityBar := &interpreter.PathCapabilityValue{ + Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "bar"), + + // Important: Capability must be for a different address, + // compared to where the capability is stored. + Address: interpreter.AddressValue(addressB), + } + + storageMap.WriteValue( + runtime.Interpreter, + storageMapKey, + capabilityBar, ) err = storage.NondeterministicCommit(runtime.Interpreter, false) From 8f7b413c517e7796d2f63b9d960ce4652cdf0de5 Mon Sep 17 00:00:00 2001 From: Yurii Oleksyshyn Date: Fri, 9 Aug 2024 22:09:20 +0300 Subject: [PATCH 4/6] Restructured logic for processing epoch recover to avoid a case where invalid event modifies current state. Updated tests --- .../epochs/fallback_statemachine.go | 39 +++++----- .../epochs/fallback_statemachine_test.go | 76 ++++++++++++++----- 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/state/protocol/protocol_state/epochs/fallback_statemachine.go b/state/protocol/protocol_state/epochs/fallback_statemachine.go index 888c80f8c5b..7c67fd8d130 100644 --- a/state/protocol/protocol_state/epochs/fallback_statemachine.go +++ b/state/protocol/protocol_state/epochs/fallback_statemachine.go @@ -165,7 +165,19 @@ func (m *FallbackStateMachine) ProcessEpochRecover(epochRecover *flow.EpochRecov m.telemetry.OnInvalidServiceEvent(epochRecover.ServiceEvent(), err) return false, nil } + nextEpoch := m.state.NextEpoch + if nextEpoch != nil { + // accept iff the EpochRecover is the same as the one we have already recovered. + if nextEpoch.SetupID != epochRecover.EpochSetup.ID() || + nextEpoch.CommitID != epochRecover.EpochCommit.ID() { + m.telemetry.OnInvalidServiceEvent(epochRecover.ServiceEvent(), + protocol.NewInvalidServiceEventErrorf("multiple inconsistent EpochRecover events sealed in the same block")) + return false, nil + } + } + // m.state.NextEpoch is either nil, or its EpochSetup and EpochCommit are identical to the given `epochRecover` + // assemble EpochStateContainer for next epoch: nextEpochParticipants, err := buildNextEpochActiveParticipants( m.state.CurrentEpoch.ActiveIdentities.Lookup(), m.state.CurrentEpochSetup, @@ -174,26 +186,14 @@ func (m *FallbackStateMachine) ProcessEpochRecover(epochRecover *flow.EpochRecov m.telemetry.OnInvalidServiceEvent(epochRecover.ServiceEvent(), fmt.Errorf("rejecting EpochRecover event: %w", err)) return false, nil } - - nextEpoch := m.state.NextEpoch - if nextEpoch == nil { - // setup next epoch if there is none - m.state.NextEpoch = &flow.EpochStateContainer{ - SetupID: epochRecover.EpochSetup.ID(), - CommitID: epochRecover.EpochCommit.ID(), - ActiveIdentities: nextEpochParticipants, - EpochExtensions: nil, - } - } else { - // accept iff the EpochRecover is the same as the one we have already recovered. - if nextEpoch.SetupID != epochRecover.EpochSetup.ID() || - nextEpoch.CommitID != epochRecover.EpochCommit.ID() { - m.telemetry.OnInvalidServiceEvent(epochRecover.ServiceEvent(), - protocol.NewInvalidServiceEventErrorf("multiple inconsistent EpochRecover events sealed in the same block")) - return false, nil - } + nextEpoch = &flow.EpochStateContainer{ + SetupID: epochRecover.EpochSetup.ID(), + CommitID: epochRecover.EpochCommit.ID(), + ActiveIdentities: nextEpochParticipants, + EpochExtensions: nil, } - err = m.ejector.TrackDynamicIdentityList(m.state.NextEpoch.ActiveIdentities) + + err = m.ejector.TrackDynamicIdentityList(nextEpoch.ActiveIdentities) if err != nil { if protocol.IsInvalidServiceEventError(err) { m.telemetry.OnInvalidServiceEvent(epochRecover.ServiceEvent(), fmt.Errorf("rejecting EpochRecover event: %w", err)) @@ -202,6 +202,7 @@ func (m *FallbackStateMachine) ProcessEpochRecover(epochRecover *flow.EpochRecov return false, fmt.Errorf("unexpected errors tracking identity list: %w", err) } // if we have processed a valid EpochRecover event, we should exit EFM. + m.state.NextEpoch = nextEpoch m.state.EpochFallbackTriggered = false m.telemetry.OnServiceEventProcessed(epochRecover.ServiceEvent()) return true, nil diff --git a/state/protocol/protocol_state/epochs/fallback_statemachine_test.go b/state/protocol/protocol_state/epochs/fallback_statemachine_test.go index abb7d3ec633..b8fd9addb07 100644 --- a/state/protocol/protocol_state/epochs/fallback_statemachine_test.go +++ b/state/protocol/protocol_state/epochs/fallback_statemachine_test.go @@ -659,28 +659,70 @@ func (s *EpochFallbackStateMachineSuite) TestEpochFallbackStateMachineInjectsMul } } -// TestEpochRecoverAndEjectionInSameBlock tests that processing an epoch recover event which re-admits an ejected identity results in an error. -// Such action should be considered illegal since smart contract emitted ejection before epoch recover and service events are delivered -// in an order-preserving manner. +// TestEpochRecoverAndEjectionInSameBlock tests that state machine correctly handles ejection events and a subsequent +// epoch recover in the same block. Specifically we test two cases: +// 1. Happy Path: The Epoch Recover event excludes the previously ejected node. +// 2. Invalid Epoch Recover: an epoch recover event which re-admits an ejected identity is ignored. Such action should +// be considered illegal since smart contract emitted ejection before epoch recover and service events are delivered +// in an order-preserving manner. However, since the FallbackStateMachine is intended to keep the protocol alive even +// in the presence of (largely) arbitrary Epoch Smart Contract bugs, it should also handle this case gracefully. +// In this case, the EpochRecover service event should be discarded and the internal state should remain unchanged. func (s *EpochFallbackStateMachineSuite) TestEpochRecoverAndEjectionInSameBlock() { nextEpochParticipants := s.parentProtocolState.CurrentEpochIdentityTable.Copy() ejectedIdentityID := nextEpochParticipants.Filter(filter.HasRole[flow.Identity](flow.RoleAccess))[0].NodeID - epochRecover := unittest.EpochRecoverFixture(func(setup *flow.EpochSetup) { - setup.Participants = nextEpochParticipants.ToSkeleton() - setup.Assignments = unittest.ClusterAssignment(1, nextEpochParticipants.ToSkeleton()) - setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1 - setup.FirstView = s.parentProtocolState.CurrentEpochSetup.FinalView + 1 - setup.FinalView = setup.FirstView + 10_000 + + s.Run("happy path", func() { + wasEjected := s.stateMachine.EjectIdentity(ejectedIdentityID) + require.True(s.T(), wasEjected) + + epochRecover := unittest.EpochRecoverFixture(func(setup *flow.EpochSetup) { + setup.Participants = nextEpochParticipants.ToSkeleton().Filter( + filter.Not(filter.HasNodeID[flow.IdentitySkeleton](ejectedIdentityID))) + setup.Assignments = unittest.ClusterAssignment(1, setup.Participants.ToSkeleton()) + setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1 + setup.FirstView = s.parentProtocolState.CurrentEpochSetup.FinalView + 1 + setup.FinalView = setup.FirstView + 10_000 + }) + s.consumer.On("OnServiceEventReceived", epochRecover.ServiceEvent()).Once() + s.consumer.On("OnServiceEventProcessed", epochRecover.ServiceEvent()).Once() + processed, err := s.stateMachine.ProcessEpochRecover(epochRecover) + require.NoError(s.T(), err) + require.True(s.T(), processed) + + updatedState, _, _ := s.stateMachine.Build() + require.False(s.T(), updatedState.EpochFallbackTriggered, "should exit EFM") + require.NotNil(s.T(), updatedState.NextEpoch, "should setup & commit next epoch") }) - ejected := s.stateMachine.EjectIdentity(ejectedIdentityID) - require.True(s.T(), ejected) + s.Run("invalid epoch recover event", func() { + s.kvstore = mockstate.NewKVStoreReader(s.T()) + s.kvstore.On("GetEpochExtensionViewCount").Return(extensionViewCount).Maybe() + s.kvstore.On("GetEpochCommitSafetyThreshold").Return(uint64(200)) - s.consumer.On("OnServiceEventReceived", epochRecover.ServiceEvent()).Once() - s.consumer.On("OnInvalidServiceEvent", epochRecover.ServiceEvent(), - mock.MatchedBy(func(err error) bool { return protocol.IsInvalidServiceEventError(err) })).Once() - processed, err := s.stateMachine.ProcessEpochRecover(epochRecover) - require.NoError(s.T(), err) - require.False(s.T(), processed) + var err error + s.stateMachine, err = NewFallbackStateMachine(s.kvstore, s.consumer, s.candidate.View, s.parentProtocolState.Copy()) + require.NoError(s.T(), err) + + wasEjected := s.stateMachine.EjectIdentity(ejectedIdentityID) + require.True(s.T(), wasEjected) + + epochRecover := unittest.EpochRecoverFixture(func(setup *flow.EpochSetup) { + setup.Participants = nextEpochParticipants.ToSkeleton() + setup.Assignments = unittest.ClusterAssignment(1, nextEpochParticipants.ToSkeleton()) + setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1 + setup.FirstView = s.parentProtocolState.CurrentEpochSetup.FinalView + 1 + setup.FinalView = setup.FirstView + 10_000 + }) + s.consumer.On("OnServiceEventReceived", epochRecover.ServiceEvent()).Once() + s.consumer.On("OnInvalidServiceEvent", epochRecover.ServiceEvent(), + mock.MatchedBy(func(err error) bool { return protocol.IsInvalidServiceEventError(err) })).Once() + processed, err := s.stateMachine.ProcessEpochRecover(epochRecover) + require.NoError(s.T(), err) + require.False(s.T(), processed) + + updatedState, _, _ := s.stateMachine.Build() + require.True(s.T(), updatedState.EpochFallbackTriggered, "should remain in EFM") + require.Nil(s.T(), updatedState.NextEpoch, "next epoch should be nil as recover event is invalid") + }) } // TestProcessingMultipleEventsAtTheSameBlock tests that the state machine can process multiple events at the same block. From 5dae66bcdf878eaf6bbbfece9ad2821c418ad5b7 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 9 Aug 2024 11:46:30 -0700 Subject: [PATCH 5/6] Report and skip storage caps with no borrow type --- cmd/util/ledger/migrations/cadence.go | 42 ++++--- .../migrations/cadence_values_migration.go | 72 +++++++++-- .../cadence_values_migration_test.go | 117 +++++++++++++++--- 3 files changed, 185 insertions(+), 46 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 393a6ad3bb5..9ad27a8f699 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -5,12 +5,13 @@ import ( _ "embed" "fmt" + "github.com/rs/zerolog" + "github.com/onflow/cadence/migrations/capcons" "github.com/onflow/cadence/migrations/statictypes" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" - "github.com/rs/zerolog" "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" @@ -228,16 +229,22 @@ type IssueStorageCapConMigration struct { programs map[runtime.Location]*interpreter.Program mapping *capcons.CapabilityMapping reporter reporters.ReportWriter + logVerboseDiff bool + verboseErrorOutput bool + errorMessageHandler *errorMessageHandler + log zerolog.Logger } const issueStorageCapConMigrationReporterName = "cadence-storage-capcon-issue-migration" func NewIssueStorageCapConMigration( rwf reporters.ReportWriterFactory, + errorMessageHandler *errorMessageHandler, chainID flow.ChainID, storageDomainCapabilities *capcons.AccountsCapabilities, programs map[runtime.Location]*interpreter.Program, capabilityMapping *capcons.CapabilityMapping, + opts Options, ) *IssueStorageCapConMigration { return &IssueStorageCapConMigration{ name: "cadence_storage_cap_con_issue_migration", @@ -246,14 +253,19 @@ func NewIssueStorageCapConMigration( accountsCapabilities: storageDomainCapabilities, programs: programs, mapping: capabilityMapping, + logVerboseDiff: opts.LogVerboseDiff, + verboseErrorOutput: opts.VerboseErrorOutput, + errorMessageHandler: errorMessageHandler, } } func (m *IssueStorageCapConMigration) InitMigration( - _ zerolog.Logger, + log zerolog.Logger, _ *registers.ByAccount, _ int, ) error { + m.log = log.With().Str("migration", m.name).Logger() + // During the migration, we only provide already checked programs, // no parsing/checking of contracts is expected. @@ -309,30 +321,22 @@ func (m *IssueStorageCapConMigration) MigrateAccount( idGenerator: idGenerator, } + reporter := newValueMigrationReporter( + m.reporter, + m.log, + m.errorMessageHandler, + m.verboseErrorOutput, + ) + capcons.IssueAccountCapabilities( migrationRuntime.Interpreter, + reporter, address, accountCapabilities, handler, m.mapping, ) - // It would be ideal to do the reporting inside `IssueAccountCapabilities` function above. - // However, that doesn't have the access to the reporter. So doing it here. - for _, capability := range accountCapabilities.Capabilities { - id, _, _ := m.mapping.Get(interpreter.AddressPath{ - Address: address, - Path: capability.Path, - }) - - m.reporter.Write(storageCapconIssuedEntry{ - AccountAddress: address, - Path: capability.Path, - BorrowType: capability.BorrowType, - CapabilityID: id, - }) - } - return nil } @@ -409,10 +413,12 @@ func NewCadence1ValueMigrations( func(opts Options) (string, AccountBasedMigration) { migration := NewIssueStorageCapConMigration( rwf, + errorMessageHandler, opts.ChainID, storageDomainCapabilities, programs, capabilityMapping, + opts, ) return migration.name, migration diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 53eebf77a14..c484d34cb35 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -449,6 +449,7 @@ type cadenceValueMigrationReporter struct { var _ capcons.LinkMigrationReporter = &cadenceValueMigrationReporter{} var _ capcons.CapabilityMigrationReporter = &cadenceValueMigrationReporter{} +var _ capcons.StorageCapabilityMigrationReporter = &cadenceValueMigrationReporter{} var _ migrations.Reporter = &cadenceValueMigrationReporter{} func newValueMigrationReporter( @@ -529,6 +530,30 @@ func (t *cadenceValueMigrationReporter) MissingCapabilityID( }) } +func (t *cadenceValueMigrationReporter) MissingBorrowType( + accountAddress common.Address, + addressPath interpreter.AddressPath, +) { + t.reportWriter.Write(storageCapConsMissingBorrowTypeEntry{ + AccountAddress: accountAddress, + AddressPath: addressPath, + }) +} + +func (t *cadenceValueMigrationReporter) IssuedStorageCapabilityController( + accountAddress common.Address, + addressPath interpreter.AddressPath, + borrowType *interpreter.ReferenceStaticType, + capabilityID interpreter.UInt64Value, +) { + t.reportWriter.Write(storageCapConIssuedEntry{ + AccountAddress: accountAddress, + AddressPath: addressPath, + BorrowType: borrowType, + CapabilityID: capabilityID, + }) +} + func (t *cadenceValueMigrationReporter) MigratedLink( accountAddressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, @@ -801,35 +826,66 @@ func (e dictionaryKeyConflictEntry) MarshalJSON() ([]byte, error) { }) } -// storageCapconIssuedEntry +// storageCapConIssuedEntry -type storageCapconIssuedEntry struct { +type storageCapConIssuedEntry struct { AccountAddress common.Address - Path interpreter.PathValue + AddressPath interpreter.AddressPath BorrowType interpreter.StaticType CapabilityID interpreter.UInt64Value } -var _ valueMigrationReportEntry = storageCapconIssuedEntry{} +var _ valueMigrationReportEntry = storageCapConIssuedEntry{} -func (e storageCapconIssuedEntry) accountAddress() common.Address { +func (e storageCapConIssuedEntry) accountAddress() common.Address { return e.AccountAddress } -var _ json.Marshaler = storageCapconIssuedEntry{} +var _ json.Marshaler = storageCapConIssuedEntry{} -func (e storageCapconIssuedEntry) MarshalJSON() ([]byte, error) { +func (e storageCapConIssuedEntry) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Kind string `json:"kind"` AccountAddress string `json:"account_address"` + Address string `json:"address"` Path string `json:"path"` BorrowType string `json:"borrow_type"` CapabilityID string `json:"capability_id"` }{ Kind: "storage-capcon-issued", AccountAddress: e.AccountAddress.HexWithPrefix(), - Path: e.Path.String(), + Address: e.AddressPath.Address.HexWithPrefix(), + Path: e.AddressPath.Path.String(), BorrowType: string(e.BorrowType.ID()), CapabilityID: e.CapabilityID.String(), }) } + +// StorageCapConMissingBorrowType + +type storageCapConsMissingBorrowTypeEntry struct { + AccountAddress common.Address + AddressPath interpreter.AddressPath +} + +var _ valueMigrationReportEntry = storageCapConsMissingBorrowTypeEntry{} + +func (e storageCapConsMissingBorrowTypeEntry) accountAddress() common.Address { + return e.AccountAddress +} + +var _ json.Marshaler = storageCapConsMissingBorrowTypeEntry{} + +func (e storageCapConsMissingBorrowTypeEntry) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind string `json:"kind"` + AccountAddress string `json:"account_address"` + Address string `json:"address"` + Path string `json:"path"` + }{ + Kind: "storage-capcon-missing-borrow-type", + AccountAddress: e.AccountAddress.HexWithPrefix(), + Address: e.AddressPath.Address.HexWithPrefix(), + Path: e.AddressPath.Path.String(), + }) +} diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 62e88f04d30..41cfb014803 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -2167,7 +2167,6 @@ func TestCapabilityMigration(t *testing.T) { require.NoError(t, err) storage := runtime.Storage - storageMapKey := interpreter.StringStorageMapKey("test") storageDomain := common.PathDomainStorage.Identifier() storageMap := storage.GetStorageMap( @@ -2184,6 +2183,8 @@ func TestCapabilityMigration(t *testing.T) { // Store a capability with storage path + fooCapStorageMapKey := interpreter.StringStorageMapKey("fooCap") + capabilityFoo := &interpreter.PathCapabilityValue{ BorrowType: borrowType, Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo"), @@ -2195,12 +2196,14 @@ func TestCapabilityMigration(t *testing.T) { storageMap.WriteValue( runtime.Interpreter, - storageMapKey, + fooCapStorageMapKey, capabilityFoo, ) // Store another capability with storage path, but without a borrow type. + barCapStorageMapKey := interpreter.StringStorageMapKey("barCap") + capabilityBar := &interpreter.PathCapabilityValue{ Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "bar"), @@ -2211,7 +2214,7 @@ func TestCapabilityMigration(t *testing.T) { storageMap.WriteValue( runtime.Interpreter, - storageMapKey, + barCapStorageMapKey, capabilityBar, ) @@ -2269,11 +2272,18 @@ func TestCapabilityMigration(t *testing.T) { reporter := rwf.reportWriters[capabilityValueMigrationReporterName] require.NotNil(t, reporter) - require.Len(t, reporter.entries, 2) + require.Len(t, reporter.entries, 3) require.Equal( t, []any{ + capabilityMissingCapabilityIDEntry{ + AccountAddress: addressA, + AddressPath: interpreter.AddressPath{ + Address: addressB, + Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "bar"), + }, + }, capabilityMigrationEntry{ AccountAddress: addressA, AddressPath: interpreter.AddressPath{ @@ -2288,7 +2298,7 @@ func TestCapabilityMigration(t *testing.T) { Key: storageDomain, Address: addressA, }, - StorageMapKey: storageMapKey, + StorageMapKey: fooCapStorageMapKey, Migration: "CapabilityValueMigration", }, }, @@ -2297,28 +2307,95 @@ func TestCapabilityMigration(t *testing.T) { issueStorageCapConReporter := rwf.reportWriters[issueStorageCapConMigrationReporterName] require.NotNil(t, issueStorageCapConReporter) - require.Len(t, issueStorageCapConReporter.entries, 1) + require.Len(t, issueStorageCapConReporter.entries, 2) + require.Equal( + t, + []any{ + storageCapConsMissingBorrowTypeEntry{ + AccountAddress: addressB, + AddressPath: interpreter.AddressPath{ + Address: addressB, + Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "bar"), + }, + }, + storageCapConIssuedEntry{ + AccountAddress: addressB, + AddressPath: interpreter.AddressPath{ + Address: addressB, + Path: interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo"), + }, + BorrowType: borrowType, + CapabilityID: 3, + }, + }, + issueStorageCapConReporter.entries, + ) +} - entry := issueStorageCapConReporter.entries[0] +func TestStorageCapConIssuedEntry_MarshalJSON(t *testing.T) { - require.IsType(t, storageCapconIssuedEntry{}, entry) - storageCapconIssued := entry.(storageCapconIssuedEntry) + t.Parallel() - actual, err := storageCapconIssued.MarshalJSON() + e := storageCapConIssuedEntry{ + AccountAddress: common.MustBytesToAddress([]byte{0x2}), + AddressPath: interpreter.AddressPath{ + Address: common.MustBytesToAddress([]byte{0x1}), + Path: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: "test", + }, + }, + BorrowType: interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.PrimitiveStaticTypeInt, + ), + CapabilityID: 3, + } + + actual, err := e.MarshalJSON() require.NoError(t, err) require.JSONEq(t, //language=JSON - fmt.Sprintf( - `{ - "kind": "storage-capcon-issued", - "account_address": "%s", - "path": "/storage/foo", - "borrow_type": "&AnyStruct", - "capability_id": "3" - }`, - addressB.HexWithPrefix(), - ), + `{ + "kind": "storage-capcon-issued", + "account_address": "0x0000000000000002", + "address": "0x0000000000000001", + "path": "/storage/test", + "borrow_type": "&Int", + "capability_id": "3" + }`, + string(actual), + ) +} + +func TestStorageCapConsMissingBorrowTypeEntry_MarshalJSON(t *testing.T) { + + t.Parallel() + + e := storageCapConsMissingBorrowTypeEntry{ + AccountAddress: common.MustBytesToAddress([]byte{0x2}), + AddressPath: interpreter.AddressPath{ + Address: common.MustBytesToAddress([]byte{0x1}), + Path: interpreter.PathValue{ + Domain: common.PathDomainStorage, + Identifier: "test", + }, + }, + } + + actual, err := e.MarshalJSON() + require.NoError(t, err) + + require.JSONEq(t, + //language=JSON + `{ + "kind": "storage-capcon-missing-borrow-type", + "account_address": "0x0000000000000002", + "address": "0x0000000000000001", + "path": "/storage/test" + }`, string(actual), ) } From d3ea857ac1f83a0ec8a91f020a0c4c8f1b1ac128 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 9 Aug 2024 13:05:28 -0700 Subject: [PATCH 6/6] Update to cadence v1.0.0-preview.44 --- go.mod | 2 +- go.sum | 4 ++-- insecure/go.mod | 2 +- insecure/go.sum | 4 ++-- integration/go.mod | 2 +- integration/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f27670305ef..1a24448c28d 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/onflow/atree v0.8.0-rc.5 - github.com/onflow/cadence v1.0.0-preview.43 + github.com/onflow/cadence v1.0.0-preview.44 github.com/onflow/crypto v0.25.1 github.com/onflow/flow v0.3.4 github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 diff --git a/go.sum b/go.sum index c623d5db725..e50b11b40b1 100644 --- a/go.sum +++ b/go.sum @@ -2167,8 +2167,8 @@ github.com/onflow/atree v0.8.0-rc.5/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/ github.com/onflow/boxo v0.0.0-20240201202436-f2477b92f483 h1:LpiQhTAfM9CAmNVEs0n//cBBgCg+vJSiIxTHYUklZ84= github.com/onflow/boxo v0.0.0-20240201202436-f2477b92f483/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.43 h1:Bnc+uNYMeE3Mj8wTH5vpT+d9wo9toYmwsucZhYbZtGE= -github.com/onflow/cadence v1.0.0-preview.43/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= +github.com/onflow/cadence v1.0.0-preview.44 h1:APQ8DzIS6kX4FMZsaYoucbvF/PPwjHEerr+aGRDcPz4= +github.com/onflow/cadence v1.0.0-preview.44/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= diff --git a/insecure/go.mod b/insecure/go.mod index 59715ff84d1..4a5a8fb5d2f 100644 --- a/insecure/go.mod +++ b/insecure/go.mod @@ -202,7 +202,7 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/atree v0.8.0-rc.5 // indirect - github.com/onflow/cadence v1.0.0-preview.43 // indirect + github.com/onflow/cadence v1.0.0-preview.44 // indirect github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 // indirect github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 // indirect github.com/onflow/flow-ft/lib/go/contracts v1.0.0 // indirect diff --git a/insecure/go.sum b/insecure/go.sum index 8d8524f7468..bb81e58ac7f 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -2157,8 +2157,8 @@ github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs github.com/onflow/atree v0.8.0-rc.5 h1:1sU+c6UfDzq/EjM8nTw4EI8GvEMarcxkWkJKy6piFSY= github.com/onflow/atree v0.8.0-rc.5/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.43 h1:Bnc+uNYMeE3Mj8wTH5vpT+d9wo9toYmwsucZhYbZtGE= -github.com/onflow/cadence v1.0.0-preview.43/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= +github.com/onflow/cadence v1.0.0-preview.44 h1:APQ8DzIS6kX4FMZsaYoucbvF/PPwjHEerr+aGRDcPz4= +github.com/onflow/cadence v1.0.0-preview.44/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= diff --git a/integration/go.mod b/integration/go.mod index 28b396fa94a..0658515807c 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -20,7 +20,7 @@ require ( github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ds-pebble v0.3.1 github.com/libp2p/go-libp2p v0.32.2 - github.com/onflow/cadence v1.0.0-preview.43 + github.com/onflow/cadence v1.0.0-preview.44 github.com/onflow/crypto v0.25.1 github.com/onflow/flow-core-contracts/lib/go/contracts v1.3.1 github.com/onflow/flow-core-contracts/lib/go/templates v1.3.1 diff --git a/integration/go.sum b/integration/go.sum index fe04f457ff0..b8039a7b298 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -2141,8 +2141,8 @@ github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs github.com/onflow/atree v0.8.0-rc.5 h1:1sU+c6UfDzq/EjM8nTw4EI8GvEMarcxkWkJKy6piFSY= github.com/onflow/atree v0.8.0-rc.5/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.43 h1:Bnc+uNYMeE3Mj8wTH5vpT+d9wo9toYmwsucZhYbZtGE= -github.com/onflow/cadence v1.0.0-preview.43/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= +github.com/onflow/cadence v1.0.0-preview.44 h1:APQ8DzIS6kX4FMZsaYoucbvF/PPwjHEerr+aGRDcPz4= +github.com/onflow/cadence v1.0.0-preview.44/go.mod h1:BCoenp1TYp+SmG7FGWStjehvvzcvNQ3xvpK5rkthq3Y= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI=