-
Notifications
You must be signed in to change notification settings - Fork 115
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
[CT-646] stream offchain updates through stream manager #1138
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -961,7 +961,9 @@ func New( | |
clobFlags := clobflags.GetClobFlagValuesFromOptions(appOpts) | ||
logger.Info("Parsed CLOB flags", "Flags", clobFlags) | ||
|
||
memClob := clobmodulememclob.NewMemClobPriceTimePriority(app.IndexerEventManager.Enabled()) | ||
memClob := clobmodulememclob.NewMemClobPriceTimePriority( | ||
app.IndexerEventManager.Enabled() || app.GrpcStreamingManager.Enabled(), | ||
) | ||
|
||
app.ClobKeeper = clobmodulekeeper.NewKeeper( | ||
appCodec, | ||
|
@@ -1758,8 +1760,5 @@ func getGrpcStreamingManagerFromOptions( | |
logger log.Logger, | ||
) (manager streamingtypes.GrpcStreamingManager) { | ||
// TODO(CT-625): add command line flags for full node streaming. | ||
if appFlags.NonValidatingFullNode { | ||
return streaming.NewGrpcStreamingManager() | ||
} | ||
return streaming.NewNoopGrpcStreamingManager() | ||
return streaming.NewGrpcStreamingManager() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The gRPC server is initialized without specifying credentials, which could lead to insecure communication. It's important to ensure that all gRPC communication is encrypted to prevent eavesdropping and tampering by malicious actors. Consider adding SSL/TLS credentials to the gRPC server initialization. You can create credentials using grpc.NewServer(grpc.Creds(credentials.NewServerTLSFromFile("cert.pem", "cert.key"))) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately the ec2 instance I am running this on does not support filesystem reads and writes. Can I paste the contents of my .pem file in the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jonfung-dydx Embedding the contents of your .pem file directly in the code is not recommended due to security concerns. A more secure approach would be to use environment variables or a secrets management service provided by your cloud provider. This way, you can securely manage the .pem file contents without exposing them in your source code. For example, you could load the .pem file contents into an environment variable and then access it in your code like so: cert := os.Getenv("CERT_PEM_CONTENTS")
key := os.Getenv("KEY_PEM_CONTENTS")
creds, err := credentials.NewServerTLSFromString(cert, key)
if err != nil {
// Handle error
}
grpcServer := grpc.NewServer(grpc.Creds(creds)) Make sure to securely set the environment variables There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately I have no money to use AWS secrets manager. What should I do here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jonfung-dydx If AWS Secrets Manager is not an option due to cost concerns, you might consider the following approaches to securely manage your .pem file contents for the gRPC server:
Each of these methods has its own set of considerations, especially regarding the secure handling of encryption keys and secret values. It's important to evaluate them in the context of your application's security requirements and operational environment. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
package grpc | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/cosmos/gogoproto/proto" | ||
ocutypes "github.com/dydxprotocol/v4-chain/protocol/indexer/off_chain_updates/types" | ||
"github.com/dydxprotocol/v4-chain/protocol/streaming/grpc/types" | ||
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" | ||
) | ||
|
@@ -9,10 +13,23 @@ var _ types.GrpcStreamingManager = (*GrpcStreamingManagerImpl)(nil) | |
|
||
// GrpcStreamingManagerImpl is an implementation for managing gRPC streaming subscriptions. | ||
type GrpcStreamingManagerImpl struct { | ||
sync.Mutex | ||
jonfung-dydx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// orderbookSubscriptions maps subscription IDs to their respective orderbook subscriptions. | ||
orderbookSubscriptions map[uint32]*OrderbookSubscription | ||
nextSubscriptionId uint32 | ||
} | ||
|
||
// OrderbookSubscription represents a active subscription to the orderbook updates stream. | ||
type OrderbookSubscription struct { | ||
clobPairIds []uint32 | ||
srv clobtypes.Query_StreamOrderbookUpdatesServer | ||
} | ||
|
||
func NewGrpcStreamingManager() *GrpcStreamingManagerImpl { | ||
return &GrpcStreamingManagerImpl{} | ||
return &GrpcStreamingManagerImpl{ | ||
orderbookSubscriptions: make(map[uint32]*OrderbookSubscription), | ||
} | ||
} | ||
|
||
func (sm *GrpcStreamingManagerImpl) Enabled() bool { | ||
|
@@ -24,15 +41,92 @@ func (sm *GrpcStreamingManagerImpl) Subscribe( | |
req clobtypes.StreamOrderbookUpdatesRequest, | ||
srv clobtypes.Query_StreamOrderbookUpdatesServer, | ||
) ( | ||
finished chan bool, | ||
err error, | ||
) { | ||
return nil, nil | ||
clobPairIds := req.GetClobPairId() | ||
|
||
// Perform some basic validation on the request. | ||
if len(clobPairIds) == 0 { | ||
return clobtypes.ErrInvalidGrpcStreamingRequest | ||
} | ||
|
||
subscription := &OrderbookSubscription{ | ||
clobPairIds: clobPairIds, | ||
srv: srv, | ||
} | ||
|
||
sm.Lock() | ||
defer sm.Unlock() | ||
|
||
sm.orderbookSubscriptions[sm.nextSubscriptionId] = subscription | ||
sm.nextSubscriptionId++ | ||
|
||
return nil | ||
} | ||
|
||
// SendOrderbookUpdates groups updates by their clob pair ids and | ||
// sends messages to the subscribers. | ||
func (sm *GrpcStreamingManagerImpl) SendOrderbookUpdates( | ||
updates *clobtypes.OffchainUpdates, | ||
offchainUpdates *clobtypes.OffchainUpdates, | ||
) { | ||
// Group updates by clob pair id. | ||
updates := make(map[uint32]*clobtypes.OffchainUpdates) | ||
for _, message := range offchainUpdates.Messages { | ||
clobPairId := message.OrderId.ClobPairId | ||
if _, ok := updates[clobPairId]; !ok { | ||
updates[clobPairId] = clobtypes.NewOffchainUpdates() | ||
} | ||
updates[clobPairId].Messages = append(updates[clobPairId].Messages, message) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't have to be now, but do we want to condense the messages somehow? We have this condensing function for indexer, but i don't think it works in this case. these events might be condensed differently.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this gets called before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It gets called during Replay Operations but i feel it might not be used half the time due to this code:
I think it should be fine, unless you can think of any edge case where condensing messages drops anything vital for orderbook updates There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah should be fine to send for example (place + remove) for example |
||
|
||
// Unmarshal messages to v1 updates. | ||
v1updates := make(map[uint32][]ocutypes.OffChainUpdateV1) | ||
for clobPairId, update := range updates { | ||
v1update, err := GetOffchainUpdatesV1(update) | ||
if err != nil { | ||
panic(err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i believe golang kills the whole process on a panic. Would this lead to the whole validator going bye bye? Do we want to have a recover? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I was trying to see what makes sense here - based on my understanding, MMs will run their own full nodes and these full nodes are only used for streaming information to their trading strategies. in this case, if something happens, probably not important to recover? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let me know your thoughts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm that sounds good to me. but we should make this extra clear in the grpcstream enabled flag description and in the docs that this feature is intended to be used on full nodes? Would it even make sense to make sense to add validation that grpcstream can only be enabled on full nodes? Might be overkill. I would be a little suspicious if a consensus-participating validator had this stream turned on of mev. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup - planning to add that in my next PR |
||
} | ||
v1updates[clobPairId] = v1update | ||
} | ||
|
||
sm.Lock() | ||
defer sm.Unlock() | ||
|
||
// Send updates to subscribers. | ||
idsToRemove := make([]uint32, 0) | ||
for id, subscription := range sm.orderbookSubscriptions { | ||
jonfung-dydx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, clobPairId := range subscription.clobPairIds { | ||
if updates, ok := v1updates[clobPairId]; ok { | ||
if err := subscription.srv.Send( | ||
&clobtypes.StreamOrderbookUpdatesResponse{ | ||
Updates: updates, | ||
Snapshot: false, | ||
}, | ||
); err != nil { | ||
idsToRemove = append(idsToRemove, id) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Clean up subscriptions that have been closed. | ||
jayy04 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// If a Send update has failed for any clob pair id, the whole subscription will be removed. | ||
for _, id := range idsToRemove { | ||
delete(sm.orderbookSubscriptions, id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure that this delete will properly clean up the nested inner data structures? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like there's no close function 🆗 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah wasn't able to find a close function either not really sure if we need to do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/grpc/grpc-go/blob/v1.60.x/stream.go#L262-L278
I'm assuming an error will be generated by SendMsg in this case so the cancel function will be called. |
||
} | ||
} | ||
|
||
// GetOffchainUpdatesV1 unmarshals messages in offchain updates to OffchainUpdateV1. | ||
func GetOffchainUpdatesV1(offchainUpdates *clobtypes.OffchainUpdates) ([]ocutypes.OffChainUpdateV1, error) { | ||
v1updates := make([]ocutypes.OffChainUpdateV1, 0) | ||
for _, message := range offchainUpdates.Messages { | ||
var update ocutypes.OffChainUpdateV1 | ||
err := proto.Unmarshal(message.Message.Value, &update) | ||
if err != nil { | ||
return nil, err | ||
} | ||
v1updates = append(v1updates, update) | ||
} | ||
return v1updates, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1241,6 +1241,8 @@ func (k Keeper) SendOffchainMessages( | |
} | ||
k.GetIndexerEventManager().SendOffchainData(update) | ||
} | ||
|
||
k.GetGrpcStreamingManager().SendOrderbookUpdates(offchainUpdates) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The addition of Ensure proper validation and error handling within |
||
} | ||
|
||
// getFillQuoteQuantums returns the total fillAmount price in quote quantums based on the maker subticks. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will add the if statement back in in the next PR