-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
gRPC support #3159
gRPC support #3159
Conversation
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.
Didn't look at the C++ code, only mainly at the proto files.
I hope I don't come across as too negative, I'm actually happy that rippled
will get a gRPC API. I'm just a bit concerned about how it'll play out and some details. In general I like the concept.
.gitmodules
Outdated
@@ -0,0 +1,3 @@ | |||
[submodule "grpc"] |
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.
Why a submodule, not a subtree as every other vendored dependency in this repository?
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.
good point - we'll be fixing that shortly (to make grpc consistent with our other third party deps).
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.
changed
Builds/CMake/RippledCore.cmake
Outdated
@@ -134,6 +134,8 @@ target_include_directories (xrpl_core | |||
PUBLIC | |||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> | |||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/ripple> | |||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/grpc> | |||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/proto> |
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.
Why a top level subfolder, not a folder in /src
?
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.
you are right, I think the proto files should be moved to /src/ripple/proto/rpc/v1
. Also, grpc
will be removed as a submodule most likely
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.
moved to src/ripple/proto/rpc/v1
Builds/CMake/RippledCore.cmake
Outdated
exclude_if_included (rippled) | ||
# define a macro for tests that might need to | ||
# be exluded or run differently in CI environment | ||
if (is_ci) | ||
target_compile_definitions(rippled PRIVATE RIPPLED_RUNNING_IN_CI) | ||
endif () | ||
|
||
if(NOT WIN32) | ||
set_source_files_properties(src/ripple/unity/app_main1.cpp PROPERTIES COMPILE_FLAGS -Wno-suggest-override) |
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.
Would be nice to know why -Wno-suggest-override is enabled everywhere here. Maybe in a comment?
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.
This is because when grpc was included as a submodule, building grpc generated a lot of warnings (all suggest-override warnings). However, since grpc will most likely be removed as a submodule (and included in a different way), suppressing these warnings will not be necessary
proto/rpc/v1/account_info.proto
Outdated
// A request to get info about an account. | ||
message GetAccountInfoRequest { | ||
// The address to get info about. | ||
string address = 1; |
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.
This is called "account" in https://xrpl.org/account_info.html and described as "most commonly", not "always" being the address.
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 change to "account"
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.
changed
proto/rpc/v1/account_info.proto
Outdated
bool strict = 2; | ||
|
||
oneof ledger_index { | ||
string ledger_index_shortcut_string = 3; |
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.
This can only be one of "validated", "closed" or "current", might be worth a separate type instead of a full string.
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.
Good idea. I could just use an enum
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.
It's not obvious that a "shortcut_string" is "validated" or "closed". I know that this is going to be changed to an enum, but I have two pieces of feedback:
- We should document every fields in these proto buf files, including strings encode data
- I would not add a type suffix to field names. I.e. no
_string
here.
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.
changed to an enum
proto/rpc/v1/transaction.proto
Outdated
} | ||
|
||
message Payment { | ||
// The amount of currency to pay, in either fiat or XRP. |
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.
// The amount of currency to pay, in either fiat or XRP. | |
// The amount of currency to pay, in either issued currency or XRP. |
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.
changed
proto/rpc/v1/transaction.proto
Outdated
string issuer = 3; | ||
} | ||
|
||
message Payment { |
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.
Tons more fields on this transaction type, it's not the easiest one for a start. ;-)
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.
I believe (and CJ can correct me) that this is at least the basics we would need to start building XRP transactions here. We just need to make sure what we have is extensible and doesn't cause breaking changes in the future.
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.
I'm just going to add the missing fields. SendMax and Paths will be moved from the Transaction type to here. I see that DestinationTag, InvoiceID and DeliverMin are missing. Any others?
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.
added fields, lmk if i missed any (non-deprecated) fields
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.
Are these fields getting added?
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.
The fields to be added were added. If there are fields missing, please point them out. Note though, we are only supporting Payment transactions at this time, so some fields necessary for other transaction types were not added.
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.
Fields missing; TransactionType, AccountTxnID. But everything else looks good. Transaction, TX and meta. You're returning all the necessary information via messages.
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.
TransactionType is omitted, since the type is defined via protobuf; each transaction type has it's own unique protobuf message type.
You are right that AccountTxnID is missing from the Transaction message. I added it to the AccountRoot ledger object message, but missed it in the Transaction message. I'll add that now; thanks for pointing it out.
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.
Oh cool. So I'm looking to add Escrow next. Seems I would follow the message format of the Payment protobuf? I can help out as my use case is wrapping up and Escrow is next.
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.
@dangell7 I added AccountTxnID (naming is a bit different though).
As to adding Escrow: I assume you mean adding protobuf messages for EscrowCreate, EscrowFinish and EscrowCancel. You are free to do so and create a PR, and yes, follow the same format as the Payment message, but you are going to have to also implement that functionality on the C++ side as well, in addition to adding the protobuf messages. Also, you would need to also add support (in protobuf and C++) for all ledger objects that those Escrow transaction types affect, in order to return complete meta for those Escrow transactions.
proto/rpc/v1/tx.proto
Outdated
bytes hash = 1; | ||
|
||
// if true, return data in binary format | ||
// TODO: does this make sense for protobuf? |
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.
...maaaybe? A client could always just encode the result themselves, but it might be nice to know what the server is actually working with.
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.
@keefertaylor what do you think about this? Is there any benefit to returning this data in binary format? protobuf is already a binary format over the wire and is heavily optimized, so I don't think it makes sense to provide a binary option for efficiency reasons. But maybe there are other reasons a client would want the data in binary form.
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.
As far as I can tell I don't think there is.
It would be easiest to leave this off for now, and if we find a use case we really must have later we can always add it back in (conversely, it's harder to remove stuff).
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.
A simple use case would be that this is the bulk of the data one needs to verify hashes. If data only arrives in some deserialized format, one first needs tom implement a serializer to verify incoming data.
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.
I kept this and removed the comment
proto/rpc/v1/tx.proto
Outdated
bytes tx_bytes = 2; | ||
}; | ||
// Sequence number of ledger that contains this transaction | ||
uint64 ledger_index = 3; |
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.
Usually you used uint32 for this one elsewhere so far.
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.
Good catch. I am wondering whether it makes sense to define a separate type for this, so that way one does not have to remember that ledger sequence numbers are uint32
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.
changed to uint32
proto/rpc/v1/xrp_ledger.proto
Outdated
|
||
|
||
// RPCs available to interact with the XRP Ledger. | ||
service XRPLedgerAPIService { |
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.
No error handling so far, I guess that'll come later?
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.
Could you elaborate on what you'd expect to see here?
Obviously, gRPC supports plain error codes and descriptions. There's the advanced error model, which isn't really standard (yet): https://cloud.google.com/apis/design/errors#error_model.
If we really needed to encode error details, we could always roll them into the response metadata?
Or are you asking for their to be a generic ResponseMessage
, that looks like:
message Response {
oneof {
google.protobuf.Any response = 1
google.rpc.Status error = 2;
}
}
message RippledError {
// ...
}
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.
Error handling is built into grpc in a way. I'm using these status codes, along with string messages: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
If you look at the c++ handler code, you can see how I'm handling errors. For instance, if one passes a non-existent account to the GetAccountInfo RPC, I return status code NOT_FOUND, with message "account not found". gRPC will return this status code and message instead of the defined response type.
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.
@MarkusTeufelberger lmk what you think here or just resolve this
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.
This is a comment, it can't be "resolved" on GitHub. Also I'm not in the Ripple team, so I also don't have some moderation tools available that project owners might have.
If the response to errors looks like in JSONRPC I guess it's fine.
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.
Only really looked at the .proto files.
proto/rpc/v1/account_info.proto
Outdated
import "rpc/v1/ledger_objects.proto"; | ||
|
||
// A request to get info about an account. | ||
message GetAccountInfoRequest { |
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.
Just FWIW, Google's naming conventions also recommend the Get
prefix (https://cloud.google.com/apis/design/naming_convention#method_names). Uber's prototool also seems to implicitly support this as well: https://github.com/uber/prototool/tree/dev/style#servicesrpcs.
Just because other folks are using get
doesn't mean we have to. I do think that (AFAIK) these all are pretty widely used style guides which makes it somewhat idiomatic to folks who work with gRPC.
proto/rpc/v1/amount.proto
Outdated
|
||
package rpc.v1; | ||
|
||
message CurrencyAmount { |
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.
This is a bad abstraction which is my fault. Supportive of moving it to be a single field called IssuedCurrencyAmount
.
proto/rpc/v1/amount.proto
Outdated
// A representation of an amount of XRP. | ||
message XRPAmount { | ||
// A numeric string representing the number of drops of XRP. | ||
uint64 drops = 1; |
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.
I think the important thing is that this is a typed number (rather than an untyped uint64 or untyped string). That way developers have to consciously spell out the word drops
rather than guessing at the unit. I suppose we could remove this message and have each field be postfixed with drops
?
Regardless, XRPDrops sounds reasonable to me. WDYT?
proto/rpc/v1/amount.proto
Outdated
string value = 2; | ||
|
||
// Unique account address of the entity issuing the currency. | ||
string issuer = 3; |
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.
I think this matches up with the pattern we are using with XRPAmount
, and does make it safer for developers to understand what's going on. Happy to hear thoughts from others.
proto/rpc/v1/fee.proto
Outdated
|
||
} | ||
|
||
//Response to a GetFee RPC |
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.
nit: add a space after //
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.
added space
proto/rpc/v1/transaction.proto
Outdated
|
||
uint32 last_ledger_sequence = 8; | ||
|
||
repeated Path paths = 9; |
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.
+1, I think this lives in payments.
proto/rpc/v1/transaction.proto
Outdated
string issuer = 3; | ||
} | ||
|
||
message Payment { |
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.
I believe (and CJ can correct me) that this is at least the basics we would need to start building XRP transactions here. We just need to make sure what we have is extensible and doesn't cause breaking changes in the future.
proto/rpc/v1/tx.proto
Outdated
message TxResponse { | ||
|
||
// The actual transaction | ||
oneof sttx { |
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.
Not sure what sttx
means. Would recommend dropping the abbreviation.
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.
It's a naming convention I copied from rippled (think it means Serialized Type Tx), though I agree that it is very cryptic. What do you think is a good name for the one of? I will probably expand tx and tx_bytes to transaction and transaction_bytes btw.
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.
I could call it serialized_transaction
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.
changed to serialized_transaction
proto/rpc/v1/tx.proto
Outdated
bool validated = 5; | ||
|
||
// metadata about the transaction | ||
oneof stmeta { |
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.
ditto
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.
What do you think is a good name for this? I could call it serialized_meta.
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.
changed to serialized_meta
proto/rpc/v1/xrp_ledger.proto
Outdated
|
||
|
||
// RPCs available to interact with the XRP Ledger. | ||
service XRPLedgerAPIService { |
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.
Could you elaborate on what you'd expect to see here?
Obviously, gRPC supports plain error codes and descriptions. There's the advanced error model, which isn't really standard (yet): https://cloud.google.com/apis/design/errors#error_model.
If we really needed to encode error details, we could always roll them into the response metadata?
Or are you asking for their to be a generic ResponseMessage
, that looks like:
message Response {
oneof {
google.protobuf.Any response = 1
google.rpc.Status error = 2;
}
}
message RippledError {
// ...
}
Great job on this @cjcobb23! You got up to speed very quickly and submitted an ambitions addition to rippled. I'll have detailed feedback later, but I wanted to give some initial feedback high-level feedback:
|
@seelabs About the strings interface: there is no difference between strings and bytes over the wire; both are encoded the same way and use the same amount of space. When using strings, protobuf enforces the invariant that each character is UTF-8. Since there is no difference in encoding, I decided that strings were a better type for data that is meant to be human readable. The string type signals that the data is meant to be readable, and the UTF-8 invariant enforces that the data actually is readable. Btw, with the C++ api, bytes and strings are both represented as std::string. In the API for other languages however, strings are represented differently and it is (supposedly) more convenient to work with strings than bytes when the data is meant to be readable (see here: https://groups.google.com/forum/#!topic/protobuf/au6eQBkRT5s ) @keefertaylor what do you think about this? I'll move I agree that merging partial functionality is less than ideal; however, providing full support for the Tx command would require expanding on the proto files quite a bit, as well as writing a lot more handler code. @keefertaylor is going to use this to develop Xpring SDK's, and doesn't need the full Tx functionality (correct me if I'm wrong here keefer), so in the interest of time, I just implemented what is necessary for Payments. About the commits, I do see the benefits of organizing the commits better, though I'm not sure that squashing something this big into one commit makes sense. I could squash this into several commits (say 10 commits as opposed to 61), though doing that now would require me to force-push to this branch. Should I reorganize the commits and force-push, or just leave as is, and remember to better organize the commits for future PRs? I appreciate the praise. @pwang200 and @p2peer deserve some credit as well, as they helped write the unit tests and work out build issues. Edit: I actually am going to take back what I said about squashing into several commits as opposed to 1. I think it will be difficult to create a handful of sensible commits out of these 61 commits, and its much simpler and less error prone just to squash everything into one commit. Plus, I have some trailing white space, which I could get rid of during the squashing. |
Re: Binary InterfaceWhen I say "string" based I mean this API encodes binary data as ASCII strings, instead of binary data directly. I.e with a hex encoding the 8-bit number 255 is not encoded as one byte, but by the two ASCII characters 'f' and 'f'. Protocol buffers support binary data, why are we encoding it? This is an API to interface between the rippled server and other programming languages. The client may take encoded data from a human and decode it to binary before talking to rippled, and it may encode binary data from rippled before presenting it to a human, but why should the two programming languages talk to each other with encoded data? I do understand this requires encoders and decoders in the client (base58 and hex at least), but it also simplifies the handlers in rippled. I'd much rather have a binary interface to rippled and then build encoded APIs on top of that than the other way around. (As an aside, I understand the rippled json interface requires these codecs, but I'm hoping we can eventually move that outside of rippled and use this new binary interface instead). Unless I'm missing a reason why the interface is using strings, I'd prefer a binary interface. Re: Implementing a subset of functionalityI understand that a fuller API will require more time. I'm OK supporting the small subset of functionality needed by xspring, but with a couple conditions:
|
Re, Strings vs Bytes: In general, I would optimize a public API for useability rather than raw efficiency. For instance, when you return the bytes for an account, are you going to return the raw (unprefixed, unchecksummed) bytes? Or will you add the prefix and checksum and return that as bytes? If the former, now the client has to understand Base58Check encoding and the specific inputs ripple uses, if the latter, then the client still probably has to go read that documentation anyway, then run the base58check encoding. Then, you have to have a Base58Check encoding library in your language and to wire it up. In general, I'd favor centralizing this business logic wherever possible (it's easier for rippled to implement this once than for every developer to implement it). Put succinctly, is saving 20 bytes over the wire a valuable tradeoff for every developer who touches this API needing to go read the documentation to go understand what they have to do to get to an address string, which is what they wanted anyway. As additional data: We're already returning these fields as ASCII in the JSON API - are folks actively complaining about the data or RPC size? |
@keefertaylor I'm not worried about the size of the data on the wire at all. I'm also not arguing that a "string" based API that encodes and decodes from our various formats isn't needed - it absolutely is. Here's where I'm coming from: I've thought about adding a low-level API to rippled, and moving the high-level APIs to a separate server outside of rippled. I was trying to steer this gRPC API into the low-level API that I want. However, I agree: without a high-level gRPC API in place, it's not realistic to force clients to do base58 encoding and decoding themselves. Address at least should be base58 encoded and not raw binary. I'm not sure I agree about hex encoded data though. Why are we encoding hex data instead of sending raw bytes? I also want to give an example why I want to eventually move the encoding out of rippled. Take a look at the code to decode secrets seeds: First it checks if it's been encoded as a ripple lib seed, because that used a non-standard encoding. Next, it checks if it's encoded as a base58 account, node public key, node private key, or account secret, in case someone accidentally sends those instead of a secret. Next it checks if it's encoded as hex. Next it checks if it's encoded as a base 58 seed. Moving this complexity out of rippled is a win. I understand it needs to live somewhere, but I'd like to make rippled itself as stable and secure as possible. |
proto/rpc/v1/amount.proto
Outdated
|
||
// A representation of an amount of XRP. | ||
message XRPAmount { | ||
// A numeric string representing the number of drops of XRP. |
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.
Comment out of date
Cool. I think we've reached agreement on As far as other data, I think I generally agree with you too! I was narrowed in on addresses. I audited the code just now and it appears to (to me) that the only places
I think all of those (modulo the Apologies if you were looking at some of the early prototype files I was using in Xpring. I was building an MVP super quickly and I think CJ has really done a great job at refactoring away my hastily made and incorrect abstractions. Edit: tagging @seelabs to make sure this comment gets visibility. |
@keefertaylor I was looking at the |
@seelabs That field is actually of type |
proto/rpc/v1/transaction.proto
Outdated
} | ||
|
||
// The public key of the account which signed the transaction in hexadecimal. | ||
bytes signing_public_key_hex = 5; |
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.
drop 'hex'
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.
dropped
@seelabs |
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.
I did a pass mostly focusing on the GRPCServer
files. I'll do some more review later, but I think I have enough in this batch that I'd like post them.
There are a couple of formatting nits we need to fix (I didn't flag these individually):
- There are several lines that end with extra whitespace, we need to remove the whitespace
- The
public:
andprivate:
keywords aren't indented like the rest of rippled's code - We put a space after commas in function arguments
Instead of auditing the code and fixing those, I'd just set up clang-format
and use the .clang-format
file in the repo to reformat the new code.
There are also a couple of style nits I'd like to fix:
- There are places where the code uses
snake_case
for variables and functions. We usepascalCase
. - I wouldn't add comments that don't add value, they get in the way of code. It's not a big deal, and there are not that many in this patchset, but I usually flag comments I don't think add value so too many of them don't build up. Examples:
// gRPC server
std::unique_ptr<grpc::Server> server_;
// referernce to ripple::Application
Application& app_;
// hash
bytes hash = 4;
proto/rpc/v1/account_info.proto
Outdated
oneof ledger_index { | ||
string ledger_index_shortcut_string = 3; | ||
uint32 ledger_index_seq = 4; | ||
bytes ledger_hash = 5; |
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.
The field "ledger index" contains things that are not ledger indexes (the shortcut string isn't an index, and this hash isn't an index). I'd rename parent group.
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.
Can we rename the parent group to just "ledger"?
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.
@cjcobb23 What do you think of ledger_id
?
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.
@seelabs I changed this, lmk what you think
proto/rpc/v1/account_info.proto
Outdated
bool strict = 2; | ||
|
||
oneof ledger_index { | ||
string ledger_index_shortcut_string = 3; |
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.
It's not obvious that a "shortcut_string" is "validated" or "closed". I know that this is going to be changed to an enum, but I have two pieces of feedback:
- We should document every fields in these proto buf files, including strings encode data
- I would not add a type suffix to field names. I.e. no
_string
here.
src/ripple/app/main/GRPCServer.h
Outdated
{ | ||
auto ptr = std::make_shared<CallData<Request,Response>>( | ||
service_,*cq_,app_,bl,handler, condition, load_type); | ||
return std::static_pointer_cast<Processor>(ptr); |
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.
There's no need for this cast, we can just return the result of make_shared
.
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 remove this whole function
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.
removed
src/ripple/app/main/GRPCServer.h
Outdated
{ | ||
auto ptr = makeCallData(bl,handler,condition,load_type); | ||
requests_.push_front(ptr); | ||
ptr->set_iter(requests_.begin()); |
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.
We shouldn't be storing the iterator in the object.
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.
removed
src/ripple/basics/strHex.h
Outdated
|
||
//converts data to a string of bytes | ||
template <class T> | ||
std::string toBytes(T const & data) |
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.
I would remove this function. Even if we needed this for protobufs (I don't think we do), this function has too wide of a scope. I'd like to use the void set_foo(const void* value, size_t size);
overloads instead of the string overloads..
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.
We can definitely just use those overloads; that will be more efficient anyways since we don't have to create a std::string
temporary. Will remove this toBytes()
function
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.
removed this function and used the other overloads
src/ripple/app/main/GRPCServer.h
Outdated
BindListener<Request,Response> bl, | ||
Handler<Request,Response> handler, | ||
RPC::Condition condition, | ||
Resource::Charge load_type) |
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.
If we're getting rid of storing the iterator with the object, I'd remove this function.
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 remove this function as well
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.
@seelabs I actually don't think this function should be removed. While we don't need to set the iterator, we still need to add each shared_ptr<CallData>
object to the requests_
collection. Without this function, I would have to write requests_.push_front()
(or whatever insertion method is used) for each RPC, as opposed to just calling this function
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.
We have to call makeAndPush for each RPC now. The difference is we replace code like
makeAndPush<rpc::v1::GetFeeRequest, rpc::v1::GetFeeResponse>(
&rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee,
doFeeGrpc,
RPC::NEEDS_CURRENT_LEDGER,
Resource::feeReferenceRPC
);
with
{
using cd = CallData<rpc::v1::GetFeeRequest, rpc::v1::GetFeeResponse>;
requests_.push_front(std::make_shared<cd>(
service_, *cq_, app_,
&rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee,
doFeeGrpc, RPC::NEEDS_CURRENT_LEDGER, Resource::feeReferenceRPC));
}
You do save repeating the service_, *cq_, app_,
parameters with a helper function, but I'm not sure it's worth defining a helper function just for that. I don't really object if you want to keep a helper function like this, there's not great harm, but I also don't see much benefit either.
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.
removed
src/ripple/app/main/GRPCServer.h
Outdated
BindListener<Request,Response> bl, | ||
Handler<Request,Response> handler, | ||
RPC::Condition condition, | ||
Resource::Charge load_type) |
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.
I'd remove this function and just call make_shared
directly where needed.
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.
removed
src/ripple/app/main/GRPCServer.h
Outdated
// Possible states of the RPC | ||
enum CallStatus { PROCESSING, FINISH }; | ||
// The current serving state. | ||
CallStatus status_; |
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.
I think a bool finished_
or somesuch is clearer. I understand this started with more states, and we pared it down to two, but now that it's two states I'd just use a bool. Tho I guess I don't object too strongly if other reviews are OK with enum
.
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.
I think a bool would be clearer as well, will change
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.
changed to bool
src/ripple/app/main/GRPCServer.h
Outdated
|
||
// iterator pointing to this object in the requests list | ||
// for lifetime management | ||
boost::optional<std::list<std::shared_ptr<Processor>>::iterator> iter_; |
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.
I'd remove iter_
as a member.
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.
removed
proto/rpc/v1/tx.proto
Outdated
// metadata about the transaction | ||
oneof stmeta { | ||
Meta meta = 6; | ||
bytes meta_bytes = 7; |
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.
@seelabs would you also omit the type suffix here? I could change it to meta_binary
, but that's still adding the type as a suffix, even if its not the exact name of the protobuf type.
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.
@cjcobb23 Maybe raw_meta
? I'm OK with _binary
too. Names are hard, maybe someone else has a good suggestion.
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.
changed to meta_binary
and transaction_binary
src/ripple/app/main/GRPCServer.cpp
Outdated
bind_listener_(bind_listener), | ||
handler_(handler), | ||
required_condition_(required_condition), | ||
load_type_(load_type) |
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.
pass by value types can be moved
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.
changed to move
src/ripple/app/main/GRPCServer.cpp
Outdated
Section section = app_.config().section("port_grpc"); | ||
|
||
//get the default values of ip and port | ||
std::size_t colon_pos = server_address_.find(':'); |
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.
I don't think we should have default grpc server addr/port. If it wasn't specified in the config, it's either an error or you just don't start grpc (probably the latter).
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.
@mellery451 I think we should not start grpc if the addr/port is not in the config. However, the unit tests need the grpc server to start, so if someone runs the unit tests without addr/port for grpc in the config, the unit tests will fail.
Edit: I guess we could just detect in the unit test whether the grpc server should be running (probably just test if addr/port for grpc is present in the config), and if its not, just do nothing.
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.
we have a default config that is used for unit tests: https://github.com/ripple/rippled/blob/db6eb7840d6edd6d7344bcf1906fff71afe5555f/src/test/jtx/impl/envconfig.cpp#L36-L64
I would suggest adding a grpc_server
helper function in that same file that sets-up the grpc config needed and then add that to the Env
initialization for any tests that need it (e.g. Env env {*this, envconfig(grpc_server)};
)
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.
Added the helper and initialization code
src/ripple/app/main/GRPCServer.cpp
Outdated
port_str = port_pair.first; | ||
} | ||
|
||
server_address_ = ip_str + ":" + port_str; |
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.
this is not ipv6 friendly. I would suggest using beast::IP::Endpoint
to create a string from previously parsed address and port values (see setup_ServerHandler
above.
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.
changed
src/ripple/app/main/GRPCServer.h
Outdated
namespace { | ||
|
||
//helper function. strips port from endpoint string | ||
std::string getEndpoint(std::string const& peer) |
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.
this method is not ipv6 friendly. I'm not sure what format these peer strings have, but it seems like mainly they are endpoint strings with an optional scheme (dns://
) ? See if parseURL
will work for you (I think it will only parse if a scheme is present..not sure) or maybe just strip the scheme yourself by searching for //
and then use Endpoint::from_string
to do the rest.
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.
In my testing, the peer strings look like ipv4:127.0.0.1:49808
. parseUrl
is not working, and Endpoint::from_string
does not work either. If I strip ipv4
off of the front, I can then pass the string to Endpoint::from_string
, which is actually what I'm doing already (if you look at where this function is used). I'm not sure the best way to do this; the documentation for the peer()
method says a string is returned in uri format.
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.
I think what I have here will work, though correct me if I'm wrong. Apparently, the string is always of the form protocol:ip:port
. See this PR for reference (you sent this to me actually): grpc/grpc#14667
The non-unity build doesn't compile. |
Codecov Report
@@ Coverage Diff @@
## develop #3159 +/- ##
==========================================
- Coverage 70.78% 70.7% -0.09%
==========================================
Files 689 691 +2
Lines 54296 55089 +793
==========================================
+ Hits 38432 38949 +517
- Misses 15864 16140 +276
Continue to review full report at Codecov.
|
src/test/jtx/envconfig.h
Outdated
@@ -27,8 +27,7 @@ namespace test { | |||
|
|||
extern std::atomic<bool> envUseIPv4; | |||
|
|||
inline |
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.
I setup my editor to use the .clang-format
file in the repo, but inadvertently reformatted code that I didn't touch. I'm going to revert those formatting changes to this file (and the others).
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.
I'll revert these changes when I squash the commits
@seelabs @keefertaylor @MarkusTeufelberger I pushed all requested changes. Take a look and let me know if I missed anything or if I should change anything. Otherwise, resolve your comments. |
src/ripple/app/main/GRPCServer.cpp
Outdated
return sPtr.get() == ptr; | ||
}); | ||
BOOST_ASSERT(it != requests.end()); | ||
*it = requests.back(); |
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.
Don't assign, swap instead. Assigning needs to change the atomic reference counts. Swapping should be able to just move pointers around.
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.
changed to swap
src/ripple/app/main/GRPCServer.cpp
Outdated
GRPCServerImpl::start() | ||
{ | ||
// if config does not specify a grpc server address, don't start | ||
if (serverAddress_ == "") |
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.
I'd call empty()
instead of comparing to ""
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.
changed to calling empty()
src/ripple/app/main/GRPCServer.h
Outdated
public: | ||
virtual ~Processor() | ||
{ | ||
} |
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.
I'd default this instead. virtual ~Processor() = default
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.
defaulted
src/ripple/app/main/GRPCServer.h
Outdated
~GRPCServerImpl() | ||
{ | ||
} |
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.
I don't think we need to define this at all. I'd leave it out.
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.
removed
src/ripple/rpc/Context.h
Outdated
template <class T> | ||
struct ContextGeneric | ||
{ | ||
beast::Journal j; | ||
T params; | ||
Application& app; | ||
Resource::Charge& loadType; | ||
NetworkOPs& netOps; | ||
LedgerMaster& ledgerMaster; | ||
Resource::Consumer& consumer; | ||
Role role; | ||
std::shared_ptr<JobQueue::Coro> coro; | ||
InfoSub::pointer infoSub; | ||
}; | ||
|
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.
This duplicates most of the Context
struct. Maybe have a base class with the common fields? Also ContextGeneric
isn't a great name. Maybe GRPCContext
or somesuch.
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.
Created a base class and changed the names of the derived classes.
rpc::v1::CurrencyAmount& proto, | ||
RPC::ContextGeneric<T>& context, | ||
std::shared_ptr<Transaction> transaction, | ||
TxMeta const& transactionMeta) |
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.
We should look at trying to get rid of this code duplication if we can. Maybe we can do it in another PR - but we should at least make a TODO to look at this (you don't need to do this now).
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.
Added a TODO
rpc::v1::CurrencyAmount&, | ||
ContextGeneric<rpc::v1::GetTxRequest>&, | ||
std::shared_ptr<Transaction>, | ||
TxMeta const&); |
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.
We're going to have to insert one of these for every request type that is added. I don't love those kind of manual changes (tho we will get a linker error to remind us to do it, so it's not killer).
It looks like we only need the app and the ledger master. What do you think about passing those as parameters directly? That way this function doesn't need to be a template. If that's a problem, we should look at moving this implementation to the header.
If Context
and ContextGeneric
were refactor into a common base class, we could also look at passing the base class in. That might help with the code duplication as well.
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.
Passed in base class to remove template
src/test/rpc/GRPCTestClientBase.h
Outdated
|
||
struct GRPCTestClientBase | ||
{ | ||
GRPCTestClientBase(std::string const& port) |
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.
Be careful with one parameter non-explicit constructors - they define an automatic conversion. Some classes want that, but most of the time we should declare these constructors explicit
. I'd declare this one explicit
.
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.
changed to explicit
src/test/rpc/Submit_test.cpp
Outdated
rpc::v1::SubmitTransactionRequest request; | ||
rpc::v1::SubmitTransactionResponse reply; | ||
|
||
SubmitClient(std::string const& port) : GRPCTestClientBase(port) |
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.
explicit
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.
changed
src/test/rpc/Tx_test.cpp
Outdated
rpc::v1::GetTxRequest request; | ||
rpc::v1::GetTxResponse reply; | ||
|
||
GrpcTxClient(std::string const& port) : GRPCTestClientBase(port) |
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.
explicit
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.
changed
@seelabs pushed the additional requested changes |
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.
👍 Assuming tests pass.
We still need to make it clear that this is experimental/prototype code that may be removed in the future or changed in backward incompatible ways. Let's make sure that's documented somewhere front and center or we're going to make life hard for ourselves.
The two biggest things we need to work on to are:
-
A spec for what this gRPC API should be. We have limited chances to introduce new APIs. Let's make the most of our chance here. Elliot and Rome can help.
-
We need to reduce the code duplication of two nearly identical APIs.
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.
Thank you for carrying this across the finish line.
This PR is to add gRPC support to rippled. In this initial iteration, 4 API's are implemented: fee, account_info, tx and submit. tx only supports Payment transactions at this time, but will be expanded on in a future iteration.
Design document can be found here: https://github.com/cjcobb23/grpcRippledDesign/blob/master/design/design.md
Requirements document can be found here: https://github.com/cjcobb23/grpcRippledDesign/blob/master/requirements/requirements.md
Full RPC documentation will be provided in a later PR.
Please note, there are issues with Travis CI currently. We are working on them and will get them resolved before merging. This will most likely result in significant changes to the cmake files. However, we would like to get eyes on the code in the meantime. For now, to build the code: clone the repo, checkout the branch, and run:
git submodule update --init --recursive
.Reach out to me if you have any issues.