Skip to content
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

Add a client for BankForks #10728

Merged
merged 1 commit into from Aug 7, 2020
Merged

Conversation

garious
Copy link
Contributor

@garious garious commented Jun 20, 2020

Problems

  • Can't really unit-test client-side Rust apps
  • Little hope of leveraging RpcClient code in the browser

Summary of Changes

  • Add BanksClient, implemented with tarpc
  • Open a new validator port for the BanksClient bincode-via-TCP transport
  • Port solana-tokens to BanksClient

@codecov
Copy link

codecov bot commented Jun 20, 2020

Codecov Report

Merging #10728 into master will increase coverage by 0.0%.
The diff coverage is 88.0%.

@@           Coverage Diff            @@
##           master   #10728    +/-   ##
========================================
  Coverage    81.9%    82.0%            
========================================
  Files         318      323     +5     
  Lines       74872    75267   +395     
========================================
+ Hits        61371    61732   +361     
- Misses      13501    13535    +34     

@garious garious force-pushed the bank_forks_client branch 4 times, most recently from 31e5e36 to d526216 Compare June 22, 2020 23:22
@garious
Copy link
Contributor Author

garious commented Jun 22, 2020

@jstarry, can you evaluate solana_sdk::bank_forks_client::ThinClient for usage via WebAssembly?

@jstarry
Copy link
Member

jstarry commented Jun 23, 2020

@garious looks pretty good. It's cool that tarpc let's you bring your own client transport. We would need a wasm / browser friendly transport using fetch or websockets.

The only wasm blocker that I found was tarpc's use of tokio::time which uses std::time::Instant internally. That could easily replaced with something browser friendly. There may be other parts that aren't wasm friendly but nothing big stood out.

@jstarry
Copy link
Member

jstarry commented Jun 23, 2020

We could use / create something like https://github.com/tomaka/wasm-timer

@garious garious force-pushed the bank_forks_client branch 3 times, most recently from 997d575 to 9e6224c Compare June 26, 2020 23:44
@garious garious force-pushed the bank_forks_client branch 9 times, most recently from 1d9ae59 to 7ba3d79 Compare July 7, 2020 04:31
@garious garious force-pushed the bank_forks_client branch 7 times, most recently from 8588820 to eb9236a Compare July 11, 2020 18:06
@jstarry
Copy link
Member

jstarry commented Jul 12, 2020

Hey @garious I tried out your branch and was able to get it working in the browser using a ws transport 🎉

As I pointed out before, the main big hurdle is the lack of support of SystemTime and Instant APIs in the browser. For those, we need to use the browser api equivalent. To summarize:

  1. tarpc uses SystemTime::now() in context::current() which is used for creating requests. I patched tarpc to be able to supply a custom SystemTime which I can create in wasm without using SystemTime::now() (which panics). Pretty simple and I imagine we could upstream this (or a similar) change to tarpc
  2. tarpc uses tokio::time for timing out Futures. The Tokio API takes a deadline duration and adds to Instant::now() which panics in wasm. I opted to just elide timeouts entirely for my proof of concept but of course we don't want that. I'm not sure the best way to upstream a fix for this since there's not much movement on wasm support in tokio in general but they do seem open to it.

There were a few other issues:

  1. ed25519-dalek uses clear_on_drop which is wasm incompatible. They use the wasm-friendly zeroize in the dev branch so hopefully that gets released soonish. But for now, it's easy enough to patch it for wasm.
  2. I think you brought this up already but BanksClient has annoying lifetime constraints. I had to Rc<RefCell<..>> wrap it in order to make the rust compiler happy
  3. Adding the WS transport wasn't too bad but the wasm / async WS crate I used for the client is pretty new and needs improvements: https://docs.rs/ws_stream_wasm
  4. (minor) Had to reduce enabled features for some dependencies in the SDK and disable some wasm-incompatible modules like genesis_config, hard_forks, and shred_version.

@carllin
Copy link
Contributor

carllin commented Jul 23, 2020

@ryoqun, Right now queries to get specific account state on a cluster confirmed root < current root are unsupported, as they will just default to account state on current root right? I think this may be a bigger overarching issue with trying to query state from before the root.

@CriesofCarrots
Copy link
Contributor

@ryoqun, Right now queries to get specific account state on a cluster confirmed root < current root are unsupported, as they will just default to account state on current root right? I think this may be a bigger overarching issue with trying to query state from before the root.

@carllin yes, see: #11161

@garious
Copy link
Contributor Author

garious commented Jul 23, 2020

I'll rebase on #11188. More code review comments?

Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! I'm definitely in favour of tarpc

banks-client/src/lib.rs Outdated Show resolved Hide resolved
transaction-status/src/banks_trait.rs Outdated Show resolved Hide resolved
transaction-status/src/banks_trait.rs Outdated Show resolved Hide resolved
tokens/src/commands.rs Outdated Show resolved Hide resolved
tokens/src/commands.rs Show resolved Hide resolved
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
let info =
TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot);
self.transaction_sender.send(info).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we send the transaction through the preflight checker first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jstarry @mvines, so I nearly added this feature today, but am now thinking there's some good reasons not to. Unlike jsonrpc, I'd like for BanksServer to be lightweight enough that every validator could run it, and not be too concerned about a DoS. The only errors that simulate_transaction can detect are InsufficientFundsForTransactionFee and SigVerifyFailed. Both of those can be done client-side. The other thing simulating can do is find program errors without paying for the transaction fee - why should the RPC node eat that cost, when otherwise the user would pay the transaction fee, which pays for signature verification and running the program?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public RPC nodes are not voting. The DOS vector and the cost they eat doesn't have impact on security. admins can sandbox them without performance impact on consensus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aeyakovenko, not talking about DOSing the cluster, just the RPC node. It's a UX issue, not a security issue.

self,
_: Context,
transaction: Transaction,
commitment: CommitmentLevel,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The web3 SDK uses confirmation counts to confirm transactions now. I think we should have the BanksClient follow that example. The confirmations in TransactionStatus represent the number of cluster confirmations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll think on this. Personally, I'd prefer to subscribe to a stream of events.

chan.respond_with(server.serve()).execute()
})
// Max 10 channels.
.buffer_unordered(10)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that only 10 concurrent requests are allowed? Seems like it should be quite a bit higher

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we'll want to tune everything in this function. I think that can be a separate PR though.

async fn send_transaction(&mut self, transaction: Transaction) -> io::Result<()>;
async fn get_recent_blockhash(&mut self) -> io::Result<Hash>;
async fn get_fees(&mut self) -> io::Result<(FeeCalculator, Hash, Slot)>;
async fn process_transaction_with_commitment(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we replace commitment with confirmations in send_and_confirm_transaction, then I propose we default process_transaction to 0 or 1 confirmation(s).

Then we could have:

  1. send_transaction
  2. execute_transaction / process_transaction which waits for 0 or 1 confirmation(s)
  3. settle_transaction which waits for MAX confirmations
  4. [optional] confirm_transaction for user-specified confirmations

cc @aeyakovenko

banks-client/src/lib.rs Show resolved Hide resolved
@aeyakovenko
Copy link
Member

  • Execute

Transaction has been replicated by turbine across the cluster, or settled.

  • Settlement=X
    The block or a child has reached at least X% safety or has been finalized

  • Finalized=X
    Rooted by at least X%

Do we need to query specifically for local finality?

Tag @carllin

Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favor as well

banks-client/src/lib.rs Outdated Show resolved Hide resolved
@carllin
Copy link
Contributor

carllin commented Jul 24, 2020

@jstarry @aeyakovenko makes sense to me. Seems like on the scale of finality, with local finality one one end and MAX on the other, not sure practically what most clients would feel safe with. >33% seems like a good enough number to make rollback impossible.

For the execution receipts, feel free to add to the issue here: #11116

@garious
Copy link
Contributor Author

garious commented Jul 24, 2020

I don't like these APIs where the client attempts to pick one and only one event. We should be exposing the data, not interpreting it. In this case, it's an event stream. Instead of returning a value, we should be passing in a client-side object that gets notified each time a relevant event occurs.

@jstarry From what I can tell, tarpc may not work well for this case. In their pubsub example, the caller needs to provide an address for the server to connect to. That wouldn't allow a client to sit behind a firewall. Also, it lets the server pick the upstream stream transport, which is weird. It's possible tarpc needs some rework to allow for upstream communication via the same transport.

@garious
Copy link
Contributor Author

garious commented Jul 24, 2020

@jstarry, @aeyakovenko, @CriesofCarrots, perhaps there should be a CommitmentLevel::Confirmations(u8)?

@@ -46,11 +46,28 @@ impl CommitmentConfig {

#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
/// An attribute of a slot. It describes how finalized a block is at some point in time. For example, a slot
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CriesofCarrots @jstarry, how's this look? I don't know if we've ever stated "commitment level means ...". I found it quite difficult to describe - maybe a red flag that I got it wrong.

@jstarry
Copy link
Member

jstarry commented Jul 25, 2020

@jstarry From what I can tell, tarpc may not work well for this case. In their pubsub example, the caller needs to provide an address for the server to connect to. That wouldn't allow a client to sit behind a firewall. Also, it lets the server pick the upstream stream transport, which is weird. It's possible tarpc needs some rework to allow for upstream communication via the same transport.

@garious I just took a look as well and I agree. tarpc would need changes to support a proper pubsub interface with Stream responses. Right now, the framework considers a request to be finished when it a response is sent or the request is canceled. Once there are no more pending requests, the connection is closed.

@jstarry
Copy link
Member

jstarry commented Jul 25, 2020

@garious As for streams of events for something like finality, I also think it would be useful. I think the primary use case would end up being a progress indicator. The progress could indicate number of confirmations or X% of stake that has confirmed. This is functionality that we don't have in the current RPC API so I think it can come later

I don't like these APIs where the client attempts to pick one and only one event

If clients want to get notified for both execution and settlement for a given transaction, they can send two requests.

perhaps there should be a CommitmentLevel::Confirmations(u8)

I'm second guessing exposing confirmations in a confirmTransaction API. It's probably best to have a few sensible defaults in the API (either execute / settle or stick with commitment) to keep things simple for clients. If devs need something custom, they'll tell us and we can learn more about what guarantees they need and go from there

sdk/src/commitment_config.rs Outdated Show resolved Hide resolved
sdk/src/commitment_config.rs Show resolved Hide resolved
@garious
Copy link
Contributor Author

garious commented Jul 29, 2020

@jstarry, fyi, new https://github.com/google/tarpc/blob/master/tarpc/examples/pubsub.rs that shares the client connection

@garious garious force-pushed the bank_forks_client branch 3 times, most recently from 3a6fb8f to 201e3e4 Compare August 5, 2020 20:17
Also:
* Use BanksClient in solana-tokens
@garious
Copy link
Contributor Author

garious commented Aug 7, 2020

What do you all think about merging this one as-is? On its current trajectory, it's an improvement over RpcClient for some Rust CLI tooling and obsoletes BankClient. But also, with 1.3 out the door, we have plenty of time to make it even better before it goes into production.

@mvines
Copy link
Member

mvines commented Aug 7, 2020

sgtm! Honestly I've not reviewed but it's gotta be an improvement. How could it not be :)

@garious garious merged commit bad4868 into solana-labs:master Aug 7, 2020
garious added a commit to garious/solana that referenced this pull request Aug 20, 2020
Cherry-picked from solana-labs#10728, but without the changes to solana-tokens
garious added a commit that referenced this pull request Aug 20, 2020
Cherry-picked from #10728, but without the changes to solana-tokens
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants