Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Access] Add util command to backfill tx error messages db #6525

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9306618
Added command to access node, implemented Validator to command
UlyanaAndrukhiv Sep 26, 2024
be6e165
Merge branch 'UlyanaAndrukhiv/6497-refactor-executionNodesForBlockID'…
UlyanaAndrukhiv Sep 30, 2024
28ec5cd
Updated validation for command
UlyanaAndrukhiv Oct 3, 2024
a9d2abf
Added tests for command
UlyanaAndrukhiv Oct 3, 2024
86a4e48
Updated readme for admin commands
UlyanaAndrukhiv Oct 3, 2024
811a623
Updated test
UlyanaAndrukhiv Oct 3, 2024
a9acd73
Merge branch 'UlyanaAndrukhiv/6497-refactor-executionNodesForBlockID'…
UlyanaAndrukhiv Oct 3, 2024
3666c1e
Added godoc for tests
UlyanaAndrukhiv Oct 3, 2024
35e4fc1
Moved mock closer to unittest mock module, updated tests
UlyanaAndrukhiv Oct 3, 2024
bb54eab
Merge branch 'master' into UlyanaAndrukhiv/6413-backfill-tx-error-mes…
UlyanaAndrukhiv Oct 8, 2024
e882b61
Merge branch 'UlyanaAndrukhiv/6497-refactor-executionNodesForBlockID'…
UlyanaAndrukhiv Oct 8, 2024
abc1a2c
Added godoc
UlyanaAndrukhiv Oct 8, 2024
95d3849
Updated godoc
UlyanaAndrukhiv Oct 8, 2024
6aef441
Merge branch 'master' into UlyanaAndrukhiv/6413-backfill-tx-error-mes…
UlyanaAndrukhiv Oct 22, 2024
6c7c189
Merged with UlyanaAndrukhiv/6497-refactor-executionNodesForBlockID
UlyanaAndrukhiv Oct 22, 2024
59cf83b
Fixed error message
UlyanaAndrukhiv Oct 22, 2024
a239bb3
Merge branch 'UlyanaAndrukhiv/6497-refactor-executionNodesForBlockID'…
UlyanaAndrukhiv Oct 24, 2024
09a19ea
Merge branch 'master' into UlyanaAndrukhiv/6413-backfill-tx-error-mes…
UlyanaAndrukhiv Oct 30, 2024
0380c35
Updated according to suggested comments
UlyanaAndrukhiv Oct 30, 2024
b3752cc
Updated according tx error messages storing, reused existing function…
UlyanaAndrukhiv Nov 1, 2024
b610309
Merge branch 'master' into UlyanaAndrukhiv/6413-backfill-tx-error-mes…
UlyanaAndrukhiv Nov 1, 2024
2675538
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into UlyanaAn…
UlyanaAndrukhiv Nov 5, 2024
fc6528b
Merge branch 'UlyanaAndrukhiv/6413-backfill-tx-error-messages' of git…
UlyanaAndrukhiv Nov 5, 2024
1080963
Merge branch 'master' into UlyanaAndrukhiv/6413-backfill-tx-error-mes…
UlyanaAndrukhiv Nov 5, 2024
26c3072
Updated according to suggested comments
UlyanaAndrukhiv Nov 5, 2024
d47137d
Updated unit test
UlyanaAndrukhiv Nov 5, 2024
57aacf9
Updated error messages
UlyanaAndrukhiv Nov 5, 2024
047d643
Updated error messages for execution-node-ids parsing
UlyanaAndrukhiv Nov 5, 2024
258273c
Added integration test for backfilling tx error messages
UlyanaAndrukhiv Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions admin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,8 @@ curl localhost:9002/admin/run_command -H 'Content-Type: application/json' -d '{"
curl localhost:9002/admin/run_command -H 'Content-Type: application/json' -d '{"commandName": "protocol-snapshot"}'
curl localhost:9002/admin/run_command -H 'Content-Type: application/json' -d '{"commandName": "protocol-snapshot", "data": { "blocks-to-skip": 10 }}'
```

### To backfill transaction error messages
```
curl localhost:9002/admin/run_command -H 'Content-Type: application/json' -d '{"commandName": "backfill-tx-error-messages", "data": { "start-height": 340, "end-height": 343, "execution-node-ids":["ec7b934df29248d574ae1cc33ae77f22f0fcf96a79e009224c46374d1837824e", "8cbdc8d24a28899a33140cb68d4146cd6f2f6c18c57f54c299f26351d126919e"] }}'
```
239 changes: 239 additions & 0 deletions admin/commands/storage/backfill_tx_error_messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package storage

import (
"context"
"fmt"

execproto "github.com/onflow/flow/protobuf/go/flow/execution"

"github.com/onflow/flow-go/admin"
"github.com/onflow/flow-go/admin/commands"
"github.com/onflow/flow-go/engine/access/index"
"github.com/onflow/flow-go/engine/access/rpc/backend"
commonrpc "github.com/onflow/flow-go/engine/common/rpc"
"github.com/onflow/flow-go/engine/common/rpc/convert"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/model/flow/filter"
"github.com/onflow/flow-go/state/protocol"
"github.com/onflow/flow-go/storage"
)

var _ commands.AdminCommand = (*BackfillTxErrorMessagesCommand)(nil)

// backfillTxErrorMessagesRequest represents the input parameters for
// backfilling transaction error messages.
type backfillTxErrorMessagesRequest struct {
startHeight uint64 // Start height from which to begin backfilling.
endHeight uint64 // End height up to which backfilling is performed.
executionNodeIds flow.IdentityList // List of execution node IDs to be used for backfilling.
}

// BackfillTxErrorMessagesCommand executes a command to backfill
// transaction error messages by fetching them from execution nodes.
type BackfillTxErrorMessagesCommand struct {
state protocol.State
txResultsIndex *index.TransactionResultsIndex
txErrorMessages storage.TransactionResultErrorMessages
backend *backend.Backend
}

// NewBackfillTxErrorMessagesCommand creates a new instance of BackfillTxErrorMessagesCommand
func NewBackfillTxErrorMessagesCommand(
state protocol.State,
txResultsIndex *index.TransactionResultsIndex,
txErrorMessages storage.TransactionResultErrorMessages,
backend *backend.Backend,
) commands.AdminCommand {
return &BackfillTxErrorMessagesCommand{
state: state,
txResultsIndex: txResultsIndex,
txErrorMessages: txErrorMessages,
backend: backend,
}
}

// Validator validates the input for the backfill command. The input is validated
// for field types, boundaries, and coherence of start and end heights.
//
// Expected errors during normal operation:
// - admin.InvalidAdminReqError - if start-height is greater than end-height or
// if the input format is invalid, if an invalid execution node ID is provided.
func (b *BackfillTxErrorMessagesCommand) Validator(request *admin.CommandRequest) error {
input, ok := request.Data.(map[string]interface{})
if !ok {
return admin.NewInvalidAdminReqFormatError("expected map[string]any")
}

data := &backfillTxErrorMessagesRequest{}

rootHeight := b.state.Params().SealedRoot().Height
data.startHeight = rootHeight // Default value

if startHeightIn, ok := input["start-height"]; ok {
if startHeight, err := parseN(startHeightIn); err != nil {
return admin.NewInvalidAdminReqErrorf("invalid 'start-height' field: %w", err)
} else if startHeight > rootHeight {
data.startHeight = startHeight
}
UlyanaAndrukhiv marked this conversation as resolved.
Show resolved Hide resolved
}

sealed, err := b.state.Sealed().Head()
if err != nil {
return fmt.Errorf("failed to lookup sealed header: %w", err)
}
data.endHeight = sealed.Height // Default value

if endHeightIn, ok := input["end-height"]; ok {
if endHeight, err := parseN(endHeightIn); err != nil {
return admin.NewInvalidAdminReqErrorf("invalid 'end-height' field: %w", err)
} else if endHeight < sealed.Height {
data.endHeight = endHeight
}
}

if data.endHeight < data.startHeight {
return admin.NewInvalidAdminReqErrorf("start-height %d should not be smaller than end-height %d", data.startHeight, data.endHeight)
}

identities, err := b.state.Final().Identities(filter.HasRole[flow.Identity](flow.RoleExecution))
if err != nil {
return fmt.Errorf("failed to retreive execution IDs: %w", err)
}

if executionNodeIdsIn, ok := input["execution-node-ids"]; ok {
executionNodeIds, err := b.parseExecutionNodeIds(executionNodeIdsIn, identities)
if err != nil {
return err
}
data.executionNodeIds = executionNodeIds
} else {
// in case no execution node ids provided, the command will use any valid execution node
data.executionNodeIds = identities
}

request.ValidatorData = data

return nil
}

// Handler performs the backfilling operation by fetching missing transaction
// error messages for blocks within the specified height range. Uses execution nodes
// from data.executionNodeIds if available, otherwise defaults to valid execution nodes.
//
// No errors are expected during normal operation.
func (b *BackfillTxErrorMessagesCommand) Handler(ctx context.Context, request *admin.CommandRequest) (interface{}, error) {
if b.txErrorMessages == nil {
return nil, fmt.Errorf("failed to backfill, could not get transaction error messages storage")
}

data := request.ValidatorData.(*backfillTxErrorMessagesRequest)

for height := data.startHeight; height <= data.endHeight; height++ {
header, err := b.state.AtHeight(height).Head()
if err != nil {
return nil, fmt.Errorf("failed to get block header: %w", err)
}

blockID := header.ID()

exists, err := b.txErrorMessages.Exists(blockID)
if err != nil {
return nil, fmt.Errorf("could not check existance of transaction result error messages: %w", err)
}

if exists {
continue
}

results, err := b.txResultsIndex.ByBlockID(blockID, height)
if err != nil {
return nil, fmt.Errorf("failed to get result by block ID: %w", err)
}

fetchTxErrorMessages := false
for _, txResult := range results {
if txResult.Failed {
fetchTxErrorMessages = true
UlyanaAndrukhiv marked this conversation as resolved.
Show resolved Hide resolved
}
}

if !fetchTxErrorMessages {
continue
}

req := &execproto.GetTransactionErrorMessagesByBlockIDRequest{
BlockId: convert.IdentifierToMessage(blockID),
}

resp, execNode, err := b.backend.GetTransactionErrorMessagesFromAnyEN(ctx, data.executionNodeIds.ToSkeleton(), req)
if err != nil {
return nil, fmt.Errorf("failed to retrieve transaction error messages for block id %v: %w", blockID, err)
}

err = b.storeTransactionResultErrorMessages(blockID, resp, execNode)
if err != nil {
return nil, fmt.Errorf("could not store error messages: %w", err)
}
}

return nil, nil
}

// parseExecutionNodeIds converts a list of node IDs from input to flow.IdentityList.
// Returns an error if the IDs are invalid or empty.
//
// Expected errors during normal operation:
// - admin.InvalidAdminReqParameterError - if execution-node-ids is empty or has an invalid format.
func (b *BackfillTxErrorMessagesCommand) parseExecutionNodeIds(executionNodeIdsIn interface{}, allIdentities flow.IdentityList) (flow.IdentityList, error) {
var ids flow.IdentityList

switch executionNodeIds := executionNodeIdsIn.(type) {
case []string:
if len(executionNodeIds) == 0 {
return nil, admin.NewInvalidAdminReqParameterError("execution-node-ids", "must be a non empty list of string", executionNodeIdsIn)
UlyanaAndrukhiv marked this conversation as resolved.
Show resolved Hide resolved
}
requestedENIdentifiers, err := commonrpc.IdentifierList(executionNodeIds)
if err != nil {
return nil, admin.NewInvalidAdminReqParameterError("execution-node-ids", err.Error(), executionNodeIdsIn)
}

for _, en := range requestedENIdentifiers {
id, exists := allIdentities.ByNodeID(en)
if !exists {
return nil, admin.NewInvalidAdminReqParameterError("execution-node-ids", "could not found execution nodes by provided ids", executionNodeIdsIn)
UlyanaAndrukhiv marked this conversation as resolved.
Show resolved Hide resolved
}
ids = append(ids, id)
}
default:
return nil, admin.NewInvalidAdminReqParameterError("execution-node-ids", "must be a list of string", executionNodeIdsIn)
UlyanaAndrukhiv marked this conversation as resolved.
Show resolved Hide resolved
}

return ids, nil
}

// storeTransactionResultErrorMessages saves retrieved error messages for a given block ID.
//
// No errors are expected during normal operation.
func (b *BackfillTxErrorMessagesCommand) storeTransactionResultErrorMessages(
blockID flow.Identifier,
errorMessagesResponses []*execproto.GetTransactionErrorMessagesResponse_Result,
execNode *flow.IdentitySkeleton,
) error {
errorMessages := make([]flow.TransactionResultErrorMessage, 0, len(errorMessagesResponses))
for _, value := range errorMessagesResponses {
errorMessage := flow.TransactionResultErrorMessage{
ErrorMessage: value.ErrorMessage,
TransactionID: convert.MessageToIdentifier(value.TransactionId),
Index: value.Index,
ExecutorID: execNode.NodeID,
}
errorMessages = append(errorMessages, errorMessage)
}

err := b.txErrorMessages.Store(blockID, errorMessages)
if err != nil {
return fmt.Errorf("failed to store transaction error messages: %w", err)
}

return nil
}
Loading
Loading