-
Notifications
You must be signed in to change notification settings - Fork 178
/
backend_scripts.go
136 lines (119 loc) · 4.35 KB
/
backend_scripts.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package backend
import (
"context"
"github.com/hashicorp/go-multierror"
execproto "github.com/onflow/flow/protobuf/go/flow/execution"
"github.com/rs/zerolog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/state/protocol"
"github.com/onflow/flow-go/storage"
)
type backendScripts struct {
headers storage.Headers
executionReceipts storage.ExecutionReceipts
state protocol.State
connFactory ConnectionFactory
log zerolog.Logger
}
func (b *backendScripts) ExecuteScriptAtLatestBlock(
ctx context.Context,
script []byte,
arguments [][]byte,
) ([]byte, error) {
// get the latest sealed header
latestHeader, err := b.state.Sealed().Head()
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get latest sealed header: %v", err)
}
// get the block id of the latest sealed header
latestBlockID := latestHeader.ID()
// execute script on the execution node at that block id
return b.executeScriptOnExecutionNode(ctx, latestBlockID, script, arguments)
}
func (b *backendScripts) ExecuteScriptAtBlockID(
ctx context.Context,
blockID flow.Identifier,
script []byte,
arguments [][]byte,
) ([]byte, error) {
// execute script on the execution node at that block id
return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments)
}
func (b *backendScripts) ExecuteScriptAtBlockHeight(
ctx context.Context,
blockHeight uint64,
script []byte,
arguments [][]byte,
) ([]byte, error) {
// get header at given height
header, err := b.headers.ByHeight(blockHeight)
if err != nil {
err = convertStorageError(err)
return nil, err
}
blockID := header.ID()
// execute script on the execution node at that block id
return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments)
}
// executeScriptOnExecutionNode forwards the request to the execution node using the execution node
// grpc client and converts the response back to the access node api response format
func (b *backendScripts) executeScriptOnExecutionNode(
ctx context.Context,
blockID flow.Identifier,
script []byte,
arguments [][]byte,
) ([]byte, error) {
execReq := execproto.ExecuteScriptAtBlockIDRequest{
BlockId: blockID[:],
Script: script,
Arguments: arguments,
}
// find few execution nodes which have executed the block earlier and provided an execution receipt for it
execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find execution nodes at blockId %v: %v", blockID.String(), err)
}
// try each of the execution nodes found
var errors *multierror.Error
// try to execute the script on one of the execution nodes
for _, execNode := range execNodes {
result, err := b.tryExecuteScript(ctx, execNode, execReq)
if err == nil {
b.log.Debug().
Str("execution_node", execNode.String()).
Hex("block_id", blockID[:]).
Str("script", string(script)).
Msg("Successfully executed script")
return result, nil
}
// return OK status if it's just a script failure as opposed to an EN failure
if status.Code(err) == codes.InvalidArgument {
b.log.Debug().Err(err).
Str("execution_node", execNode.String()).
Hex("block_id", blockID[:]).
Str("script", string(script)).
Msg("script failed to execute on the execution node")
return nil, status.Errorf(codes.OK, "failed to execute script on execution node %v", execNode.String())
}
errors = multierror.Append(errors, err)
}
errToReturn := errors.ErrorOrNil()
if errToReturn != nil {
b.log.Error().Err(err).Msg("script execution failed for execution node internal reasons")
}
return nil, errToReturn
}
func (b *backendScripts) tryExecuteScript(ctx context.Context, execNode *flow.Identity, req execproto.ExecuteScriptAtBlockIDRequest) ([]byte, error) {
execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create client for execution node %s: %v", execNode.String(), err)
}
defer closer.Close()
execResp, err := execRPCClient.ExecuteScriptAtBlockID(ctx, &req)
if err != nil {
return nil, status.Errorf(status.Code(err), "failed to execute the script on the execution node %s: %v", execNode.String(), err)
}
return execResp.GetValue(), nil
}