In the background to PR 1856 I described the motivation and general approach to adding this friendly feature to the Dogecoin core.
It didn’t take too long to get something together.
As you may have gathered from the previous posts, I consider myself a lazy (in a good way) programmer: if there’s existing code that more or less does the right thing, I’d rather follow its style and customs than invent something new.
In the case of adding a feature to the Qt GUI, there was already a lot of existing code: create a new tab, create a new menu entry, pop up a text box, include a label and a default field, et cetera.
One thing I like about Qt is that it uses XML to define the window layouts. There are pros and cons to this, but this design decision decouples the layout of the GUI from the code that makes the GUI work. That complexity has to go somewhere, but when it comes time to write C++ code to display or hide GUI elements or process incoming data and act on it, that code doesn’t have to do much besides refer to GUI elements defined elsewhere (in the XML in this case).
I figured I’d modify the XML by hand and… okay, that might have been a little too lazy.
The right way to make a change like this–adding a new group of GUI elements and behavior–is to install and run Qt’s GUI designer. I fiddled with the XML by hand for a little bit, then decided I was outsmarting myself and installed the developer tools.
A little bit of work later and I had what I wanted: the GUI definition that
made sense. These are the files called something.ui
in the pull request,
with the contrib/bitcoin-qt.pro
file tracking that I added one new dialog to
the GUI.
The flow goes a little bit like this. To import a new private key, use the File menu. Choose Import Private Key. This pops up a new modal dialog that asks you for the key and an optional label, then allows you to select whether to rescan the entire blockchain. You also have action buttons: Reset, Import, and Cancel.
The heart of the GUI actions lives in the file src/qt/importkeysdialog.cpp
.
This C++ class performs the actions of the buttons from the GUI: resetting the
form’s values, cancelling the operation and discarding its values, or
attempting to import the key.
I thought the actual key importing would be a lot easier than it was. I knew
I’d have to handle error cases, such as “you provided no key to import” or
“what you provided isn’t a valid key”. I couldn’t reuse code from the RPC
system directly, but fortunately I could reuse a class called CBitcoinSecret
to manage key information, such as checking validity.
Then I ran into a problem that Ross noticed right off the bat.
There are two reasons you’d want to import a private key. First, because you already have transactions on the blockchain somewhere and you want to access those transactions (especially any unspent funds) with the wallet into which you’re importing the key. Second, because you generated a key elsewhere and you really love it, but there are no transactions on the blockchain for it yet.
Mostly you’ll do this for the first reason.
Importing a key doesn’t do much. At least, it has no obvious effect if you have transactions on the blockchain for that key. You’re unlikely to see them unless you tell the Dogecoin core to scan the blockchain for transactions which involved that key.
That’s what the rescan behavior does.
When you use the RPC command to import a private key, you can optionally perform that rescan operation. You definitely want to do it to get your wallet up to date with the transactions which included your key, but you might not want to do it right now because it can take a while.
As Ross pointed out, doing this rescan from the GUI can take a while.
That’s still true with the PR as applied and available now, but it was very, very evident with the PR as I originally wrote it.
This rescanning operation takes a while, and if you’re not careful (I wasn’t careful), you end up doing it in a way that prevents any other work from taking place.
In C++ terms, you’re doing this work on the GUI thread. Think of a thread as a way to have a computer do more than one thing at a time. Ten threads means it’s switching back and forth between ten things, but they’re all going on more-or-less simultaneously. One thread and only one thing is going on at a time.
I put the wallet rescanning on the GUI thread, so the program looked to users like it was locked up. The GUI wouldn’t display anything. The modal dialog to import the key wouldn’t even go away in the first place. Users would look at the screen and say “Nothing’s happening. This is awful. Some developer was way too lazy. I’m going to play video games instead.”
That’s the thing about developer laziness. It’s always going to come back to bite someone. I’m glad Ross gave me that feedback early in the process, even though it meant more work for me, because the entire goal of this work was to make the lives of users better, and the work isn’t done until we’ve accomplished that.
How did I do that? (If you’ve done much GUI programming or thread programming, you already know, but don’t spoil the surprise for the rest of the readers!) I’ll explain that next time.