bitcoinj is a Bitcoin library for the JVM language that specializes in SPV mode.
This time, I translated about 1/3 of the bitcoinj document "Working with the wallet". [^ 1] By reading this document, we believe that you will deepen your knowledge of creating your own wallet.
If you make a mistake, please point it out.
The original text is here. Working with the wallet https://bitcoinj.github.io/working-with-the-wallet
Working with the wallet
Learn how to use the Wallet class and create custom transactions.
Introduction
The Wallet class is one of the most important classes in bitcoinj and has the following features:
--Stores keys and transactions related to keys. --Create a new transaction that consumes the saved transaction output. --Notify when the contents of the wallet are changed.
In order to build different types of apps, you will need to learn how to use Wallet.
This article assumes that you have read the Satoshi white paper and Working With Transactions.
Setup
For optimal operation, the wallet should be connected to BlockChain
and either Peer
or PeerGroup
.
BlockChain is passed to the wallet's constructor and can send and receive blocks related to the wallet and extract transactions related to the wallet (transactions that send and receive coins using the key in the wallet).
Peer / PeerGroup broadcasts transactions to the network before the transactions in the wallet are included in the block.
The wallet starts with no transactions, that is, zero balance, regardless of its state on the blockchain. To use Wallet, you need to download the blockchain. This will load the transaction into your wallet for analysis and payment.
Wallet wallet = new Wallet(params);
BlockChain chain = new BlockChain(params, wallet, ...);
PeerGroup peerGroup = new PeerGroup(params, chain);
peerGroup.addWallet(wallet);
peerGroup.startAndWait();
Getting addresses
Of course, the above code pieces have no way to deposit into the wallet and are not very useful. To get the key and address from the wallet, use the following API call.
Address a = wallet.currentReceiveAddress();
ECKey b = wallet.currentReceiveKey();
Address c = wallet.freshReceiveAddress();
assert b.toAddress(wallet.getParams()).equals(a);
assert !c.equals(a);
These are handed over to receive payment. The wallet has the concept of a "current" address. This is because of the GUI wallet where you always want to see the address. If the current address is used, it will change to the new address. The freshReceiveKey / Address method, on the other hand, always returns the newly derived address.
Seeds and mnemonic codes
The keys and addresses returned by these methods are deterministically derived from seed using the algorithms specified in BIP 32 and BIP 39. The life of the key is as follows.
SecureRandom
and selects a random 128-bit entropy.Keys, including seeds and precomputed keys, are stored on disk to avoid slow re-deriving loops when loading the wallet.
The mnemonic code is designed to be easier to handle and write down than a raw private key. The mnemonic code reduces the possibility of typographical errors and allows the user to easily write it down with a pen or paper. Therefore, it is advisable to expose the word to the user as a backup mechanism (write down the date to speed up the restore).
You can work as follows:
DeterministicSeed seed = wallet.getKeyChainSeed();
println("Seed words are: " + Joiner.on(" ").join(seed.getMnemonicCode()));
println("Seed birthday is: " + seed.getCreationTimeSeconds());
String seedCode = "yard impulse luxury drive today throw farm pepper survey wreck glass federal";
long creationtime = 1409478661L;
DeterministicSeed seed = new DeterministicSeed(seedCode, null, "", creationtime);
Wallet restoredWallet = Wallet.fromSeed(params, seed);
// now sync the restored wallet as described below.
The look-ahead zone plays an important role in keeping the wallet in sync. The default size of the look-ahead zone is 100 keys. If wallet A is replicated to wallet B, wallet A issues 50 keys, and only the last key is actually used to receive the payment, wallet B recognizes the payment and moves through the look-ahead zone. And track a total of 150 keys. If Wallet A distributes 120 keys and receives only the 110th payment, Wallet B will not be aware of what happened. For this reason, it is important to estimate the number of unpaid addresses awaiting payment at any given time for wallet synchronization. The default of 100 is chosen to suit the consumer's wallet, but merchant scenarios may require larger zones.
Replaying the chain
If you are importing a key that has already been used into your wallet, you must reset the wallet (using the reset
method) to delete the transaction and re-download the chain to get the transaction for the added key. .. At this time, there is no way to play the blockchain on a wallet that already holds transactions, and if you try this, you can corrupt your wallet. This is subject to change in the future. Alternatively, you can download the raw transaction data from another source, such as Block Explorer, and insert the transaction directly into your wallet. However, this is currently unsupported and untested. For most users, importing an existing key is a bad idea and represents a situation where there is some serious lack of functionality. Please contact us if you need to import your keys into your wallet on a regular basis.
The wallet works with other classes in the system to speed up synchronization with the blockchain, but by default only some optimizations are enabled. Please read SpeedingUpChainSync to understand the optimization contents and setting method by the wallet / PeerGroup.
Creating spends
After catching up with the chain, you can spend some coins:
System.out.println("You have " + Coin.FRIENDLY_FORMAT.format(wallet.getBalance()));
Expenditure consists of 4 steps.
For convenience, there are helper methods that perform these steps. In the simplest case:
// Get the address 1RbxbA1yP2Lebauuef3cBiBho853f7jxs in object form.
Address targetAddress = new Address(params, "1RbxbA1yP2Lebauuef3cBiBho853f7jxs");
// Do the send of 1 BTC in the background. This could throw InsufficientMoneyException.
Wallet.SendResult result = wallet.sendCoins(peerGroup, targetAddress, Coin.COIN);
// Save the wallet to disk, optional if using auto saving (see below).
wallet.saveToFile(....);
// Wait for the transaction to propagate across the P2P network, indicating acceptance.
result.broadcastComplete.get();
The sendCoins
method returns the generated transaction and the ListenableFuture
. ListenableFuture allows you to block processing until a transaction is accepted by the network (sent to one Peer and received from another). Also, for the returned Future, you can register a callback to know when the propagation is completed, register your own TransactionConfidence.Listener
in the transaction to monitor the propagation status, or mine yourself. I can do it.
At lower levels, you can do these steps yourself:
// Make sure this code is run in a single thread at once.
SendRequest request = SendRequest.to(address, value);
// The SendRequest object can be customized at this point to modify how the transaction will be created.
wallet.completeTx(request);
// Ensure these funds won't be spent again.
wallet.commitTx(request.tx);
wallet.saveToFile(...);
// A proposed transaction is now sitting in request.tx - send it in the background.
ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(request.tx);
// The future will complete when we've seen the transaction ripple across the network to a sufficient degree.
// Here, we just wait for it to finish, but we can also attach a listener that'll get run on a background
// thread when finished. Or we could just assume the network accepts the transaction and carry on.
future.get();
To create a transaction, first use the static helper method of the SendRequest
object. The SendRequest
consists of a partially invalidTransaction
object that contains unchanged items such as fees, address changes, and future privacy features (such as coin selection methods). You can modify partial transactions as needed, or build your own transactions in scratch. The static helper method of SendRequest
is another simple way to build a partial transaction.
Then complete the request. This means that the transaction in the send request has been added I / O and signed to enable the transaction. The transaction is now accepted on the Bitcoin network.
Note that no lock is held between completeTx
and commitTx
. Therefore, if the wallet is modified outside your control, this code may conflict and fail. For example, if the wallet key was exported and used elsewhere, and a transaction using the selected output occurred between two method calls. Using a simple structure that locks the wallet while both operations are performed can ensure that you do not commit double spending.
When to commit a transaction
Committing a transaction means updating the wallet's penta flag so that it is not reused. It is important to commit transactions at the right time, and there are various strategies for doing so.
The default behavior of sendCoins () is to broadcast after a commit. In most cases this is a good choice. This means that if you have problems with your network, or if multiple threads try to create and broadcast transactions at the same time, you can't accidentally double spend. On the other hand, if the network does not accept the transaction for any reason (for example, inadequate fees / non-standard form), the wallet considers the money consumed and you have to fix the problem.
You can also use peerGroup.broadcastTransaction
instead of just calling wallet.commitTx
. Once a transaction is referenced by multiple Peers, the transaction is given to the wallet and then committed. The main reason you want to commit after a successful broadcast is if you're trying out new code and you're creating a transaction that isn't always acceptable. In this case, having to roll back the wallet all the time is a hassle. After it turns out that the network always accepts transactions, you can create and complete send requests and commit transaction results, all under a single lock. So multiple threads don't accidentally generate double spending.
[^ 1]: I really wanted to translate everything, but it was too long and I failed.
Recommended Posts