-
Notifications
You must be signed in to change notification settings - Fork 86
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
Get latest block from zcashd #348
Conversation
I'd like to understand better the motivation for this change. Is this strictly a performance improvement? Or does this fix incorrect behavior? I would guess this isn't a performance-only improvement, because, as you mentioned, it happens only rarely. If it fixes a behavioral problem, then I don't think it will actually fix anything, because even with this change, the result of I'm not aware if this was intentional, but It may be helpful to understand that lightwalletd does something perhaps unexpected: Every few seconds, it forgets the latest block. If it knows about block 10 as the latest block, then it polls (every couple of seconds) for a block at height 11. Usually, the reply is "no such block" (until block 11 is mined). What I was concerned about while coding this is: What happens if a one-block reorg occurs, replacing block 10? Simply polling for block 11 will continue to return "no such block" -- with no indication that a reorg has occurred. Only when a new block 11 is mined (which takes, on average, 75 seconds but can be much longer) will lightwalletd notice (after fetching this version of block 11) that its prev-hash doesn't refer to block 10, so it will begin to back up, looking for the fork point, and then move forward along the new chain. This delay (in recognizing that a reorg has occurred) seemed to me to be much too long. The simplest, most bullet-proof way to solve this problem is to completely forget about the tip every few seconds. If block 10 is the tip, lightwalletd suddenly thinks 9 is the tip, and immediately request 10, and, if there's been a reorg, it will get the new version of 10. (If no reorg, it will get the version of 10 that it forgot, no harm done.) One side-effect of this approach is that every few seconds, there's a small window during which At first I thought, that's very bad! But then I realized that wallets have to be able to deal with this situation anyway. There could be a real reorg that makes 9 the tip, at least for an instant, while we're fetching the new version of 10 from zcashd. And here's a situation that's even more subtle: Suppose 10 is the tip, and a reorg happens with 5 as the fork point, and there's a new 6 and a new 7, and that's the new tip. It's true that the better branch will almost always be at least as long as the old (abandoned) branch, but "best chain" isn't based on length, it's based on total work. So it's possible that the new best chain has 7 as its tip. If we simply keep asking for 11, we'll have to wait for 8, 9, 10, and 11 to be mined, before we realize there's been a reorg. The way lightwalletd works today in this (admittedly unlikely) scenario is that (as I said) it will forget about 10 every few seconds (think 9 is the tip), so it will re-request 10, get "no such block", so immediately request 9, then 8, getting "no such block" to those requests, then 7, and get the new block 7, but notice its prev-hash doesn't refer to the 6 we know about, so it will request 6, and will see that its prev-hash does indeed refer to the 5 we know about, so it will keep that 6, then move ahead in the normal way, requesting 7, and then finally 8 but will get a "no such block" and now we're back to our normal synced state. So, during this time, |
By the way, it seems to me that the wallets must implement an approach equivalent to this for the same reason. If the wallet polls for new blocks by requesting the latest height, or requesting the block at what they think is the tip height plus one, then it won't know there's been a reorg until quite some time has passed. I'm sorry I didn't think of this before now, but it seems like we could improve this by adding a new gRPC that takes as its argument the hash of the block that the wallet considers to be the tip, and the gRPC will return the child of that hash, or "no such block" if it hasn't been mined yet. Both of those are sunny-day cases. But, let's take the previous example of a one-block reorg. If the wallet thinks the tip is height 10, the wallet would make a gRPC call passing its hash, and lightwalletd would notice that has doesn't match the hash of what it considers to be block 10 (or that block doesn't exist at all), so it would return an error that says, in effect, "the hash you sent me doesn't correspond to a block I know about". So then the wallet can immediately request block 9 by sending its hash, and lightwalletd would reply with the new (post-reorg) version of block 10. So, to summarize, there should be a form of
This same change could be implemented in |
Thanks for this great explanation @LarryRuane. I think it's very important to have it surfaced somewhere. As you mentioned this is probably the same logic than the one needed for chain reorgs, but probably people don't expect the lightclient server to do this 'forget' logic on its own. |
Having worked on #358, I think I now see the motivation for this PR better. As I was testing #358, I noticed that the But also, I noticed there's a way to fix this problem without changing the
This way we'll detect a reorg within a few seconds even if a one-block (or any length) reorg changes the tip. It won't be necessary to "forget" the latest block as is currently done, and we won't have this problem of I don't know exactly how the various wallets detect a reorg, but we could implement the same functionality in the gRPC interface -- either add a |
This is very cool. I think detecting 1-block reorgs is very important, and I think Zecwallet has not been doing that properly. I'm 100% in favor of @LarryRuane 's comment above - LightwalletD should detect the 1-block reorg, and clients should too. Thanks for looking into this and fixing it! |
@LarryRuane the pseudocode above looks good to me. The "this is a reorg, back up" part should probably go inside the |
Closing in favor of #363 (see #348 (comment) above). |
I propose that we get the latest block from
zcashd
instead of from the cache.This fixes an issue where in some rare cases, the value returned from
GetLightDInfo
doesn't match whatGetLatestBlock
returns if the cache is slightly behind.Also, If the cache is behind or if the server is just starting, we should ask zcashd for the latest block height, because when the clients request for a block and we don't have it, we go to
zcashd
anyway.