8000 wallet: save and recover undo data from critical namestate transitions by pinheadmz · Pull Request #564 · handshake-org/hsd · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

wallet: save and recover undo data from critical namestate transitions #564

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

Merged
merged 9 commits into from
Mar 2, 2021

Conversation

pinheadmz
Copy link
Member
@pinheadmz pinheadmz commented Feb 24, 2021

Closes #510

This PR fixes a wallet issue that throws an assertion error during a rescan in a shockingly simple scenario:

  • Alice OPENs a name. It doesn't matter if she bids or not
  • Bob wins the name
  • Bob sends a TRANSFER for the name. It doesn't matter if it's ever FINALIZEd or what address the TRANSFER commits to
  • Alice rescans --> assertion error at TXDB.connectNames

Background

There are other related scenarios where Alice doesn't send the OPEN but does bid, and still loses. The crux of the issue has to do with UNDO DATA. Blockchain is generally designed to be append-only and therefore only move forwards. However there are scenarios in which the chain is rewound: a reorganization or a wallet rescan. In these scenarios, the software depends on UNDO data, which are essentially snapshots of state just before a transition happens. To move backwards, the undo data is retrieved and applied. Essentially for every new block and every wallet transaction, a "negative" is stored in the database and used to reverse whatever state update the action caused initially.

The Handshake wallet is a mysterious beast because a lot of its state depends on the actions of OTHER USERS, like tracking bids and determining who won an auction. Bids and Reveals are stored in special buckets in the database so that they can be reverted even if they don't actually directly affect the wallet's balance.

TRANSFER, FINALIZE, and REVOKE transactions up until now are not stored in special buckets in the database. In fact, they are not stored at all unless they directly affect the wallet's balance. That's what this PR changes. We still don't need to store the entire TX but we do need to store the UNDO data, and we need to remember to retrieve it if one of the above "rewind" scenarios is triggered.

With all this background I can now explain the bug at the lowest level: When the wallet rescans, it starts by reverting / undoing / unconfirming all the transactions in every block from chain tip backwards to the rescan target (provided as an argument by the user, default 0 aka genesis block). Before this PR, the wallet DID NOT revert transactions like TRANSFERs unless they involved the wallet directly. Because of this, when the wallet rescans the chain it encounters the TRANSFER a second time, but throws an error because it thinks the name is still in a transfer state (ns.transfer !== 0)

To test / fix your own wallet

If you've encountered this bug, you can test this PR by attempting to fix your own wallet:

# enter hsd repo
cd /path/to/hsd

# download this branch
git remote add pinheadmz https://github.com/pinheadmz/hsd
git fetch pinheadmz
git checkout rescantransfer1

# stop hsd (or killall hsd)
hsd-rpc stop

# back up your wallet
cp -r ~/.hsd/wallet ~/.hsd/wallet-backup-for-rescantransfer1

# restart hsd
hsd

# deep clean the wallet and rescan, may require API key
curl 127.0.0.1:12039/deepclean -X POST --data '{"I_HAVE_BACKED_UP_MY_WALLET":true}'
hsw-cli rescan 0

# watch wallet height and wait until it is fully synced (check height in JSON result)
hsw-rpc getwalletinfo

# rescan a second time
hsw-cli rescan 0

# check
hsw-rpc getwalletinfo
hsw-cli balance

Bonus

Because this PR is urgent and likely to get merged and released quickly, I attached a rider ;-)

I cherry picked 4b75f8a from #499 which bypasses a too-strict length requirement in Address length. (see also bcoin-org/bcoin#989) Without this fix, assertion errors are still thrown during rescan for wallets (like mine) that have ever sent to a nulldata address. I had to add this bug fix to test this PR against my own wallet.

TODO

Just by eyeballing the code, I assume that this bug will have the same effect if Bob (see above) sends a FINALIZE or REVOKE -- because these covenant types update namestate properties that are also assert()'ed in txdb. So this PR was written to also catch those types but I still need to add explicit tests

  • Add tests for FINALIZE and REVOKE 5013fff

💰 PR REVIEW BOUNTY: 💰

10 HNS: add a comment to the PR with an original meme expressing how tired we are all of all the damn wallet bugs
100 HNS: add a comment with a screen shot proving you pulled the branch and ran the test locally: npm run test-file test/wallet-rescan-test.js
1000 HNS: add a comment that actually influnces me to change the pull request in some way. if its just a typo/ nit ill send you..... oh i dunno 200 HNS

@coveralls
Copy link
coveralls commented Feb 24, 2021

Pull Request Test Coverage Report for Build 614562008

  • 16 of 16 (100.0%) changed or added relevant lines in 1 file are covered.
  • 2 unchanged lines in 2 files lost coverage.
  • Overall coverage decreased (-8.2%) to 59.405%

Files with Coverage Reduction New Missed Lines %
lib/utils/binary.js 1 56.9%
lib/wallet/txdb.js 1 84.71%
Totals Coverage Status
Change from base Build 541449016: -8.2%
Covered Lines: 19519
Relevant Lines: 30588

💛 - Coveralls

@pinheadmz pinheadmz requested a review from chjj February 24, 2021 16:05
@pinheadmz
Copy link
Member Author
pinheadmz commented Feb 24, 2021

Requesting reviews from: @brandondees @chikeichan @turbomaze @HDardenne

@rozaydin
Copy link
rozaydin commented Feb 24, 2021

i am hoping to get that 100 hns, hs1quxtpel7xnf856ljvgj4jleqlsn3hqpx54s40nc

bounty

@chjj
Copy link
Contributor
chjj commented Feb 24, 2021

This fixes the assertion error, but I'm now wondering what other actions are potentially affected by this bug. There could be some edge cases where updates/renewals/etc. are not rolled back properly. Granted, it's for a name you probably don't particularly care about (since you don't own it), but there may be other implications down the line.

To be safe we could consider updating the block record/map for damn near everything. Though, that might get funky too.

We should merge/release this but also continue thinking about potential edge cases here.

Modifies `txdb` during TX confirm and unconfirm:
If a TX updates namestate in a critical way, save it. Even if it
does not affect wallet balance or TX history. During rollback, revert,
reorg, or rescan -- look for the name undo data even if the TX
is not in wallet history, and apply.
@pinheadmz
Copy link
Member Author

@rozaydin thank you for reviewing! I sent you 300 HNS

@brandondees
Copy link

hs1qexn22kqtlle9nqyzg2pkn3ldkw0ksw4harymq0

Screenshot from 2021-02-24 13-10-04

tests passing here, looking at the rest now

@brandondees
Copy link

@chjj any idea of what exactly might get funky with tracking everything? seems like the sensible and safe thing to do from my limited view

assert.strictEqual(registerBlocks[0].txs.length, 2);
});

it('should get namestate', async () => {

Choose a reason for hiding this comment

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

it looks like these test blocks are sequentially order dependent, right? this might should be spelled out somewhere up top or at the repo/contributing.md level since it's a general best practice to write test cases that are strictly independent and randomize the order.

Copy link
Member Author

Choose a reason for hiding this comment

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

thats true, we should mention that in contirbuting. In this case I am using separate describe blocks for overall test cases, and it blocks to verify incrementally. Its a standard we frequently break in this repo.

Choose a reason for hiding this comment

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

cool, well then yeah just something newcomers should be able to avoid tripping over by viewing the key docs

// Hash of the FINALIZE transaction
let aliceFinalizeHash;

async function mineBlocks(n, addr) {

Choose a reason for hiding this comment

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

i feel like all this setup procedure stuff could be a lot more DRY just by moving it up a scope. this could become cumbersome if some of the internal apis change and a lot of test code has to be changed.

Choose a reason for hiding this comment

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

or if it's special per context it should probably be highlighted what exactly is special in each place

Copy link
Member Author

Choose a reason for hiding this comment

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

true, lots of test modules have a mineBlocks function. They are usually slightly different in each case but we could probably add this to a util file in a future PR. It would need to accept a chain and miner as parameters though, which are usually local to each test. In this case I need the function to return the blocks, but thats not always the case in other test that mine blocks.

Choose a reason for hiding this comment

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

gotcha, yeah i could see building up a small library of these sorts of things to raise the level of abstraction for various bob/alice scenarios but you'd need to find the common patterns. obvs all tangential to this branch, just something that sticks out to me as worth improving.

@brandondees
Copy link

Read through all the changes, all looks good to me overall, overlooking style preference stuff.

I think the test cases are probably adequate but could maybe be expanded to prove that balances aren't impacted too, if that's a potential side effect.

@chikeichan
Copy link
Contributor

tested import and rescan in this branch in Bob. My wallet used to run into rescan loop on second rescan after first import. Second and subsequent rescans now can perform without issues on using this PR.

pinheadmz pushed a commit to pinheadmz/hsd that referenced this pull request Mar 1, 2021
@pinheadmz pinheadmz merged commit 914d9e2 into handshake-org:master Mar 2, 2021
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.

Assertion error when rescanning a block with TRANSFER/FINALIZE for wallet
6 participants
0