github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/backend_scripts.go (about) 1 package backend 2 3 import ( 4 "context" 5 "crypto/md5" //nolint:gosec 6 "time" 7 8 lru "github.com/hashicorp/golang-lru" 9 10 "github.com/hashicorp/go-multierror" 11 execproto "github.com/onflow/flow/protobuf/go/flow/execution" 12 "github.com/rs/zerolog" 13 "google.golang.org/grpc/codes" 14 "google.golang.org/grpc/status" 15 16 "github.com/koko1123/flow-go-1/engine/common/rpc" 17 "github.com/koko1123/flow-go-1/model/flow" 18 "github.com/koko1123/flow-go-1/module" 19 "github.com/koko1123/flow-go-1/state/protocol" 20 "github.com/koko1123/flow-go-1/storage" 21 ) 22 23 // uniqueScriptLoggingTimeWindow is the duration for checking the uniqueness of scripts sent for execution 24 const uniqueScriptLoggingTimeWindow = 10 * time.Minute 25 26 type backendScripts struct { 27 headers storage.Headers 28 executionReceipts storage.ExecutionReceipts 29 state protocol.State 30 connFactory ConnectionFactory 31 log zerolog.Logger 32 metrics module.BackendScriptsMetrics 33 loggedScripts *lru.Cache 34 } 35 36 func (b *backendScripts) ExecuteScriptAtLatestBlock( 37 ctx context.Context, 38 script []byte, 39 arguments [][]byte, 40 ) ([]byte, error) { 41 42 // get the latest sealed header 43 latestHeader, err := b.state.Sealed().Head() 44 if err != nil { 45 return nil, status.Errorf(codes.Internal, "failed to get latest sealed header: %v", err) 46 } 47 48 // get the block id of the latest sealed header 49 latestBlockID := latestHeader.ID() 50 51 // execute script on the execution node at that block id 52 return b.executeScriptOnExecutionNode(ctx, latestBlockID, script, arguments) 53 } 54 55 func (b *backendScripts) ExecuteScriptAtBlockID( 56 ctx context.Context, 57 blockID flow.Identifier, 58 script []byte, 59 arguments [][]byte, 60 ) ([]byte, error) { 61 // execute script on the execution node at that block id 62 return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments) 63 } 64 65 func (b *backendScripts) ExecuteScriptAtBlockHeight( 66 ctx context.Context, 67 blockHeight uint64, 68 script []byte, 69 arguments [][]byte, 70 ) ([]byte, error) { 71 // get header at given height 72 header, err := b.headers.ByHeight(blockHeight) 73 if err != nil { 74 err = rpc.ConvertStorageError(err) 75 return nil, err 76 } 77 78 blockID := header.ID() 79 80 // execute script on the execution node at that block id 81 return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments) 82 } 83 84 // executeScriptOnExecutionNode forwards the request to the execution node using the execution node 85 // grpc client and converts the response back to the access node api response format 86 func (b *backendScripts) executeScriptOnExecutionNode( 87 ctx context.Context, 88 blockID flow.Identifier, 89 script []byte, 90 arguments [][]byte, 91 ) ([]byte, error) { 92 93 execReq := &execproto.ExecuteScriptAtBlockIDRequest{ 94 BlockId: blockID[:], 95 Script: script, 96 Arguments: arguments, 97 } 98 99 // find few execution nodes which have executed the block earlier and provided an execution receipt for it 100 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 101 if err != nil { 102 return nil, status.Errorf(codes.Internal, "failed to find execution nodes at blockId %v: %v", blockID.String(), err) 103 } 104 // encode to MD5 as low compute/memory lookup key 105 // CAUTION: cryptographically insecure md5 is used here, but only to de-duplicate logs. 106 // *DO NOT* use this hash for any protocol-related or cryptographic functions. 107 insecureScriptHash := md5.Sum(script) //nolint:gosec 108 109 // try each of the execution nodes found 110 var errors *multierror.Error 111 // try to execute the script on one of the execution nodes 112 for _, execNode := range execNodes { 113 execStartTime := time.Now() // record start time 114 result, err := b.tryExecuteScript(ctx, execNode, execReq) 115 if err == nil { 116 if b.log.GetLevel() == zerolog.DebugLevel { 117 executionTime := time.Now() 118 if b.shouldLogScript(executionTime, insecureScriptHash) { 119 b.log.Debug(). 120 Str("execution_node", execNode.String()). 121 Hex("block_id", blockID[:]). 122 Hex("script_hash", insecureScriptHash[:]). 123 Str("script", string(script)). 124 Msg("Successfully executed script") 125 b.loggedScripts.Add(insecureScriptHash, executionTime) 126 } 127 } 128 129 // log execution time 130 b.metrics.ScriptExecuted( 131 time.Since(execStartTime), 132 len(script), 133 ) 134 135 return result, nil 136 } 137 // return if it's just a script failure as opposed to an EN failure and skip trying other ENs 138 if status.Code(err) == codes.InvalidArgument { 139 b.log.Debug().Err(err). 140 Str("execution_node", execNode.String()). 141 Hex("block_id", blockID[:]). 142 Hex("script_hash", insecureScriptHash[:]). 143 Str("script", string(script)). 144 Msg("script failed to execute on the execution node") 145 return nil, err 146 } 147 errors = multierror.Append(errors, err) 148 } 149 errToReturn := errors.ErrorOrNil() 150 if errToReturn != nil { 151 b.log.Error().Err(err).Msg("script execution failed for execution node internal reasons") 152 } 153 return nil, errToReturn 154 } 155 156 // shouldLogScript checks if the script hash is unique in the time window 157 func (b *backendScripts) shouldLogScript(execTime time.Time, scriptHash [16]byte) bool { 158 rawTimestamp, seen := b.loggedScripts.Get(scriptHash) 159 if !seen || rawTimestamp == nil { 160 return true 161 } else { 162 // safe cast 163 timestamp := rawTimestamp.(time.Time) 164 return execTime.Sub(timestamp) >= uniqueScriptLoggingTimeWindow 165 } 166 } 167 168 func (b *backendScripts) tryExecuteScript(ctx context.Context, execNode *flow.Identity, req *execproto.ExecuteScriptAtBlockIDRequest) ([]byte, error) { 169 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 170 if err != nil { 171 return nil, status.Errorf(codes.Internal, "failed to create client for execution node %s: %v", execNode.String(), err) 172 } 173 defer closer.Close() 174 175 execResp, err := execRPCClient.ExecuteScriptAtBlockID(ctx, req) 176 if err != nil { 177 if status.Code(err) == codes.Unavailable { 178 b.connFactory.InvalidateExecutionAPIClient(execNode.Address) 179 } 180 return nil, status.Errorf(status.Code(err), "failed to execute the script on the execution node %s: %v", execNode.String(), err) 181 } 182 return execResp.GetValue(), nil 183 }