You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The AcceptListGrpcQuerier is not concurrent-safe. This leads to the state when one query changes the result of another. Or even worse, when a query changes the state of the transaction that uses the query (leads to consensus failure).
The AcceptListGrpcQuerier accepts the map of the [request.Path]Response that is a singleton for the app. Hence there is a chance, when you query a smart contract (which uses the GRPC query to query the chain) to get the result of the query which is processing in parallel.
Example with step-by-step comments:
// 2 goroutines read bank balance of acc1 and acc2 concurrently, so they both get BalanceResponse{}protoResponse, accepted:=acceptList[request.Path]
if!accepted {
returnnil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)}
}
// the rote is also the same for both goroutinesroute:=queryRouter.Route(request.Path)
ifroute==nil {
returnnil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)}
}
// the requests are different, so the res is different for each goroutineres, err:=route(ctx, &abci.RequestQuery{
Data: request.Data,
Path: request.Path,
})
iferr!=nil {
returnnil, err
}
// but here, becasue of the race condition, one goroutine can get the result of another goroutinereturnConvertProtoToJSONMarshal(codec, protoResponse, res.Value)
The best solution for the issue is to change the signature of the AcceptedQueries, and instead of the proto.Message, use the func()proto.Message to use a unique proto response per-goroutine/request.
AcceptedQueries map[string]func()proto.Message
Or, add a synchronization (locks), which is not the best solution, because it can be a bottleneck for the app.
Or, add a deep copy of the proto object before using it, which is not the best solution, because it might not work with all possible implementations of the proto.Message interface.
The text was updated successfully, but these errors were encountered:
Great finding! So, basically the problem is that we are using the same Response object per path, right?
I agree with your three possible solutions.
The best solution for the issue is to change the signature of the AcceptedQueries, and instead of the proto.Message, use the func()proto.Message to use a unique proto response per-goroutine/request.
That requires an (addmitedly small) breaking change. Not the end of the world, but nice if we can avoid it.
Or, add a deep copy of the proto object before using it, which is not the best solution, because it might not work with all possible implementations of the proto.Message interface.
So, basically calling proto.Clone on the message in the map. Do you have an example where that would not work?
Because I think this would be the ideal solution otherwise.
But if you parse the proto-files to find a tag "cosmos.query.v1.module_query_safe" and build the results dynamically using "google.golang.org/protobuf/types/dynamicpb", for example. The usage of the proto.Clone leads to panic. Because of the parallel access on the map used in the dynamicpb,
But it might be too specific use case. For such the developers can use their own implementation of the querier.
AcceptListGrpcQuerier is not concurrent-safe
Issue description
The AcceptListGrpcQuerier is not concurrent-safe. This leads to the state when one query changes the result of another. Or even worse, when a query changes the state of the transaction that uses the query (leads to consensus failure).
The
AcceptListGrpcQuerier
accepts the map of the[request.Path]Response
that is a singleton for the app. Hence there is a chance, when you query a smart contract (which uses the GRPC query to query the chain) to get the result of the query which is processing in parallel.Example with step-by-step comments:
Test that proves the statement:
The output is:
Solutions
AcceptedQueries
, and instead of theproto.Message
, use thefunc()proto.Message
to use a unique proto response per-goroutine/request.AcceptedQueries map[string]func()proto.Message
Or, add a synchronization (locks), which is not the best solution, because it can be a bottleneck for the app.
Or, add a deep copy of the proto object before using it, which is not the best solution, because it might not work with all possible implementations of the
proto.Message
interface.The text was updated successfully, but these errors were encountered: